Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/actions/setup-cloudstack/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ website/node_modules
*.iml
*.test
*.iml
dist/
.gocache/
.gomodcache/

website/vendor

Expand Down
153 changes: 153 additions & 0 deletions cloudstack/data_source_cloudstack_gpu_card.go
Original file line number Diff line number Diff line change
@@ -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
}
51 changes: 51 additions & 0 deletions cloudstack/data_source_cloudstack_gpu_card_test.go
Original file line number Diff line number Diff line change
@@ -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 = "keyword"
value = "Simulator Graphics Card Pro"
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The keyword seems to be resulting in a error on the management server side and this is resulting in test failures. I will fix it separately there. Can we update the test to use some other field?

}
`
Loading
Loading