From eb801eb2139a754ee95e21188b3999a07e7961fd Mon Sep 17 00:00:00 2001 From: faiq Date: Wed, 1 May 2024 20:49:07 -0600 Subject: [PATCH] feat: adds GPU support to packer plugin nutanix --- builder/nutanix/config.go | 7 ++++- builder/nutanix/config.hcl2spec.go | 27 ++++++++++++++++++++ builder/nutanix/driver.go | 41 ++++++++++++++++++++++++++++++ docs/builders/nutanix.mdx | 14 +++++++++- 4 files changed, 87 insertions(+), 2 deletions(-) diff --git a/builder/nutanix/config.go b/builder/nutanix/config.go index 561f2d5..9226e4e 100644 --- a/builder/nutanix/config.go +++ b/builder/nutanix/config.go @@ -1,4 +1,4 @@ -//go:generate packer-sdc mapstructure-to-hcl2 -type Config,Category,ClusterConfig,VmConfig,VmDisk,VmNIC +//go:generate packer-sdc mapstructure-to-hcl2 -type Config,Category,ClusterConfig,VmConfig,VmDisk,VmNIC,GPU package nutanix @@ -45,6 +45,10 @@ type Config struct { ctx interpolate.Context } +type GPU struct { + Name string `mapstructure:"name" json:"name" required:"false"` +} + type Category struct { Key string `mapstructure:"key" json:"key" required:"false"` Value string `mapstructure:"value" json:"value" required:"false"` @@ -86,6 +90,7 @@ type VmConfig struct { UserData string `mapstructure:"user_data" json:"user_data" required:"false"` VMCategories []Category `mapstructure:"vm_categories" required:"false"` Project string `mapstructure:"project" required:"false"` + GPU []GPU `mapstructure:"gpu" required:"false"` } func (c *Config) Prepare(raws ...interface{}) ([]string, error) { diff --git a/builder/nutanix/config.hcl2spec.go b/builder/nutanix/config.hcl2spec.go index 2c48d70..357c95b 100644 --- a/builder/nutanix/config.hcl2spec.go +++ b/builder/nutanix/config.hcl2spec.go @@ -146,6 +146,7 @@ type FlatConfig struct { UserData *string `mapstructure:"user_data" json:"user_data" required:"false" cty:"user_data" hcl:"user_data"` VMCategories []FlatCategory `mapstructure:"vm_categories" required:"false" cty:"vm_categories" hcl:"vm_categories"` Project *string `mapstructure:"project" required:"false" cty:"project" hcl:"project"` + GPU []FlatGPU `mapstructure:"gpu" required:"false" cty:"gpu" hcl:"gpu"` ForceDeregister *bool `mapstructure:"force_deregister" json:"force_deregister" required:"false" cty:"force_deregister" hcl:"force_deregister"` ImageDescription *string `mapstructure:"image_description" json:"image_description" required:"false" cty:"image_description" hcl:"image_description"` ImageCategories []FlatCategory `mapstructure:"image_categories" required:"false" cty:"image_categories" hcl:"image_categories"` @@ -247,6 +248,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false}, "vm_categories": &hcldec.BlockListSpec{TypeName: "vm_categories", Nested: hcldec.ObjectSpec((*FlatCategory)(nil).HCL2Spec())}, "project": &hcldec.AttrSpec{Name: "project", Type: cty.String, Required: false}, + "gpu": &hcldec.BlockListSpec{TypeName: "gpu", Nested: hcldec.ObjectSpec((*FlatGPU)(nil).HCL2Spec())}, "force_deregister": &hcldec.AttrSpec{Name: "force_deregister", Type: cty.Bool, Required: false}, "image_description": &hcldec.AttrSpec{Name: "image_description", Type: cty.String, Required: false}, "image_categories": &hcldec.BlockListSpec{TypeName: "image_categories", Nested: hcldec.ObjectSpec((*FlatCategory)(nil).HCL2Spec())}, @@ -258,6 +260,29 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { return s } +// FlatGPU is an auto-generated flat version of GPU. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatGPU struct { + Name *string `mapstructure:"name" json:"name" required:"false" cty:"name" hcl:"name"` +} + +// FlatMapstructure returns a new FlatGPU. +// FlatGPU is an auto-generated flat version of GPU. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*GPU) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatGPU) +} + +// HCL2Spec returns the hcl spec of a GPU. +// This spec is used by HCL to read the fields of GPU. +// The decoded values from this spec will then be applied to a FlatGPU. +func (*FlatGPU) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "name": &hcldec.AttrSpec{Name: "name", Type: cty.String, Required: false}, + } + return s +} + // FlatVmConfig is an auto-generated flat version of VmConfig. // Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. type FlatVmConfig struct { @@ -274,6 +299,7 @@ type FlatVmConfig struct { UserData *string `mapstructure:"user_data" json:"user_data" required:"false" cty:"user_data" hcl:"user_data"` VMCategories []FlatCategory `mapstructure:"vm_categories" required:"false" cty:"vm_categories" hcl:"vm_categories"` Project *string `mapstructure:"project" required:"false" cty:"project" hcl:"project"` + GPU []FlatGPU `mapstructure:"gpu" required:"false" cty:"gpu" hcl:"gpu"` } // FlatMapstructure returns a new FlatVmConfig. @@ -301,6 +327,7 @@ func (*FlatVmConfig) HCL2Spec() map[string]hcldec.Spec { "user_data": &hcldec.AttrSpec{Name: "user_data", Type: cty.String, Required: false}, "vm_categories": &hcldec.BlockListSpec{TypeName: "vm_categories", Nested: hcldec.ObjectSpec((*FlatCategory)(nil).HCL2Spec())}, "project": &hcldec.AttrSpec{Name: "project", Type: cty.String, Required: false}, + "gpu": &hcldec.BlockListSpec{TypeName: "gpu", Nested: hcldec.ObjectSpec((*FlatGPU)(nil).HCL2Spec())}, } return s } diff --git a/builder/nutanix/driver.go b/builder/nutanix/driver.go index 72f69af..2c57a58 100644 --- a/builder/nutanix/driver.go +++ b/builder/nutanix/driver.go @@ -151,6 +151,37 @@ func findSubnetByName(conn *v3.Client, name string) (*v3.SubnetIntentResponse, e return found[0], nil } +func findGPUByName(conn *v3.Client, name string) (*v3.VMGpu, error) { + hosts, err := conn.V3.ListAllHost() + if err != nil { + return nil, err + } + + for _, host := range hosts.Entities { + if host == nil || + host.Status == nil || + host.Status.ClusterReference == nil || + host.Status.Resources == nil || + len(host.Status.Resources.GPUList) == 0 { + continue + } + + for _, peGpu := range host.Status.Resources.GPUList { + if peGpu == nil { + continue + } + if peGpu.Name == name { + return &v3.VMGpu{ + DeviceID: peGpu.DeviceID, + Vendor: &peGpu.Vendor, + Mode: &peGpu.Mode, + }, nil + } + } + } + return nil, fmt.Errorf("failed to find GPU %s", name) +} + func sourceImageExists(conn *v3.Client, name string, uri string) (*v3.ImageIntentResponse, error) { filter := fmt.Sprintf("name==%s", name) resp, err := conn.V3.ListAllImage(filter) @@ -441,6 +472,15 @@ func (d *NutanixDriver) CreateRequest(vm VmConfig, state multistep.StateBag) (*v } NICList = append(NICList, &newNIC) } + GPUList := make([]*v3.VMGpu, 0, len(vm.GPU)) + for _, gpu := range vm.GPU { + vmGPU, err := findGPUByName(conn, gpu.Name) + if err != nil { + return nil, fmt.Errorf("error while findGPUByName %s", err.Error()) + } + GPUList = append(GPUList, vmGPU) + } + PowerStateOn := "ON" cluster := &v3.ClusterIntentResponse{} @@ -466,6 +506,7 @@ func (d *NutanixDriver) CreateRequest(vm VmConfig, state multistep.StateBag) (*v PowerState: &PowerStateOn, DiskList: DiskList, NicList: NICList, + GpuList: GPUList, }, ClusterReference: BuildReference(*cluster.Metadata.UUID, "cluster"), Description: StringPtr(fmt.Sprintf(vmDescription, d.Config.VmConfig.ImageName)), diff --git a/docs/builders/nutanix.mdx b/docs/builders/nutanix.mdx index 3eb764b..bba6237 100644 --- a/docs/builders/nutanix.mdx +++ b/docs/builders/nutanix.mdx @@ -34,7 +34,7 @@ These parameters allow to define information about platform and temporary VM use - `ip_wait_timeout` (duration string | ex: "0h42m0s") - Amount of time to wait for VM's IP, similar to 'ssh_timeout'. Defaults to 15m (15 minutes). See the Golang [ParseDuration](https://golang.org/pkg/time/#ParseDuration) documentation for full details. - `vm_categories` ([]Category) - Assign Categories to the vm. - `project` (string) - Assign Project to the vm. - + - `gpu` ([] GPU) - GPU in cluster name to be attached on temporary VM. ## Output configuration @@ -153,6 +153,18 @@ Sample Note: Categories must already be present in Prism Central. +## GPU Configuration + +Use `GPU` to assign a GPU that is present on `cluster-name` on the temporary vm. Add the name of the GPU you wish to attach. + +Sample + +```hcl + gpu { + name = "Ampere 40" + } +``` + ## Samples You can find samples [here](https://github.com/nutanix-cloud-native/packer-plugin-nutanix/tree/main/example) for these instructions usage.