This is an automated email from the ASF dual-hosted git repository.

sureshanaparti pushed a commit to branch main
in repository 
https://gitbox.apache.org/repos/asf/cloudstack-terraform-provider.git


The following commit(s) were added to refs/heads/main by this push:
     new 9159ca8  Add GPU support: gpu_card / vgpu_profile data sources and GPU 
on service offerings (#315)
9159ca8 is described below

commit 9159ca8294bc58ff92d997c687c3c517e958d35e
Author: poddm <[email protected]>
AuthorDate: Tue Sep 15 05:22:17 2026 -0700

    Add GPU support: gpu_card / vgpu_profile data sources and GPU on service 
offerings (#315)
---
 .github/actions/setup-cloudstack/action.yml        |  28 +++
 .gitignore                                         |   3 +
 cloudstack/data_source_cloudstack_gpu_card.go      | 153 ++++++++++++++++
 cloudstack/data_source_cloudstack_gpu_card_test.go |  51 ++++++
 cloudstack/data_source_cloudstack_vgpu_profile.go  | 197 +++++++++++++++++++++
 .../data_source_cloudstack_vgpu_profile_test.go    |  50 ++++++
 cloudstack/provider.go                             |   2 +
 cloudstack/provider_test.go                        |  31 ++++
 .../service_offering_constrained_resource.go       |  22 +--
 .../service_offering_constrained_resource_test.go  |  58 ++++++
 cloudstack/service_offering_fixed_resource.go      |  22 +--
 cloudstack/service_offering_fixed_resource_test.go |  53 ++++++
 cloudstack/service_offering_models.go              |   7 +
 cloudstack/service_offering_schema.go              |  44 +++++
 .../service_offering_unconstrained_resource.go     |  22 +--
 ...service_offering_unconstrained_resource_test.go |  47 +++++
 cloudstack/service_offering_util.go                |  65 ++++++-
 website/docs/d/gpu_card.html.markdown              |  51 ++++++
 website/docs/d/vgpu_profile.html.markdown          |  58 ++++++
 .../r/service_offering_constrained.html.markdown   |  12 ++
 .../docs/r/service_offering_fixed.html.markdown    |  11 ++
 .../r/service_offering_unconstrained.html.markdown |  11 ++
 22 files changed, 949 insertions(+), 49 deletions(-)

diff --git a/.github/actions/setup-cloudstack/action.yml 
b/.github/actions/setup-cloudstack/action.yml
index 7d53ab8..f20bd52 100644
--- a/.github/actions/setup-cloudstack/action.yml
+++ b/.github/actions/setup-cloudstack/action.yml
@@ -96,6 +96,34 @@ runs:
         echo "api_key=$CLOUDSTACK_API_KEY" >> $GITHUB_OUTPUT
         echo "secret_key=$CLOUDSTACK_SECRET_KEY" >> $GITHUB_OUTPUT
 
+    - name: Discover GPU devices
+      shell: bash
+      run: |
+
+        set -euo pipefail
+
+        # GPU management APIs require CloudStack 4.22.0.0 or newer.
+        CURRENT_VERSION="${{ inputs.cloudstack-version }}"
+        if [ "$(printf '%s\n4.22.0.0\n' "$CURRENT_VERSION" | sort -V | head 
-n1)" != "4.22.0.0" ]; then
+          echo "CloudStack $CURRENT_VERSION predates GPU support (4.22.0.0+); 
skipping GPU discovery."
+          exit 0
+        fi
+
+        CONTAINER_ID=$(docker container ls --format=json -l | jq -r .ID)
+
+        # Refresh the API cache so cmk knows the 4.22+ GPU APIs (the bundled
+        # cache predates them); "discover gpudevices" maps to 
discoverGpuDevices.
+        docker exec $CONTAINER_ID cmk -p localcloud sync
+
+        # Discover GPU devices on each routing host so the simulator exposes 
GPU
+        # cards and vGPU profiles for the GPU acceptance tests. 
discoverGpuDevices
+        # takes a required "id" argument that is the host's UUID.
+        HOST_IDS=$(docker exec $CONTAINER_ID cmk -p localcloud list hosts 
type=Routing --output json | jq -r '.host[].id')
+        for HOST_ID in $HOST_IDS; do
+          echo "Discovering GPU devices on host $HOST_ID"
+          docker exec $CONTAINER_ID cmk -p localcloud discover gpudevices 
id=$HOST_ID
+        done
+
     - name: Install CMK
       shell: bash
       run: |
diff --git a/.gitignore b/.gitignore
index 2c88f78..0980d2a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,6 +23,9 @@ website/node_modules
 *.iml
 *.test
 *.iml
+dist/
+.gocache/
+.gomodcache/
 
 website/vendor
 
diff --git a/cloudstack/data_source_cloudstack_gpu_card.go 
b/cloudstack/data_source_cloudstack_gpu_card.go
new file mode 100644
index 0000000..b7224bb
--- /dev/null
+++ b/cloudstack/data_source_cloudstack_gpu_card.go
@@ -0,0 +1,153 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package cloudstack
+
+import (
+       "fmt"
+       "log"
+       "strconv"
+
+       "github.com/apache/cloudstack-go/v2/cloudstack"
+       "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+func dataSourceCloudstackGpuCard() *schema.Resource {
+       return &schema.Resource{
+               Read: datasourceCloudStackGpuCardRead,
+               Schema: map[string]*schema.Schema{
+                       "filter": dataSourceFiltersSchema(),
+
+                       //Computed values
+                       "id": {
+                               Type:     schema.TypeString,
+                               Computed: true,
+                       },
+                       "name": {
+                               Description: "the name of the GPU card",
+                               Type:        schema.TypeString,
+                               Computed:    true,
+                       },
+                       "device_id": {
+                               Description: "the device id of the GPU card",
+                               Type:        schema.TypeString,
+                               Computed:    true,
+                       },
+                       "device_name": {
+                               Description: "the device name of the GPU card",
+                               Type:        schema.TypeString,
+                               Computed:    true,
+                       },
+                       "vendor_id": {
+                               Description: "the vendor id of the GPU card",
+                               Type:        schema.TypeString,
+                               Computed:    true,
+                       },
+                       "vendor_name": {
+                               Description: "the vendor name of the GPU card",
+                               Type:        schema.TypeString,
+                               Computed:    true,
+                       },
+               },
+       }
+}
+
+func datasourceCloudStackGpuCardRead(d *schema.ResourceData, meta interface{}) 
error {
+       cs := meta.(*cloudstack.CloudStackClient)
+       p := cs.GPU.NewListGpuCardsParams()
+
+       if err := applyGpuCardFilters(p, d.Get("filter").(*schema.Set)); err != 
nil {
+               return err
+       }
+
+       csGpuCards, err := cs.GPU.ListGpuCards(p)
+       if err != nil {
+               return fmt.Errorf("failed to list GPU cards: %s", err)
+       }
+
+       switch len(csGpuCards.GpuCards) {
+       case 0:
+               return fmt.Errorf("no GPU cards found")
+       case 1:
+               return gpuCardDescriptionAttributes(d, csGpuCards.GpuCards[0])
+       default:
+               return fmt.Errorf("%d GPU cards matched the given filters; "+
+                       "refine the filters (e.g. add device_id or vendor_id) 
to match exactly one card", len(csGpuCards.GpuCards))
+       }
+}
+
+func gpuCardDescriptionAttributes(d *schema.ResourceData, card 
*cloudstack.GpuCard) error {
+       d.SetId(card.Id)
+
+       fields := map[string]interface{}{
+               "id":          card.Id,
+               "name":        card.Name,
+               "device_id":   card.Deviceid,
+               "device_name": card.Devicename,
+               "vendor_id":   card.Vendorid,
+               "vendor_name": card.Vendorname,
+       }
+
+       for k, v := range fields {
+               if err := d.Set(k, v); err != nil {
+                       log.Printf("[WARN] Error setting %s: %s", k, err)
+               }
+       }
+
+       return nil
+}
+
+func applyGpuCardFilters(p *cloudstack.ListGpuCardsParams, filters 
*schema.Set) error {
+       seen := make(map[string]bool)
+       for _, f := range filters.List() {
+               filter := f.(map[string]interface{})
+               name := filter["name"].(string)
+               value := filter["value"].(string)
+
+               if seen[name] {
+                       return fmt.Errorf("duplicate filter %q; each filter 
name may only be specified once", name)
+               }
+               seen[name] = true
+
+               switch name {
+               case "id":
+                       p.SetId(value)
+               case "device_id":
+                       p.SetDeviceid(value)
+               case "device_name":
+                       p.SetDevicename(value)
+               case "vendor_id":
+                       p.SetVendorid(value)
+               case "vendor_name":
+                       p.SetVendorname(value)
+               case "keyword":
+                       p.SetKeyword(value)
+               case "active_only":
+                       b, err := strconv.ParseBool(value)
+                       if err != nil {
+                               return fmt.Errorf("invalid boolean value %q for 
filter %q: %s", value, name, err)
+                       }
+                       p.SetActiveonly(b)
+               default:
+                       return fmt.Errorf("unsupported filter %q; supported 
filters: id, device_id, device_name, vendor_id, vendor_name, keyword, 
active_only", name)
+               }
+       }
+
+       return nil
+}
diff --git a/cloudstack/data_source_cloudstack_gpu_card_test.go 
b/cloudstack/data_source_cloudstack_gpu_card_test.go
new file mode 100644
index 0000000..9d2a43d
--- /dev/null
+++ b/cloudstack/data_source_cloudstack_gpu_card_test.go
@@ -0,0 +1,51 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package cloudstack
+
+import (
+       "testing"
+
+       "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+)
+
+func TestAccGpuCardDataSource_basic(t *testing.T) {
+       resource.Test(t, resource.TestCase{
+               PreCheck:  func() { testAccPreCheckGPU(t) },
+               Providers: testAccProviders,
+               Steps: []resource.TestStep{
+                       {
+                               Config: testGpuCardDataSourceConfig_basic,
+                               Check: resource.ComposeTestCheckFunc(
+                                       
resource.TestCheckResourceAttrSet("data.cloudstack_gpu_card.test", "id"),
+                                       
resource.TestCheckResourceAttr("data.cloudstack_gpu_card.test", "name", 
"Simulator Graphics Card Pro"),
+                               ),
+                       },
+               },
+       })
+}
+
+const testGpuCardDataSourceConfig_basic = `
+data "cloudstack_gpu_card" "test" {
+  filter {
+    name  = "device_name"
+    value = "Graphics Card Pro"
+  }
+}
+`
diff --git a/cloudstack/data_source_cloudstack_vgpu_profile.go 
b/cloudstack/data_source_cloudstack_vgpu_profile.go
new file mode 100644
index 0000000..453af4f
--- /dev/null
+++ b/cloudstack/data_source_cloudstack_vgpu_profile.go
@@ -0,0 +1,197 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package cloudstack
+
+import (
+       "fmt"
+       "log"
+       "strconv"
+
+       "github.com/apache/cloudstack-go/v2/cloudstack"
+       "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+func dataSourceCloudstackVgpuProfile() *schema.Resource {
+       return &schema.Resource{
+               Read: datasourceCloudStackVgpuProfileRead,
+               Schema: map[string]*schema.Schema{
+                       "filter": dataSourceFiltersSchema(),
+
+                       //Computed values
+                       "id": {
+                               Type:     schema.TypeString,
+                               Computed: true,
+                       },
+                       "name": {
+                               Description: "the name of the vGPU profile",
+                               Type:        schema.TypeString,
+                               Computed:    true,
+                       },
+                       "description": {
+                               Description: "the description of the vGPU 
profile",
+                               Type:        schema.TypeString,
+                               Computed:    true,
+                       },
+                       "device_id": {
+                               Description: "the device id of the GPU card",
+                               Type:        schema.TypeString,
+                               Computed:    true,
+                       },
+                       "device_name": {
+                               Description: "the device name of the GPU card",
+                               Type:        schema.TypeString,
+                               Computed:    true,
+                       },
+                       "gpu_card_id": {
+                               Description: "the GPU card id of the vGPU 
profile",
+                               Type:        schema.TypeString,
+                               Computed:    true,
+                       },
+                       "gpu_card_name": {
+                               Description: "the GPU card name of the vGPU 
profile",
+                               Type:        schema.TypeString,
+                               Computed:    true,
+                       },
+                       "max_heads": {
+                               Description: "the maximum displays per vGPU 
instance",
+                               Type:        schema.TypeInt,
+                               Computed:    true,
+                       },
+                       "max_resolution_x": {
+                               Description: "the maximum X resolution per 
display",
+                               Type:        schema.TypeInt,
+                               Computed:    true,
+                       },
+                       "max_resolution_y": {
+                               Description: "the maximum Y resolution per 
display",
+                               Type:        schema.TypeInt,
+                               Computed:    true,
+                       },
+                       "max_vgpu_per_physical_gpu": {
+                               Description: "the maximum number of vGPU 
instances per physical GPU",
+                               Type:        schema.TypeInt,
+                               Computed:    true,
+                       },
+                       "vendor_id": {
+                               Description: "the vendor id of the GPU card",
+                               Type:        schema.TypeString,
+                               Computed:    true,
+                       },
+                       "vendor_name": {
+                               Description: "the vendor name of the GPU card",
+                               Type:        schema.TypeString,
+                               Computed:    true,
+                       },
+                       "video_ram": {
+                               Description: "the video RAM size in MB for the 
vGPU profile",
+                               Type:        schema.TypeInt,
+                               Computed:    true,
+                       },
+               },
+       }
+}
+
+func datasourceCloudStackVgpuProfileRead(d *schema.ResourceData, meta 
interface{}) error {
+       cs := meta.(*cloudstack.CloudStackClient)
+       p := cs.GPU.NewListVgpuProfilesParams()
+
+       if err := applyVgpuProfileFilters(p, d.Get("filter").(*schema.Set)); 
err != nil {
+               return err
+       }
+
+       csVgpuProfiles, err := cs.GPU.ListVgpuProfiles(p)
+       if err != nil {
+               return fmt.Errorf("failed to list vGPU profiles: %s", err)
+       }
+
+       switch len(csVgpuProfiles.VgpuProfiles) {
+       case 0:
+               return fmt.Errorf("no vGPU profiles found")
+       case 1:
+               return vgpuProfileDescriptionAttributes(d, 
csVgpuProfiles.VgpuProfiles[0])
+       default:
+               return fmt.Errorf("%d vGPU profiles matched the given filters; 
"+
+                       "refine the filters (e.g. add gpu_card_id) to match 
exactly one profile", len(csVgpuProfiles.VgpuProfiles))
+       }
+}
+
+func vgpuProfileDescriptionAttributes(d *schema.ResourceData, profile 
*cloudstack.VgpuProfile) error {
+       d.SetId(profile.Id)
+
+       fields := map[string]interface{}{
+               "id":                        profile.Id,
+               "name":                      profile.Name,
+               "description":               profile.Description,
+               "device_id":                 profile.Deviceid,
+               "device_name":               profile.Devicename,
+               "gpu_card_id":               profile.Gpucardid,
+               "gpu_card_name":             profile.Gpucardname,
+               "max_heads":                 profile.Maxheads,
+               "max_resolution_x":          profile.Maxresolutionx,
+               "max_resolution_y":          profile.Maxresolutiony,
+               "max_vgpu_per_physical_gpu": profile.Maxvgpuperphysicalgpu,
+               "vendor_id":                 profile.Vendorid,
+               "vendor_name":               profile.Vendorname,
+               "video_ram":                 profile.Videoram,
+       }
+
+       for k, v := range fields {
+               if err := d.Set(k, v); err != nil {
+                       log.Printf("[WARN] Error setting %s: %s", k, err)
+               }
+       }
+
+       return nil
+}
+
+func applyVgpuProfileFilters(p *cloudstack.ListVgpuProfilesParams, filters 
*schema.Set) error {
+       seen := make(map[string]bool)
+       for _, f := range filters.List() {
+               filter := f.(map[string]interface{})
+               name := filter["name"].(string)
+               value := filter["value"].(string)
+
+               if seen[name] {
+                       return fmt.Errorf("duplicate filter %q; each filter 
name may only be specified once", name)
+               }
+               seen[name] = true
+
+               switch name {
+               case "id":
+                       p.SetId(value)
+               case "name":
+                       p.SetName(value)
+               case "gpu_card_id":
+                       p.SetGpucardid(value)
+               case "keyword":
+                       p.SetKeyword(value)
+               case "active_only":
+                       b, err := strconv.ParseBool(value)
+                       if err != nil {
+                               return fmt.Errorf("invalid boolean value %q for 
filter %q: %s", value, name, err)
+                       }
+                       p.SetActiveonly(b)
+               default:
+                       return fmt.Errorf("unsupported filter %q; supported 
filters: id, name, gpu_card_id, keyword, active_only", name)
+               }
+       }
+
+       return nil
+}
diff --git a/cloudstack/data_source_cloudstack_vgpu_profile_test.go 
b/cloudstack/data_source_cloudstack_vgpu_profile_test.go
new file mode 100644
index 0000000..284f9fe
--- /dev/null
+++ b/cloudstack/data_source_cloudstack_vgpu_profile_test.go
@@ -0,0 +1,50 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package cloudstack
+
+import (
+       "testing"
+
+       "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+)
+
+func TestAccVgpuProfileDataSource_basic(t *testing.T) {
+       resource.Test(t, resource.TestCase{
+               PreCheck:  func() { testAccPreCheckGPU(t) },
+               Providers: testAccProviders,
+               Steps: []resource.TestStep{
+                       {
+                               Config: testVgpuProfileDataSourceConfig_basic,
+                               Check: resource.ComposeTestCheckFunc(
+                                       
resource.TestCheckResourceAttr("data.cloudstack_vgpu_profile.test", "name", 
"sim-8q"),
+                               ),
+                       },
+               },
+       })
+}
+
+const testVgpuProfileDataSourceConfig_basic = `
+data "cloudstack_vgpu_profile" "test" {
+  filter {
+    name = "name"
+    value = "sim-8q"
+  }
+}
+`
diff --git a/cloudstack/provider.go b/cloudstack/provider.go
index f4d3a4a..81bd20b 100644
--- a/cloudstack/provider.go
+++ b/cloudstack/provider.go
@@ -109,6 +109,8 @@ func Provider() *schema.Provider {
                        "cloudstack_user_data":                 
dataSourceCloudstackUserData(),
                        "cloudstack_kubernetes_cluster_config": 
dataSourceCloudstackKubernetesClusterConfig(),
                        "cloudstack_security_group":            
dataSourceCloudstackSecurityGroup(),
+                       "cloudstack_vgpu_profile":              
dataSourceCloudstackVgpuProfile(),
+                       "cloudstack_gpu_card":                  
dataSourceCloudstackGpuCard(),
                },
 
                ResourcesMap: map[string]*schema.Resource{
diff --git a/cloudstack/provider_test.go b/cloudstack/provider_test.go
index ca47cbf..190dc3b 100644
--- a/cloudstack/provider_test.go
+++ b/cloudstack/provider_test.go
@@ -206,6 +206,37 @@ func testAccPreCheckStaticRouteNexthop(t *testing.T) {
        requireMinimumCloudStackVersion(t, minVersionNum, "Static route nexthop 
parameter")
 }
 
+// testAccPreCheckGPU checks if the CloudStack version supports GPU features 
(requires 4.22.0+)
+// and that GPU devices have been discovered on the hosts. vGPU profiles only 
exist once GPU
+// devices are discovered (via the discoverGpuDevices API); when none are 
present the GPU tests
+// are skipped so environments without GPU hardware (e.g. the CI simulator) do 
not fail.
+func testAccPreCheckGPU(t *testing.T) {
+       testAccPreCheck(t)
+
+       const minVersionNum = 4022 // 4.22.0
+       requireMinimumCloudStackVersion(t, minVersionNum, "GPU card and vGPU 
profile support")
+
+       requireVgpuProfiles(t)
+}
+
+// requireVgpuProfiles skips the test when no vGPU profiles are available. 
vGPU profiles are only
+// created once GPU devices have been discovered on the hosts (via the 
discoverGpuDevices API),
+// which is not the case in environments without GPU hardware.
+func requireVgpuProfiles(t *testing.T) {
+       t.Helper()
+       cs := newTestClient(t)
+
+       p := cs.GPU.NewListVgpuProfilesParams()
+       profiles, err := cs.GPU.ListVgpuProfiles(p)
+       if err != nil {
+               t.Fatalf("Failed to list vGPU profiles: %v", err)
+       }
+
+       if profiles.Count == 0 {
+               t.Skip("No vGPU profiles found; discover GPU devices on the 
hosts (discoverGpuDevices) to run GPU acceptance tests")
+       }
+}
+
 // newTestClient creates a CloudStack client from environment variables for 
use in test PreCheck functions.
 // This is needed because PreCheck functions run before the test framework 
configures the provider,
 // so testAccProvider.Meta() is nil at that point.
diff --git a/cloudstack/service_offering_constrained_resource.go 
b/cloudstack/service_offering_constrained_resource.go
index 92c8077..4ada5b6 100644
--- a/cloudstack/service_offering_constrained_resource.go
+++ b/cloudstack/service_offering_constrained_resource.go
@@ -93,6 +93,7 @@ func (r *serviceOfferingConstrainedResource) Create(ctx 
context.Context, req res
        var planDiskQosHypervisor ServiceOfferingDiskQosHypervisor
        var planDiskOffering ServiceOfferingDiskOffering
        var planDiskQosStorage ServiceOfferingDiskQosStorage
+       var planGpu ServiceOfferingGpu
 
        resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
        if !plan.ServiceOfferingDiskQosHypervisor.IsNull() {
@@ -104,6 +105,9 @@ func (r *serviceOfferingConstrainedResource) Create(ctx 
context.Context, req res
        if !plan.ServiceOfferingDiskQosStorage.IsNull() {
                
resp.Diagnostics.Append(plan.ServiceOfferingDiskQosStorage.As(ctx, 
&planDiskQosStorage, basetypes.ObjectAsOptions{})...)
        }
+       if !plan.ServiceOfferingGpu.IsNull() {
+               resp.Diagnostics.Append(plan.ServiceOfferingGpu.As(ctx, 
&planGpu, basetypes.ObjectAsOptions{})...)
+       }
        if resp.Diagnostics.HasError() {
                return
        }
@@ -114,6 +118,7 @@ func (r *serviceOfferingConstrainedResource) Create(ctx 
context.Context, req res
        planDiskQosHypervisor.commonCreateParams(ctx, params)
        planDiskOffering.commonCreateParams(ctx, params)
        planDiskQosStorage.commonCreateParams(ctx, params)
+       planGpu.commonCreateParams(ctx, params)
 
        // resource specific params
        if !plan.CpuSpeed.IsNull() {
@@ -151,20 +156,8 @@ func (r *serviceOfferingConstrainedResource) Create(ctx 
context.Context, req res
 
 func (r *serviceOfferingConstrainedResource) Read(ctx context.Context, req 
resource.ReadRequest, resp *resource.ReadResponse) {
        var state serviceOfferingConstrainedResourceModel
-       var stateDiskQosHypervisor ServiceOfferingDiskQosHypervisor
-       var stateDiskOffering ServiceOfferingDiskOffering
-       var stateDiskQosStorage ServiceOfferingDiskQosStorage
 
        resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
-       if !state.ServiceOfferingDiskQosHypervisor.IsNull() {
-               
resp.Diagnostics.Append(state.ServiceOfferingDiskQosHypervisor.As(ctx, 
&stateDiskQosHypervisor, basetypes.ObjectAsOptions{})...)
-       }
-       if !state.ServiceOfferingDiskOffering.IsNull() {
-               
resp.Diagnostics.Append(state.ServiceOfferingDiskOffering.As(ctx, 
&stateDiskOffering, basetypes.ObjectAsOptions{})...)
-       }
-       if !state.ServiceOfferingDiskQosStorage.IsNull() {
-               
resp.Diagnostics.Append(state.ServiceOfferingDiskQosStorage.As(ctx, 
&stateDiskQosStorage, basetypes.ObjectAsOptions{})...)
-       }
        if resp.Diagnostics.HasError() {
                return
        }
@@ -227,10 +220,7 @@ func (r *serviceOfferingConstrainedResource) Read(ctx 
context.Context, req resou
                state.MinMemory = types.Int32Value(int32(i))
        }
 
-       state.commonRead(ctx, cs)
-       stateDiskQosHypervisor.commonRead(ctx, cs)
-       stateDiskOffering.commonRead(ctx, cs)
-       stateDiskQosStorage.commonRead(ctx, cs)
+       resp.Diagnostics.Append(state.commonRead(ctx, cs)...)
        if resp.Diagnostics.HasError() {
                return
        }
diff --git a/cloudstack/service_offering_constrained_resource_test.go 
b/cloudstack/service_offering_constrained_resource_test.go
index 5368db7..6cb90d3 100644
--- a/cloudstack/service_offering_constrained_resource_test.go
+++ b/cloudstack/service_offering_constrained_resource_test.go
@@ -76,6 +76,24 @@ func TestAccServiceOfferingConstrained(t *testing.T) {
        })
 }
 
+func TestAccServiceOfferingConstrained_GPU(t *testing.T) {
+       resource.Test(t, resource.TestCase{
+               PreCheck:                 func() { testAccPreCheckGPU(t) },
+               ProtoV6ProviderFactories: testAccMuxProvider,
+               Steps: []resource.TestStep{
+                       {
+                               Config: 
testAccServiceOfferingCustomConstrained_gpu,
+                               Check: resource.ComposeTestCheckFunc(
+                                       
resource.TestCheckResourceAttr("cloudstack_service_offering_constrained.gpu", 
"name", "gpu"),
+                                       
resource.TestCheckResourceAttrPair("cloudstack_service_offering_constrained.gpu",
 "gpu.vgpu_profile_id", "data.cloudstack_vgpu_profile.test", "id"),
+                                       
resource.TestCheckResourceAttr("cloudstack_service_offering_constrained.gpu", 
"gpu.count", "1"),
+                                       
resource.TestCheckResourceAttr("cloudstack_service_offering_constrained.gpu", 
"gpu.display", "true"),
+                               ),
+                       },
+               },
+       })
+}
+
 const testAccServiceOfferingCustomConstrained1 = `
 resource "cloudstack_zone" "test" {
        name          = "acctest"
@@ -274,6 +292,46 @@ resource "cloudstack_service_offering_constrained" 
"disk_hypervisor" {
 }
 `
 
+const testAccServiceOfferingCustomConstrained_gpu = `
+data "cloudstack_vgpu_profile" "test" {
+       filter {
+               name  = "name"
+               value = "sim-8q"
+       }
+}
+
+resource "cloudstack_service_offering_constrained" "gpu" {
+       display_text = "gpu"
+       name         = "gpu"
+
+       // compute
+       cpu_speed  = 2500
+       max_cpu_number = 10
+       min_cpu_number = 2
+
+       // memory
+       max_memory     = 4096
+       min_memory     = 1024
+
+       // other
+       host_tags = "test0101,test0202"
+       network_rate = 1024
+       deployment_planner = "UserDispersingPlanner"
+
+       // Feature flags
+       dynamic_scaling_enabled = false
+       is_volatile             = false
+       limit_cpu_use           = false
+       offer_ha                = false
+
+       gpu = {
+               vgpu_profile_id = data.cloudstack_vgpu_profile.test.id
+               count           = 1
+               display         = true
+       }
+}
+`
+
 const testAccServiceOfferingCustomConstrained_disk_storage = `
 resource "cloudstack_service_offering_constrained" "disk_storage" {
        display_text = "disk_storage"
diff --git a/cloudstack/service_offering_fixed_resource.go 
b/cloudstack/service_offering_fixed_resource.go
index 6b500fd..3bbfd28 100644
--- a/cloudstack/service_offering_fixed_resource.go
+++ b/cloudstack/service_offering_fixed_resource.go
@@ -78,6 +78,7 @@ func (r *serviceOfferingFixedResource) Create(ctx 
context.Context, req resource.
        var planDiskQosHypervisor ServiceOfferingDiskQosHypervisor
        var planDiskOffering ServiceOfferingDiskOffering
        var planDiskQosStorage ServiceOfferingDiskQosStorage
+       var planGpu ServiceOfferingGpu
 
        resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
        if !plan.ServiceOfferingDiskQosHypervisor.IsNull() {
@@ -89,6 +90,9 @@ func (r *serviceOfferingFixedResource) Create(ctx 
context.Context, req resource.
        if !plan.ServiceOfferingDiskQosStorage.IsNull() {
                
resp.Diagnostics.Append(plan.ServiceOfferingDiskQosStorage.As(ctx, 
&planDiskQosStorage, basetypes.ObjectAsOptions{})...)
        }
+       if !plan.ServiceOfferingGpu.IsNull() {
+               resp.Diagnostics.Append(plan.ServiceOfferingGpu.As(ctx, 
&planGpu, basetypes.ObjectAsOptions{})...)
+       }
        if resp.Diagnostics.HasError() {
                return
        }
@@ -99,6 +103,7 @@ func (r *serviceOfferingFixedResource) Create(ctx 
context.Context, req resource.
        planDiskQosHypervisor.commonCreateParams(ctx, params)
        planDiskOffering.commonCreateParams(ctx, params)
        planDiskQosStorage.commonCreateParams(ctx, params)
+       planGpu.commonCreateParams(ctx, params)
 
        // resource specific params
        if !plan.CpuNumber.IsNull() {
@@ -128,20 +133,8 @@ func (r *serviceOfferingFixedResource) Create(ctx 
context.Context, req resource.
 func (r *serviceOfferingFixedResource) Read(ctx context.Context, req 
resource.ReadRequest, resp *resource.ReadResponse) {
 
        var state serviceOfferingFixedResourceModel
-       var stateDiskQosHypervisor ServiceOfferingDiskQosHypervisor
-       var stateDiskOffering ServiceOfferingDiskOffering
-       var stateDiskQosStorage ServiceOfferingDiskQosStorage
 
        resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
-       if !state.ServiceOfferingDiskQosHypervisor.IsNull() {
-               
resp.Diagnostics.Append(state.ServiceOfferingDiskQosHypervisor.As(ctx, 
&stateDiskQosHypervisor, basetypes.ObjectAsOptions{})...)
-       }
-       if !state.ServiceOfferingDiskOffering.IsNull() {
-               
resp.Diagnostics.Append(state.ServiceOfferingDiskOffering.As(ctx, 
&stateDiskOffering, basetypes.ObjectAsOptions{})...)
-       }
-       if !state.ServiceOfferingDiskQosStorage.IsNull() {
-               
resp.Diagnostics.Append(state.ServiceOfferingDiskQosStorage.As(ctx, 
&stateDiskQosStorage, basetypes.ObjectAsOptions{})...)
-       }
        if resp.Diagnostics.HasError() {
                return
        }
@@ -166,10 +159,7 @@ func (r *serviceOfferingFixedResource) Read(ctx 
context.Context, req resource.Re
                state.Memory = types.Int32Value(int32(cs.Memory))
        }
 
-       state.commonRead(ctx, cs)
-       stateDiskQosHypervisor.commonRead(ctx, cs)
-       stateDiskOffering.commonRead(ctx, cs)
-       stateDiskQosStorage.commonRead(ctx, cs)
+       resp.Diagnostics.Append(state.commonRead(ctx, cs)...)
        if resp.Diagnostics.HasError() {
                return
        }
diff --git a/cloudstack/service_offering_fixed_resource_test.go 
b/cloudstack/service_offering_fixed_resource_test.go
index 438740e..12de429 100644
--- a/cloudstack/service_offering_fixed_resource_test.go
+++ b/cloudstack/service_offering_fixed_resource_test.go
@@ -70,6 +70,24 @@ func TestAccServiceOfferingFixed(t *testing.T) {
        })
 }
 
+func TestAccServiceOfferingFixed_GPU(t *testing.T) {
+       resource.Test(t, resource.TestCase{
+               PreCheck:                 func() { testAccPreCheckGPU(t) },
+               ProtoV6ProviderFactories: testAccMuxProvider,
+               Steps: []resource.TestStep{
+                       {
+                               Config: testAccServiceOfferingFixed_gpu,
+                               Check: resource.ComposeTestCheckFunc(
+                                       
resource.TestCheckResourceAttr("cloudstack_service_offering_fixed.gpu", "name", 
"gpu"),
+                                       
resource.TestCheckResourceAttrPair("cloudstack_service_offering_fixed.gpu", 
"gpu.vgpu_profile_id", "data.cloudstack_vgpu_profile.test", "id"),
+                                       
resource.TestCheckResourceAttr("cloudstack_service_offering_fixed.gpu", 
"gpu.count", "1"),
+                                       
resource.TestCheckResourceAttr("cloudstack_service_offering_fixed.gpu", 
"gpu.display", "true"),
+                               ),
+                       },
+               },
+       })
+}
+
 const testAccServiceOfferingFixed1 = `
 resource "cloudstack_service_offering_fixed" "fixed1" {
        display_text = "fixed1"
@@ -237,3 +255,38 @@ resource "cloudstack_service_offering_fixed" 
"disk_storage" {
        }
 }
 `
+
+const testAccServiceOfferingFixed_gpu = `
+data "cloudstack_vgpu_profile" "test" {
+       filter {
+               name  = "name"
+               value = "sim-8q"
+       }
+}
+
+resource "cloudstack_service_offering_fixed" "gpu" {
+       display_text = "gpu"
+       name         = "gpu"
+
+       // compute
+       cpu_number     = 2
+       cpu_speed      = 2500
+       memory         = 2048
+
+       // other
+       host_tags          = "test0101, test0202"
+       network_rate       = 1024
+       deployment_planner = "UserDispersingPlanner"
+
+       dynamic_scaling_enabled = false
+       is_volatile             = false
+       limit_cpu_use           = false
+       offer_ha                = false
+
+       gpu = {
+               vgpu_profile_id = data.cloudstack_vgpu_profile.test.id
+               count           = 1
+               display         = true
+       }
+}
+`
diff --git a/cloudstack/service_offering_models.go 
b/cloudstack/service_offering_models.go
index a93ffa4..35c6dd3 100644
--- a/cloudstack/service_offering_models.go
+++ b/cloudstack/service_offering_models.go
@@ -58,6 +58,7 @@ type serviceOfferingCommonResourceModel struct {
        ServiceOfferingDiskQosHypervisor types.Object `tfsdk:"disk_hypervisor"`
        ServiceOfferingDiskOffering      types.Object `tfsdk:"disk_offering"`
        ServiceOfferingDiskQosStorage    types.Object `tfsdk:"disk_storage"`
+       ServiceOfferingGpu               types.Object `tfsdk:"gpu"`
 }
 
 type ServiceOfferingDiskQosHypervisor struct {
@@ -84,3 +85,9 @@ type ServiceOfferingDiskQosStorage struct {
        MaxIops                   types.Int64 `tfsdk:"max_iops"`
        MinIops                   types.Int64 `tfsdk:"min_iops"`
 }
+
+type ServiceOfferingGpu struct {
+       VgpuProfileId types.String `tfsdk:"vgpu_profile_id"`
+       Count         types.Int32  `tfsdk:"count"`
+       Display       types.Bool   `tfsdk:"display"`
+}
diff --git a/cloudstack/service_offering_schema.go 
b/cloudstack/service_offering_schema.go
index 9586d51..368bd5e 100644
--- a/cloudstack/service_offering_schema.go
+++ b/cloudstack/service_offering_schema.go
@@ -27,8 +27,10 @@ import (
        "github.com/hashicorp/terraform-plugin-framework/resource/schema"
        
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
        
"github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier"
+       
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int32default"
        
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int32planmodifier"
        
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
+       
"github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier"
        
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
        
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
        "github.com/hashicorp/terraform-plugin-framework/types"
@@ -115,6 +117,9 @@ func serviceOfferingMergeCommonSchema(s1 
map[string]schema.Attribute) map[string
                },
                "disk_offering": schema.SingleNestedAttribute{
                        Optional: true,
+                       PlanModifiers: []planmodifier.Object{
+                               objectplanmodifier.RequiresReplace(),
+                       },
                        Attributes: map[string]schema.Attribute{
                                "cache_mode": schema.StringAttribute{
                                        Description: "the cache mode to use for 
this disk offering. none, writeback or writethrough",
@@ -159,6 +164,9 @@ func serviceOfferingMergeCommonSchema(s1 
map[string]schema.Attribute) map[string
                },
                "disk_hypervisor": schema.SingleNestedAttribute{
                        Optional: true,
+                       PlanModifiers: []planmodifier.Object{
+                               objectplanmodifier.RequiresReplace(),
+                       },
                        Attributes: map[string]schema.Attribute{
                                "bytes_read_rate": schema.Int64Attribute{
                                        Description: "io requests read rate of 
the disk offering",
@@ -206,6 +214,9 @@ func serviceOfferingMergeCommonSchema(s1 
map[string]schema.Attribute) map[string
                },
                "disk_storage": schema.SingleNestedAttribute{
                        Optional: true,
+                       PlanModifiers: []planmodifier.Object{
+                               objectplanmodifier.RequiresReplace(),
+                       },
                        Attributes: map[string]schema.Attribute{
                                "customized_iops": schema.BoolAttribute{
                                        Description: "true if disk offering 
uses custom iops, false otherwise",
@@ -237,6 +248,39 @@ func serviceOfferingMergeCommonSchema(s1 
map[string]schema.Attribute) map[string
                                },
                        },
                },
+               "gpu": schema.SingleNestedAttribute{
+                       Optional: true,
+                       PlanModifiers: []planmodifier.Object{
+                               objectplanmodifier.RequiresReplace(),
+                       },
+                       Attributes: map[string]schema.Attribute{
+                               "vgpu_profile_id": schema.StringAttribute{
+                                       Description: "the ID of the vGPU 
profile to associate with the service offering",
+                                       Required:    true,
+                                       PlanModifiers: []planmodifier.String{
+                                               
stringplanmodifier.RequiresReplace(),
+                                       },
+                               },
+                               "count": schema.Int32Attribute{
+                                       Description: "the number of GPUs to 
assign to the guest VM",
+                                       Optional:    true,
+                                       Computed:    true,
+                                       PlanModifiers: []planmodifier.Int32{
+                                               
int32planmodifier.RequiresReplace(),
+                                       },
+                                       Default: int32default.StaticInt32(1),
+                               },
+                               "display": schema.BoolAttribute{
+                                       Description: "whether the GPU is 
presented as a display device to the guest VM",
+                                       Optional:    true,
+                                       Computed:    true,
+                                       PlanModifiers: []planmodifier.Bool{
+                                               
boolplanmodifier.RequiresReplace(),
+                                       },
+                                       Default: booldefault.StaticBool(false),
+                               },
+                       },
+               },
        }
 
        for key, value := range s1 {
diff --git a/cloudstack/service_offering_unconstrained_resource.go 
b/cloudstack/service_offering_unconstrained_resource.go
index 98b937c..fc8a319 100644
--- a/cloudstack/service_offering_unconstrained_resource.go
+++ b/cloudstack/service_offering_unconstrained_resource.go
@@ -55,6 +55,7 @@ func (r *serviceOfferingUnconstrainedResource) Create(ctx 
context.Context, req r
        var planDiskQosHypervisor ServiceOfferingDiskQosHypervisor
        var planDiskOffering ServiceOfferingDiskOffering
        var planDiskQosStorage ServiceOfferingDiskQosStorage
+       var planGpu ServiceOfferingGpu
 
        resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
        if !plan.ServiceOfferingDiskQosHypervisor.IsNull() {
@@ -66,6 +67,9 @@ func (r *serviceOfferingUnconstrainedResource) Create(ctx 
context.Context, req r
        if !plan.ServiceOfferingDiskQosStorage.IsNull() {
                
resp.Diagnostics.Append(plan.ServiceOfferingDiskQosStorage.As(ctx, 
&planDiskQosStorage, basetypes.ObjectAsOptions{})...)
        }
+       if !plan.ServiceOfferingGpu.IsNull() {
+               resp.Diagnostics.Append(plan.ServiceOfferingGpu.As(ctx, 
&planGpu, basetypes.ObjectAsOptions{})...)
+       }
        if resp.Diagnostics.HasError() {
                return
        }
@@ -76,6 +80,7 @@ func (r *serviceOfferingUnconstrainedResource) Create(ctx 
context.Context, req r
        planDiskQosHypervisor.commonCreateParams(ctx, params)
        planDiskOffering.commonCreateParams(ctx, params)
        planDiskQosStorage.commonCreateParams(ctx, params)
+       planGpu.commonCreateParams(ctx, params)
 
        // create offering
        cs, err := r.client.ServiceOffering.CreateServiceOffering(params)
@@ -94,20 +99,8 @@ func (r *serviceOfferingUnconstrainedResource) Create(ctx 
context.Context, req r
 
 func (r *serviceOfferingUnconstrainedResource) Read(ctx context.Context, req 
resource.ReadRequest, resp *resource.ReadResponse) {
        var state serviceOfferingUnconstrainedResourceModel
-       var stateDiskQosHypervisor ServiceOfferingDiskQosHypervisor
-       var stateDiskOffering ServiceOfferingDiskOffering
-       var stateDiskQosStorage ServiceOfferingDiskQosStorage
 
        resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
-       if !state.ServiceOfferingDiskQosHypervisor.IsNull() {
-               
resp.Diagnostics.Append(state.ServiceOfferingDiskQosHypervisor.As(ctx, 
&stateDiskQosHypervisor, basetypes.ObjectAsOptions{})...)
-       }
-       if !state.ServiceOfferingDiskOffering.IsNull() {
-               
resp.Diagnostics.Append(state.ServiceOfferingDiskOffering.As(ctx, 
&stateDiskOffering, basetypes.ObjectAsOptions{})...)
-       }
-       if !state.ServiceOfferingDiskQosStorage.IsNull() {
-               
resp.Diagnostics.Append(state.ServiceOfferingDiskQosStorage.As(ctx, 
&stateDiskQosStorage, basetypes.ObjectAsOptions{})...)
-       }
        if resp.Diagnostics.HasError() {
                return
        }
@@ -121,10 +114,7 @@ func (r *serviceOfferingUnconstrainedResource) Read(ctx 
context.Context, req res
                return
        }
 
-       state.commonRead(ctx, cs)
-       stateDiskQosHypervisor.commonRead(ctx, cs)
-       stateDiskOffering.commonRead(ctx, cs)
-       stateDiskQosStorage.commonRead(ctx, cs)
+       resp.Diagnostics.Append(state.commonRead(ctx, cs)...)
        if resp.Diagnostics.HasError() {
                return
        }
diff --git a/cloudstack/service_offering_unconstrained_resource_test.go 
b/cloudstack/service_offering_unconstrained_resource_test.go
index 5aba779..d592dc7 100644
--- a/cloudstack/service_offering_unconstrained_resource_test.go
+++ b/cloudstack/service_offering_unconstrained_resource_test.go
@@ -70,6 +70,24 @@ func TestAccServiceOfferingUnconstrained(t *testing.T) {
        })
 }
 
+func TestAccServiceOfferingUnconstrained_GPU(t *testing.T) {
+       resource.Test(t, resource.TestCase{
+               PreCheck:                 func() { testAccPreCheckGPU(t) },
+               ProtoV6ProviderFactories: testAccMuxProvider,
+               Steps: []resource.TestStep{
+                       {
+                               Config: testAccServiceOfferingUnconstrained_gpu,
+                               Check: resource.ComposeTestCheckFunc(
+                                       
resource.TestCheckResourceAttr("cloudstack_service_offering_unconstrained.gpu", 
"name", "gpu"),
+                                       
resource.TestCheckResourceAttrPair("cloudstack_service_offering_unconstrained.gpu",
 "gpu.vgpu_profile_id", "data.cloudstack_vgpu_profile.test", "id"),
+                                       
resource.TestCheckResourceAttr("cloudstack_service_offering_unconstrained.gpu", 
"gpu.count", "1"),
+                                       
resource.TestCheckResourceAttr("cloudstack_service_offering_unconstrained.gpu", 
"gpu.display", "true"),
+                               ),
+                       },
+               },
+       })
+}
+
 const testAccServiceOfferingUnconstrained1 = `
 resource "cloudstack_service_offering_unconstrained" "unconstrained1" {
        display_text = "unconstrained1"
@@ -205,3 +223,32 @@ resource "cloudstack_service_offering_unconstrained" 
"disk_storage" {
        }
 }
 `
+
+const testAccServiceOfferingUnconstrained_gpu = `
+data "cloudstack_vgpu_profile" "test" {
+       filter {
+               name  = "name"
+               value = "sim-8q"
+       }
+}
+
+resource "cloudstack_service_offering_unconstrained" "gpu" {
+       display_text = "gpu"
+       name         = "gpu"
+
+       host_tags = "test0101,test0202"
+       network_rate = 1024
+       deployment_planner = "UserDispersingPlanner"
+
+       dynamic_scaling_enabled = true
+       is_volatile             = true
+       limit_cpu_use           = true
+       offer_ha                = true
+
+       gpu = {
+               vgpu_profile_id = data.cloudstack_vgpu_profile.test.id
+               count           = 1
+               display         = true
+       }
+}
+`
diff --git a/cloudstack/service_offering_util.go 
b/cloudstack/service_offering_util.go
index 666e0fc..6b5de5a 100644
--- a/cloudstack/service_offering_util.go
+++ b/cloudstack/service_offering_util.go
@@ -22,7 +22,9 @@ import (
        "strings"
 
        "github.com/apache/cloudstack-go/v2/cloudstack"
+       "github.com/hashicorp/terraform-plugin-framework/diag"
        "github.com/hashicorp/terraform-plugin-framework/types"
+       "github.com/hashicorp/terraform-plugin-framework/types/basetypes"
 )
 
 // 
------------------------------------------------------------------------------------------------------------------------------
@@ -72,7 +74,9 @@ func (plan *serviceOfferingCommonResourceModel) 
commonUpdateParams(ctx context.C
 // 
------------------------------------------------------------------------------------------------------------------------------
 // common Read methods
 // -
-func (state *serviceOfferingCommonResourceModel) commonRead(ctx 
context.Context, cs *cloudstack.ServiceOffering) {
+func (state *serviceOfferingCommonResourceModel) commonRead(ctx 
context.Context, cs *cloudstack.ServiceOffering) diag.Diagnostics {
+       var diags diag.Diagnostics
+
        state.Id = types.StringValue(cs.Id)
 
        if cs.Deploymentplanner != "" {
@@ -108,6 +112,41 @@ func (state *serviceOfferingCommonResourceModel) 
commonRead(ctx context.Context,
        state.LimitCpuUse = types.BoolValue(cs.Limitcpuuse)
        state.OfferHa = types.BoolValue(cs.Offerha)
 
+       // Refresh the nested blocks and encode them back into state so drift 
is detected
+       if !state.ServiceOfferingDiskQosHypervisor.IsNull() {
+               var v ServiceOfferingDiskQosHypervisor
+               diags.Append(state.ServiceOfferingDiskQosHypervisor.As(ctx, &v, 
basetypes.ObjectAsOptions{})...)
+               v.commonRead(ctx, cs)
+               obj, d := types.ObjectValueFrom(ctx, 
state.ServiceOfferingDiskQosHypervisor.AttributeTypes(ctx), v)
+               diags.Append(d...)
+               state.ServiceOfferingDiskQosHypervisor = obj
+       }
+       if !state.ServiceOfferingDiskOffering.IsNull() {
+               var v ServiceOfferingDiskOffering
+               diags.Append(state.ServiceOfferingDiskOffering.As(ctx, &v, 
basetypes.ObjectAsOptions{})...)
+               v.commonRead(ctx, cs)
+               obj, d := types.ObjectValueFrom(ctx, 
state.ServiceOfferingDiskOffering.AttributeTypes(ctx), v)
+               diags.Append(d...)
+               state.ServiceOfferingDiskOffering = obj
+       }
+       if !state.ServiceOfferingDiskQosStorage.IsNull() {
+               var v ServiceOfferingDiskQosStorage
+               diags.Append(state.ServiceOfferingDiskQosStorage.As(ctx, &v, 
basetypes.ObjectAsOptions{})...)
+               v.commonRead(ctx, cs)
+               obj, d := types.ObjectValueFrom(ctx, 
state.ServiceOfferingDiskQosStorage.AttributeTypes(ctx), v)
+               diags.Append(d...)
+               state.ServiceOfferingDiskQosStorage = obj
+       }
+       if !state.ServiceOfferingGpu.IsNull() {
+               var v ServiceOfferingGpu
+               diags.Append(state.ServiceOfferingGpu.As(ctx, &v, 
basetypes.ObjectAsOptions{})...)
+               v.commonRead(ctx, cs)
+               obj, d := types.ObjectValueFrom(ctx, 
state.ServiceOfferingGpu.AttributeTypes(ctx), v)
+               diags.Append(d...)
+               state.ServiceOfferingGpu = obj
+       }
+
+       return diags
 }
 
 func (state *ServiceOfferingDiskQosHypervisor) commonRead(ctx context.Context, 
cs *cloudstack.ServiceOffering) {
@@ -170,6 +209,16 @@ func (state *ServiceOfferingDiskQosStorage) commonRead(ctx 
context.Context, cs *
 
 }
 
+func (state *ServiceOfferingGpu) commonRead(ctx context.Context, cs 
*cloudstack.ServiceOffering) {
+       if cs.Vgpuprofileid != "" {
+               state.VgpuProfileId = types.StringValue(cs.Vgpuprofileid)
+       } else {
+               state.VgpuProfileId = types.StringNull()
+       }
+       state.Count = types.Int32Value(int32(cs.Gpucount))
+       state.Display = types.BoolValue(cs.Gpudisplay)
+}
+
 // 
------------------------------------------------------------------------------------------------------------------------------
 // common Create methods
 // -
@@ -278,3 +327,17 @@ func (plan *ServiceOfferingDiskQosStorage) 
commonCreateParams(ctx context.Contex
 
        return p
 }
+
+func (plan *ServiceOfferingGpu) commonCreateParams(ctx context.Context, p 
*cloudstack.CreateServiceOfferingParams) 
*cloudstack.CreateServiceOfferingParams {
+       if !plan.VgpuProfileId.IsNull() {
+               p.SetVgpuprofileid(plan.VgpuProfileId.ValueString())
+       }
+       if !plan.Count.IsNull() {
+               p.SetGpucount(int(plan.Count.ValueInt32()))
+       }
+       if !plan.Display.IsNull() {
+               p.SetGpudisplay(plan.Display.ValueBool())
+       }
+
+       return p
+}
diff --git a/website/docs/d/gpu_card.html.markdown 
b/website/docs/d/gpu_card.html.markdown
new file mode 100644
index 0000000..087a5d8
--- /dev/null
+++ b/website/docs/d/gpu_card.html.markdown
@@ -0,0 +1,51 @@
+---
+layout: "cloudstack"
+page_title: "CloudStack: cloudstack_gpu_card"
+description: |-
+  Gets information about a GPU card.
+---
+
+# cloudstack_gpu_card
+
+Use this data source to get information about a GPU card for use in other 
resources.
+
+## Example Usage
+
+```hcl
+data "cloudstack_gpu_card" "card" {
+  filter {
+    name  = "keyword"
+    value = "NVIDIA"
+  }
+}
+
+output "gpu_card_id" {
+  value = data.cloudstack_gpu_card.card.id
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `filter` - (Required) One or more name/value pairs to filter off of. See 
detailed documentation below.
+
+### Filter Arguments
+
+* `name` - (Required) The name of the field to filter on. Filtering is 
performed server-side by the
+  CloudStack API. Supported values are `id`, `device_id`, `device_name`, 
`vendor_id`, `vendor_name`,
+  `keyword`, and `active_only`.
+* `value` - (Required) The value to filter on. This is passed directly to the 
CloudStack API and
+  matched exactly (not as a regular expression). The filters must narrow the 
result to a single GPU
+  card; if more than one card matches, an error is returned.
+
+## Attributes Reference
+
+The following attributes are exported:
+
+* `id` - The ID of the GPU card.
+* `name` - The name of the GPU card.
+* `device_id` - The device id of the GPU card.
+* `device_name` - The device name of the GPU card.
+* `vendor_id` - The vendor id of the GPU card.
+* `vendor_name` - The vendor name of the GPU card.
diff --git a/website/docs/d/vgpu_profile.html.markdown 
b/website/docs/d/vgpu_profile.html.markdown
new file mode 100644
index 0000000..c266892
--- /dev/null
+++ b/website/docs/d/vgpu_profile.html.markdown
@@ -0,0 +1,58 @@
+---
+layout: "cloudstack"
+page_title: "CloudStack: cloudstack_vgpu_profile"
+description: |-
+  Gets information about a vGPU profile.
+---
+
+# cloudstack_vgpu_profile
+
+Use this data source to get information about a vGPU profile for use in other 
resources.
+
+## Example Usage
+
+```hcl
+data "cloudstack_vgpu_profile" "profile" {
+  filter {
+    name = "name"
+    value = "passthrough"
+  }
+}
+
+output "vgpu_profile_id" {
+  value = data.cloudstack_vgpu_profile.profile.id
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `filter` - (Required) One or more name/value pairs to filter off of. See 
detailed documentation below.
+
+### Filter Arguments
+
+* `name` - (Required) The name of the field to filter on. Filtering is 
performed server-side by the
+  CloudStack API. Supported values are `id`, `name`, `gpu_card_id`, `keyword`, 
and `active_only`.
+* `value` - (Required) The value to filter on. This is passed directly to the 
CloudStack API and
+  matched exactly (not as a regular expression). The filters must narrow the 
result to a single
+  vGPU profile; if more than one profile matches, an error is returned.
+
+## Attributes Reference
+
+The following attributes are exported:
+
+* `id` - The ID of the vGPU profile.
+* `name` - The name of the vGPU profile.
+* `description` - The description of the vGPU profile.
+* `device_id` - The device id of the GPU card.
+* `device_name` - The device name of the GPU card.
+* `gpu_card_id` - The GPU card id of the vGPU profile.
+* `gpu_card_name` - The GPU card name of the vGPU profile.
+* `max_heads` - The maximum displays per vGPU instance.
+* `max_resolution_x` - The maximum X resolution per display.
+* `max_resolution_y` - The maximum Y resolution per display.
+* `max_vgpu_per_physical_gpu` - The maximum number of vGPU instances per 
physical GPU.
+* `vendor_id` - The vendor id of the GPU card.
+* `vendor_name` - The vendor name of the GPU card.
+* `video_ram` - The video RAM size in MB for the vGPU profile.
diff --git a/website/docs/r/service_offering_constrained.html.markdown 
b/website/docs/r/service_offering_constrained.html.markdown
index 20a33b5..8ff5bc7 100644
--- a/website/docs/r/service_offering_constrained.html.markdown
+++ b/website/docs/r/service_offering_constrained.html.markdown
@@ -37,6 +37,12 @@ resource "cloudstack_service_offering_constrained" "example" 
{
                provisioning_type         = "thin"
                storage_type              = "local"
        }
+
+       gpu {
+               vgpu_profile_id = "gpu-profile-uuid"
+               count           = 1
+               display         = false
+       }
 }
 ```
 
@@ -89,6 +95,12 @@ The following arguments are supported:
 - `max_iops` (Int, Optional) - Max IOPS of the compute offering.
 - `min_iops` (Int, Optional) - Min IOPS of the compute offering.
 
+#### `gpu` (Block, Optional)
+
+- `vgpu_profile_id` (String, Required) - The ID of the vGPU profile to 
associate with the service offering.
+- `count` (Int, Optional, Computed) - The number of GPUs to assign to the 
guest VM.
+- `display` (Bool, Optional, Computed, Default: false) - Whether the GPU is 
presented as a display device to the guest VM.
+
 ## Attributes Reference
 
 In addition to the arguments above, the following attributes are exported:
diff --git a/website/docs/r/service_offering_fixed.html.markdown 
b/website/docs/r/service_offering_fixed.html.markdown
index 448baa4..1c86805 100644
--- a/website/docs/r/service_offering_fixed.html.markdown
+++ b/website/docs/r/service_offering_fixed.html.markdown
@@ -32,6 +32,11 @@ resource "cloudstack_service_offering_fixed" "fixed1" {
   # disk_offering { ... }
   # disk_hypervisor { ... }
   # disk_storage { ... }
+  # gpu {
+  #   vgpu_profile_id = "..."
+  #   count           = 1
+  #   display         = false
+  # }
 }
 ```
 
@@ -86,6 +91,12 @@ The following arguments are supported:
 - `max_iops` (Optional) - Max IOPS of the compute offering.
 - `min_iops` (Optional) - Min IOPS of the compute offering.
 
+#### `gpu` (Optional)
+
+- `vgpu_profile_id` (Required) - The ID of the vGPU profile to associate with 
the service offering.
+- `count` (Optional, Computed) - The number of GPUs to assign to the guest VM.
+- `display` (Optional, Computed) - Whether the GPU is presented as a display 
device to the guest VM. Defaults to `false`.
+
 ## Attributes Reference
 
 In addition to the arguments above, the following attributes are exported:
diff --git a/website/docs/r/service_offering_unconstrained.html.markdown 
b/website/docs/r/service_offering_unconstrained.html.markdown
index 4a71140..5d4bb02 100644
--- a/website/docs/r/service_offering_unconstrained.html.markdown
+++ b/website/docs/r/service_offering_unconstrained.html.markdown
@@ -29,6 +29,11 @@ resource "cloudstack_service_offering_unconstrained" 
"unconstrained1" {
   # disk_offering { ... }
   # disk_hypervisor { ... }
   # disk_storage { ... }
+  # gpu {
+  #   vgpu_profile_id = "..."
+  #   count           = 1
+  #   display         = false
+  # }
 }
 ```
 
@@ -80,6 +85,12 @@ The following arguments are supported:
 - `max_iops` (Optional) - Max IOPS of the compute offering.
 - `min_iops` (Optional) - Min IOPS of the compute offering.
 
+#### `gpu` (Optional)
+
+- `vgpu_profile_id` (Required) - The ID of the vGPU profile to associate with 
the service offering.
+- `count` (Optional, Computed) - The number of GPUs to assign to the guest VM.
+- `display` (Optional, Computed) - Whether the GPU is presented as a display 
device to the guest VM. Defaults to `false`.
+
 ## Attributes Reference
 
 In addition to the arguments above, the following attributes are exported:

Reply via email to