diff --git a/charts/region/Chart.yaml b/charts/region/Chart.yaml index 0bae27e..656d5b1 100644 --- a/charts/region/Chart.yaml +++ b/charts/region/Chart.yaml @@ -4,8 +4,8 @@ description: A Helm chart for deploying Unikorn's Region Controller type: application -version: v0.1.25 -appVersion: v0.1.25 +version: v0.1.26 +appVersion: v0.1.26 icon: https://raw.githubusercontent.com/unikorn-cloud/assets/main/images/logos/dark-on-light/icon.png diff --git a/charts/region/crds/region.unikorn-cloud.org_regions.yaml b/charts/region/crds/region.unikorn-cloud.org_regions.yaml index 12300af..233d359 100644 --- a/charts/region/crds/region.unikorn-cloud.org_regions.yaml +++ b/charts/region/crds/region.unikorn-cloud.org_regions.yaml @@ -190,60 +190,93 @@ spec: image: description: Image is configuration for the image service. properties: - propertiesInclude: + selector: description: |- - PropertiesInclude defines the set of properties that must all exist - for an image to be advertised by the provider. - items: - type: string - type: array - signingKey: - description: |- - SigningKey defines a PEM encoded public ECDSA signing key used to verify - the image is trusted. If specified, an image must contain the "digest" - property, the value of which must be a base64 encoded ECDSA signature of - the SHA256 hash of the image ID. - format: byte - type: string + Selector defines a set of rules to lookup images. + If not specified, all images are selected. + properties: + properties: + description: |- + Properties defines the set of properties an image needs to have to + be selected. + items: + type: string + type: array + signingKey: + description: |- + SigningKey defines a PEM encoded public ECDSA signing key used to verify + the image is trusted. If specified, an image must contain the "digest" + property, the value of which must be a base64 encoded ECDSA signature of + the SHA256 hash of the image ID. + format: byte + type: string + type: object type: object network: description: Network is configuration for the network service. properties: - physicalNetwork: - description: |- - PhysicalNetwork is the neutron provider specific network name used - to provision provider networks e.g. VLANs for bare metal clusters. - type: string - vlan: - description: |- - VLAN is the VLAN configuration. If not specified and a VLAN provider - network is requested then the ID will be allocated between 1-6094 - inclusive. + externalNetworks: + description: ExternalNetworks allows external network options + to be specified. properties: - segments: + selector: description: |- - Segements allow blocks of VLAN IDs to be allocated from. In a multi - tenant system, it's possible and perhaps necessary, that this controller - be limited to certain ranges to avoid split brain scenarios when another - user or system is allocating VLAN IDs for itself. - items: - properties: - endId: - description: EndID is the VLAN ID at the end of - the range. - maximum: 4094 - type: integer - startId: - description: StartID is VLAN ID at the start of - the range. - minimum: 1 - type: integer - required: - - endId - - startId - type: object - minItems: 1 - type: array + Selector defines a set of rules to lookup external networks. + In none is specified, all external networks are selected. + properties: + ids: + description: IDs is an explicit list of network IDs. + items: + type: string + type: array + tags: + description: Tags is an implicit selector of networks + with a set of all specified tags. + items: + type: string + type: array + type: object + type: object + providerNetworks: + description: ProviderNetworks allows provider networks to + be configured. + properties: + physicalNetwork: + description: |- + PhysicalNetwork is the neutron provider specific network name used + to provision provider networks e.g. VLANs for bare metal clusters. + type: string + vlan: + description: |- + VLAN is the VLAN configuration. If not specified and a VLAN provider + network is requested then the ID will be allocated between 1-6094 + inclusive. + properties: + segments: + description: |- + Segements allow blocks of VLAN IDs to be allocated from. In a multi + tenant system, it's possible and perhaps necessary, that this controller + be limited to certain ranges to avoid split brain scenarios when another + user or system is allocating VLAN IDs for itself. + items: + properties: + endId: + description: EndID is the VLAN ID at the end + of the range. + maximum: 4094 + type: integer + startId: + description: StartID is VLAN ID at the start + of the range. + minimum: 1 + type: integer + required: + - endId + - startId + type: object + minItems: 1 + type: array + type: object type: object type: object serviceAccountSecret: diff --git a/charts/region/templates/region.yaml b/charts/region/templates/region.yaml index 0dcdf63..465f115 100644 --- a/charts/region/templates/region.yaml +++ b/charts/region/templates/region.yaml @@ -21,59 +21,19 @@ spec: name: {{ $openstack.serviceAccountSecret.name }} {{- with $identity := $openstack.identity }} {{ printf "identity:" | nindent 4 }} - {{- with $roles := $identity.clusterRoles }} - {{ printf "clusterRoles:" | nindent 6 }} - {{- range $role := $roles }} - {{ printf "- %s" $role | nindent 6 }} - {{- end }} - {{- end }} + {{- toYaml $identity | nindent 6 }} {{- end }} {{- with $compute := $openstack.compute -}} - {{- printf "compute:" | nindent 4 }} - {{- with $policy := $compute.regionGroupPolicy -}} - {{ printf "regionGroupPolicy: %s" $policy | nindent 6 }} - {{- end }} - {{- with $flavors := $compute.flavors -}} - {{- printf "flavors:" | nindent 6 }} - {{- printf "selectionPolicy: %s" $flavors.selectionPolicy | nindent 8 }} - {{- with $includes := $flavors.include }} - {{- printf "include:" | nindent 8 }} - {{- range $include := $includes }} - {{- printf "- id: %s" $include.id | nindent 8 }} - {{- with $cpu := $include.cpu -}} - {{- printf "cpu:" | nindent 10 }} - {{- with $family := $cpu.family -}} - {{ printf "family: %s" $family | nindent 12 }} - {{- end }} - {{- end }} - {{- with $gpu := $include.gpu -}} - {{- printf "gpu:" | nindent 10 }} - {{- printf "vendor: %s" $gpu.vendor | nindent 12 }} - {{- printf "model: %s" $gpu.model | nindent 12 }} - {{- printf "memory: %s" $gpu.memory | nindent 12 }} - {{- printf "count: %v" $gpu.count | nindent 12 }} - {{- end }} - {{- end }} - {{- end }} - {{- with $excludes := $flavors.exclude -}} - {{- printf "exclude:" | nindent 8 }} - {{- range $exclude := $excludes }} - {{- printf "- id: %s" $exclude.id | nindent 8 }} - {{- end }} - {{- end }} - {{- end }} + {{ printf "compute:" | nindent 4 }} + {{- toYaml $compute | nindent 6 }} {{- end }} {{- with $image := $openstack.image -}} - {{- printf "image:" | nindent 4 }} - {{- with $properties := $image.propertiesInclude -}} - {{ printf "propertiesInclude:" | nindent 6 }} - {{- range $property := $properties }} - {{ printf "- %s" $property | nindent 6 }} - {{- end }} - {{- end }} - {{- with $signingKey := $image.signingKey -}} - {{ printf "signingKey: %s" $signingKey | nindent 6 }} - {{- end }} + {{ printf "image:" | nindent 4 }} + {{- toYaml $image | nindent 6 }} + {{- end }} + {{- with $network := $openstack.network -}} + {{ printf "network:" | nindent 4 }} + {{- toYaml $network | nindent 6 }} {{- end }} {{- end }} {{- end }} diff --git a/charts/region/values.schema.json b/charts/region/values.schema.json new file mode 100644 index 0000000..03bd173 --- /dev/null +++ b/charts/region/values.schema.json @@ -0,0 +1,414 @@ +{ + "$schema": "http://json-schema.org/schema#", + "type": "object", + "required": [ + "repository", + "organization", + "region", + "ingress", + "identity" + ], + "properties": { + "global": { + "type": "object", + "properties": { + "ca": { + "type": "object", + "properties": { + "secretNamespace": { + "type": "string" + }, + "secretName": { + "type": "string" + } + } + }, + "identity": { + "type": "object", + "properties": { + "host": { + "type": "string" + } + } + }, + "region": { + "type": "object", + "properties": { + "host": { + "type": "string" + } + } + }, + "kubernetes": { + "type": "object", + "properties": { + "host": { + "type": "string" + } + } + }, + "ui": { + "type": "object", + "properties": { + "host": { + "type": "string" + } + } + }, + "otlp": { + "type": "object", + "properties": { + "endpoint": { + "type": "string" + } + } + } + } + }, + "repository": { + "type": "string" + }, + "tag": { + "type": "string" + }, + "dockerConfig": { + "type": "string" + }, + "imagePullSecret": { + "type": "string" + }, + "image": { + "type": "string" + }, + "region": { + "type": "object", + "required": [ + "host" + ], + "properties": { + "host": { + "type": "string" + } + } + }, + "ingress": { + "type": "object", + "required": [ + "clusterIssuer" + ], + "properties": { + "class": { + "type": "string" + }, + "clusterIssuer": { + "type": "string" + }, + "externalDns": { + "type": "boolean" + } + } + }, + "cors": { + "type": "object", + "properties": { + "allowOrigin": { + "type": "array", + "items": { + "type": "string" + } + }, + "maxAge": { + "type": "integer" + } + } + }, + "identity": { + "type": "object", + "required": [ + "host" + ], + "properties": { + "host": { + "type": "string" + } + } + }, + "ca": { + "type": "object", + "reqired": [ + "secretNamespace", + "secretName" + ], + "properties": { + "secretNamespace": { + "type": "string" + }, + "secretName": { + "type": "string" + } + } + }, + "otlp": { + "type": "object", + "required": [ + "endpoint" + ], + "properties": { + "endpoint": { + "type": "string" + } + } + }, + "regions": { + "type": "array", + "items": { + "type": "object", + "required": [ + "name", + "provider" + ], + "properties": { + "name": { + "type": "string" + }, + "provider": { + "type": "string", + "enum": [ + "openstack" + ] + }, + "openstack": { + "type": "object", + "required": [ + "endpoint", + "serviceAccountSecret" + ], + "properties": { + "endpoint": { + "type": "string" + }, + "serviceAccountSecret": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "namespace": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "identity": { + "type": "object", + "properties": { + "clusterRoles": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "compute": { + "type": "object", + "properties": { + "flavors": { + "type": "object", + "required": [ + "selectionPolicy" + ], + "properties": { + "exclude": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string" + } + } + } + }, + "include": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "baremetal": { + "type": "boolean" + }, + "cpu": { + "properties": { + "family": { + "type": "string" + } + }, + "type": "object" + }, + "gpu": { + "type": "object", + "required": [ + "count", + "memory", + "model", + "vendor" + ], + "properties": { + "count": { + "type": "integer" + }, + "memory": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$" + }, + "model": { + "type": "string" + }, + "vendor": { + "type": "string", + "enum": [ + "NVIDIA", + "AMD" + ] + } + } + }, + "id": { + "type": "string" + } + } + } + }, + "selectionPolicy": { + "type": "string", + "enum": [ + "All", + "None" + ] + } + } + }, + "serverGroupPolicy": { + "type": "string" + } + } + }, + "image": { + "type": "object", + "properties": { + "selector": { + "type": "object", + "properties": { + "properties": { + "type": "array", + "items": { + "type": "string" + } + }, + "signingKey": { + "type": "string" + } + } + } + } + }, + "network": { + "type": "object", + "properties": { + "externalNetworks": { + "type": "object", + "properties": { + "selector": { + "type": "object", + "properties": { + "ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + "providerNetworks": { + "type": "object", + "properties": { + "physicalNetwork": { + "type": "string" + }, + "vlan": { + "type": "object", + "properties": { + "segments": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "endId", + "startId" + ], + "properties": { + "endId": { + "type": "integer", + "maximum": 4094 + }, + "startId": { + "type": "integer", + "minimum": 1 + } + } + } + } + } + } + } + } + } + } + } + } + }, + "oneOf": [ + { + "type": "object", + "required": [ + "openstack" + ], + "properties": { + "provider": { + "type": "string", + "enum": [ + "openstack" + ] + } + } + } + ] + } + } + } +} diff --git a/charts/region/values.yaml b/charts/region/values.yaml index cdb2d63..79c048a 100644 --- a/charts/region/values.yaml +++ b/charts/region/values.yaml @@ -12,12 +12,12 @@ organization: unikorn-cloud # Set the docker configuration, doing so will create a secret and link it # to the service accounts of all the controllers. You can do something like: # --set dockerConfig=$(cat ~/.docker/config.json | base64 -w0) -dockerConfig: +# dockerConfig: # Set the image pull secret on the service accounts of all the controllers. # This is an alternative to dockerConfigs, but unlikely to play ball with # ArgoCD as it's a foreign object that needs pruning. -imagePullSecret: +# imagePullSecret: # Region discovery information. # regions: @@ -75,18 +75,41 @@ imagePullSecret: # expression: ^(\d+)$ # # Image service configuration. # image: -# # Images must contain all the following properties to be exposed. -# propertiesInclude: -# - k8s -# # If specified the image signing key defines a base64 PEM encoded ECDSA -# # public key used to trust images. Images must have the "digest" property -# # defined, and its value must be the ECDSA signature of the SHA256 hash of -# # the image ID. -# signingKey: ~ +# # Image selection, the result is a boolean intersection of chosen options. +# selector: +# # Images must contain all the following properties to be exposed. +# properties: +# - k8s +# # If specified the image signing key defines a base64 PEM encoded ECDSA +# # public key used to trust images. Images must have the "digest" property +# # defined, and its value must be the ECDSA signature of the SHA256 hash of +# # the image ID. +# signingKey: ~ +# # Network service configuration. +# network: +# # External network selection, the result is a boolean intersection of +# # chosen options. +# externalNetworks: +# # Explicit list of network IDs. +# ids: +# - 49d51e7d-1c57-4480-9328-b466f9a12818 +# # Implicit tags a network must contain. +# tags: +# - unikorn:external-network +# # Provider network configuration. +# providerNetwoks: +# # Physical network to allocate network segements on. +# physicalNetwork: physnet1 +# # VLAN configuration. +# vlan: +# # A set of valid VLAN ID ranges for allocation +# segments: +# - startID: 1 +# endID: 4094 # REST server specific configuration. # Allows override of the global default image. -image: +# image: # Sets the DNS hosts/X.509 Certs. region: diff --git a/pkg/apis/unikorn/v1alpha1/types.go b/pkg/apis/unikorn/v1alpha1/types.go index 2d6d67a..bb26717 100644 --- a/pkg/apis/unikorn/v1alpha1/types.go +++ b/pkg/apis/unikorn/v1alpha1/types.go @@ -179,9 +179,15 @@ type GPUSpec struct { } type RegionOpenstackImageSpec struct { - // PropertiesInclude defines the set of properties that must all exist - // for an image to be advertised by the provider. - PropertiesInclude []string `json:"propertiesInclude,omitempty"` + // Selector defines a set of rules to lookup images. + // If not specified, all images are selected. + Selector *ImageSelector `json:"selector,omitempty"` +} + +type ImageSelector struct { + // Properties defines the set of properties an image needs to have to + // be selected. + Properties []string `json:"properties,omitempty"` // SigningKey defines a PEM encoded public ECDSA signing key used to verify // the image is trusted. If specified, an image must contain the "digest" // property, the value of which must be a base64 encoded ECDSA signature of @@ -190,6 +196,26 @@ type RegionOpenstackImageSpec struct { } type RegionOpenstackNetworkSpec struct { + // ExternalNetworks allows external network options to be specified. + ExternalNetworks *ExternalNetworks `json:"externalNetworks,omitempty"` + // ProviderNetworks allows provider networks to be configured. + ProviderNetworks *ProviderNetworks `json:"providerNetworks,omitempty"` +} + +type ExternalNetworks struct { + // Selector defines a set of rules to lookup external networks. + // In none is specified, all external networks are selected. + Selector *NetworkSelector `json:"selector,omitempty"` +} + +type NetworkSelector struct { + // IDs is an explicit list of network IDs. + IDs []string `json:"ids,omitempty"` + // Tags is an implicit selector of networks with a set of all specified tags. + Tags []string `json:"tags,omitempty"` +} + +type ProviderNetworks struct { // PhysicalNetwork is the neutron provider specific network name used // to provision provider networks e.g. VLANs for bare metal clusters. PhysicalNetwork *string `json:"physicalNetwork,omitempty"` diff --git a/pkg/apis/unikorn/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/unikorn/v1alpha1/zz_generated.deepcopy.go index e9d0fce..1b2e301 100644 --- a/pkg/apis/unikorn/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/unikorn/v1alpha1/zz_generated.deepcopy.go @@ -47,6 +47,27 @@ func (in *CPUSpec) DeepCopy() *CPUSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExternalNetworks) DeepCopyInto(out *ExternalNetworks) { + *out = *in + if in.Selector != nil { + in, out := &in.Selector, &out.Selector + *out = new(NetworkSelector) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalNetworks. +func (in *ExternalNetworks) DeepCopy() *ExternalNetworks { + if in == nil { + return nil + } + out := new(ExternalNetworks) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GPUSpec) DeepCopyInto(out *GPUSpec) { *out = *in @@ -187,6 +208,32 @@ func (in *IdentityStatus) DeepCopy() *IdentityStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ImageSelector) DeepCopyInto(out *ImageSelector) { + *out = *in + if in.Properties != nil { + in, out := &in.Properties, &out.Properties + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.SigningKey != nil { + in, out := &in.SigningKey, &out.SigningKey + *out = make([]byte, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageSelector. +func (in *ImageSelector) DeepCopy() *ImageSelector { + if in == nil { + return nil + } + out := new(ImageSelector) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NamespacedObject) DeepCopyInto(out *NamespacedObject) { *out = *in @@ -203,6 +250,32 @@ func (in *NamespacedObject) DeepCopy() *NamespacedObject { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkSelector) DeepCopyInto(out *NetworkSelector) { + *out = *in + if in.IDs != nil { + in, out := &in.IDs, &out.IDs + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Tags != nil { + in, out := &in.Tags, &out.Tags + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkSelector. +func (in *NetworkSelector) DeepCopy() *NetworkSelector { + if in == nil { + return nil + } + out := new(NetworkSelector) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OpenstackFlavorExclude) DeepCopyInto(out *OpenstackFlavorExclude) { *out = *in @@ -392,6 +465,32 @@ func (in *PhysicalNetworkStatus) DeepCopy() *PhysicalNetworkStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProviderNetworks) DeepCopyInto(out *ProviderNetworks) { + *out = *in + if in.PhysicalNetwork != nil { + in, out := &in.PhysicalNetwork, &out.PhysicalNetwork + *out = new(string) + **out = **in + } + if in.VLAN != nil { + in, out := &in.VLAN, &out.VLAN + *out = new(VLANSpec) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderNetworks. +func (in *ProviderNetworks) DeepCopy() *ProviderNetworks { + if in == nil { + return nil + } + out := new(ProviderNetworks) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Region) DeepCopyInto(out *Region) { *out = *in @@ -503,15 +602,10 @@ func (in *RegionOpenstackIdentitySpec) DeepCopy() *RegionOpenstackIdentitySpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RegionOpenstackImageSpec) DeepCopyInto(out *RegionOpenstackImageSpec) { *out = *in - if in.PropertiesInclude != nil { - in, out := &in.PropertiesInclude, &out.PropertiesInclude - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.SigningKey != nil { - in, out := &in.SigningKey, &out.SigningKey - *out = make([]byte, len(*in)) - copy(*out, *in) + if in.Selector != nil { + in, out := &in.Selector, &out.Selector + *out = new(ImageSelector) + (*in).DeepCopyInto(*out) } return } @@ -529,14 +623,14 @@ func (in *RegionOpenstackImageSpec) DeepCopy() *RegionOpenstackImageSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RegionOpenstackNetworkSpec) DeepCopyInto(out *RegionOpenstackNetworkSpec) { *out = *in - if in.PhysicalNetwork != nil { - in, out := &in.PhysicalNetwork, &out.PhysicalNetwork - *out = new(string) - **out = **in + if in.ExternalNetworks != nil { + in, out := &in.ExternalNetworks, &out.ExternalNetworks + *out = new(ExternalNetworks) + (*in).DeepCopyInto(*out) } - if in.VLAN != nil { - in, out := &in.VLAN, &out.VLAN - *out = new(VLANSpec) + if in.ProviderNetworks != nil { + in, out := &in.ProviderNetworks, &out.ProviderNetworks + *out = new(ProviderNetworks) (*in).DeepCopyInto(*out) } return diff --git a/pkg/handler/handler.go b/pkg/handler/handler.go index eeaa36f..bc117de 100644 --- a/pkg/handler/handler.go +++ b/pkg/handler/handler.go @@ -177,6 +177,19 @@ func (h *Handler) GetApiV1OrganizationsOrganizationIDRegionsRegionIDFlavors(w ht util.WriteJSONResponse(w, r, http.StatusOK, out) } +func convertImageVirtualization(in providers.ImageVirtualization) openapi.ImageVirtualization { + switch in { + case providers.Virtualized: + return openapi.Virtualized + case providers.Baremetal: + return openapi.Baremetal + case providers.Any: + return openapi.Any + } + + return "" +} + func convertImage(in providers.Image) openapi.Image { out := openapi.Image{ Metadata: coreapi.StaticResourceMetadata{ @@ -185,6 +198,7 @@ func convertImage(in providers.Image) openapi.Image { CreationTime: in.Created, }, Spec: openapi.ImageSpec{ + Virtualization: convertImageVirtualization(in.Virtualization), SoftwareVersions: &openapi.SoftwareVersions{}, }, } @@ -193,6 +207,19 @@ func convertImage(in providers.Image) openapi.Image { out.Spec.SoftwareVersions.Kubernetes = coreutil.ToPointer(in.KubernetesVersion) } + if in.GPU != nil { + gpu := &openapi.ImageGpu{ + Vendor: convertGpuVendor(in.GPU.Vendor), + Driver: in.GPU.Driver, + } + + if len(in.GPU.Models) > 0 { + gpu.Models = &in.GPU.Models + } + + out.Spec.Gpu = gpu + } + return out } diff --git a/pkg/openapi/schema.go b/pkg/openapi/schema.go index f8bfbed..6b92250 100644 --- a/pkg/openapi/schema.go +++ b/pkg/openapi/schema.go @@ -19,91 +19,93 @@ import ( // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/pkg/openapi/server.spec.yaml b/pkg/openapi/server.spec.yaml index a21dd6d..46c238f 100644 --- a/pkg/openapi/server.spec.yaml +++ b/pkg/openapi/server.spec.yaml @@ -254,6 +254,13 @@ components: type: array items: $ref: '#/components/schemas/regionRead' + imageVirtualization: + description: What type of machine the image is for. + type: string + enum: + - virtualized + - baremetal + - any softwareVersions: description: Image preinstalled version version metadata. type: object @@ -266,26 +273,40 @@ components: enum: - NVIDIA - AMD - gpuDriver: + gpuModel: + description: A GPU model number. + type: string + gpuModelList: + description: A list of GPU model numbers. + type: array + items: + $ref: '#/components/schemas/gpuModel' + imageGpu: description: The GPU driver if installed. type: object required: - vendor - - version + - driver properties: vendor: $ref: '#/components/schemas/gpuVendor' - version: + driver: description: The GPU driver version, this is vendor specific. type: string + models: + $ref: '#/components/schemas/gpuModelList' imageSpec: description: An image. type: object + required: + - virtualization properties: + virtualization: + $ref: '#/components/schemas/imageVirtualization' softwareVersions: $ref: '#/components/schemas/softwareVersions' - gpuDriver: - $ref: '#/components/schemas/gpuDriver' + gpu: + $ref: '#/components/schemas/imageGpu' image: description: An image. type: object @@ -569,11 +590,16 @@ components: name: ubu2204-v1.25.6-gpu-525.85.05-7ced4154 creationTime: 2023-02-22T12:04:13Z spec: + virtualization: virtualized softwareVersions: kubernetes: v1.25.6 - gpuDriver: + gpu: vendor: NVIDIA - version: 525.85.05 + driver: 525.85.05 + models: + - A100 + - H100 + - H200 flavorsResponse: description: A list of flavors. content: diff --git a/pkg/openapi/types.go b/pkg/openapi/types.go index 4519ed8..01497a9 100644 --- a/pkg/openapi/types.go +++ b/pkg/openapi/types.go @@ -17,6 +17,13 @@ const ( NVIDIA GpuVendor = "NVIDIA" ) +// Defines values for ImageVirtualization. +const ( + Any ImageVirtualization = "any" + Baremetal ImageVirtualization = "baremetal" + Virtualized ImageVirtualization = "virtualized" +) + // Defines values for RegionType. const ( Openstack RegionType = "openstack" @@ -66,14 +73,11 @@ type FlavorSpec struct { // Flavors A list of flavors. type Flavors = []Flavor -// GpuDriver The GPU driver if installed. -type GpuDriver struct { - // Vendor The GPU vendor. - Vendor GpuVendor `json:"vendor"` +// GpuModel A GPU model number. +type GpuModel = string - // Version The GPU driver version, this is vendor specific. - Version string `json:"version"` -} +// GpuModelList A list of GPU model numbers. +type GpuModelList = []GpuModel // GpuSpec GPU specification. type GpuSpec struct { @@ -167,15 +171,33 @@ type Image struct { Spec ImageSpec `json:"spec"` } +// ImageGpu The GPU driver if installed. +type ImageGpu struct { + // Driver The GPU driver version, this is vendor specific. + Driver string `json:"driver"` + + // Models A list of GPU model numbers. + Models *GpuModelList `json:"models,omitempty"` + + // Vendor The GPU vendor. + Vendor GpuVendor `json:"vendor"` +} + // ImageSpec An image. type ImageSpec struct { - // GpuDriver The GPU driver if installed. - GpuDriver *GpuDriver `json:"gpuDriver,omitempty"` + // Gpu The GPU driver if installed. + Gpu *ImageGpu `json:"gpu,omitempty"` // SoftwareVersions Image preinstalled version version metadata. SoftwareVersions *SoftwareVersions `json:"softwareVersions,omitempty"` + + // Virtualization What type of machine the image is for. + Virtualization ImageVirtualization `json:"virtualization"` } +// ImageVirtualization What type of machine the image is for. +type ImageVirtualization string + // Images A list of images that are compatible with this platform. type Images = []Image diff --git a/pkg/providers/openstack/compute.go b/pkg/providers/openstack/compute.go index 26f13e4..0ee6771 100644 --- a/pkg/providers/openstack/compute.go +++ b/pkg/providers/openstack/compute.go @@ -107,6 +107,22 @@ func (c *ComputeClient) Flavors(ctx context.Context) ([]flavors.Flavor, error) { return nil, err } + // ************************************************************************* + // HACK HACK HACK + // ************************************************************************* + for i := range result { + f := &result[i] + + if f.ID == "c9b3b8c6-7268-4ed3-98d3-76743e3436cf" { + f.VCPUs = 128 + f.RAM = 2 * 1024 * 1024 + } + + } + // ************************************************************************* + // HACK HACK HACK + // ************************************************************************* + result = slices.DeleteFunc(result, func(flavor flavors.Flavor) bool { // We are admin, so see all the things, throw out private flavors. // TODO: we _could_ allow if our project is in the allowed IDs. diff --git a/pkg/providers/openstack/image.go b/pkg/providers/openstack/image.go index f957dfe..0402365 100644 --- a/pkg/providers/openstack/image.go +++ b/pkg/providers/openstack/image.go @@ -81,11 +81,11 @@ func NewImageClient(ctx context.Context, provider CredentialProvider, options *u } func (c *ImageClient) validateProperties(image *images.Image) bool { - if c.options == nil { + if c.options == nil || c.options.Selector == nil { return true } - for _, r := range c.options.PropertiesInclude { + for _, r := range c.options.Selector.Properties { if !slices.Contains(util.Keys(image.Properties), r) { return false } @@ -95,7 +95,7 @@ func (c *ImageClient) validateProperties(image *images.Image) bool { } func (c *ImageClient) decodeSigningKey() (*ecdsa.PublicKey, error) { - pemBlock, _ := pem.Decode(c.options.SigningKey) + pemBlock, _ := pem.Decode(c.options.Selector.SigningKey) if pemBlock == nil { return nil, ErrPEMDecode } @@ -119,7 +119,7 @@ func (c *ImageClient) decodeSigningKey() (*ecdsa.PublicKey, error) { // verifyImage asserts the image is trustworthy for use with our goodselves. func (c *ImageClient) verifyImage(image *images.Image) bool { - if c.options == nil || c.options.SigningKey == nil { + if c.options == nil || c.options.Selector == nil || c.options.Selector.SigningKey == nil { return true } @@ -129,7 +129,7 @@ func (c *ImageClient) verifyImage(image *images.Image) bool { // These will be digitally signed by Baski when created, so we only trust // those images. - signatureRaw, ok := image.Properties["digest"] + signatureRaw, ok := image.Properties["unikorn:digest"] if !ok { return false } @@ -154,24 +154,24 @@ func (c *ImageClient) verifyImage(image *images.Image) bool { return ecdsa.VerifyASN1(signingKey, hash[:], signature) } -func (c *ImageClient) imageValid(image *images.Image) bool { +func (c *ImageClient) filterImage(image *images.Image) bool { if image.Status != "active" { - return false + return true } if !c.validateProperties(image) { - return false + return true } if !c.verifyImage(image) { - return false + return true } - return true + return false } -// Images returns a list of images. -func (c *ImageClient) Images(ctx context.Context) ([]images.Image, error) { +// images does a memoized lookup of images. +func (c *ImageClient) images(ctx context.Context) ([]images.Image, error) { if result, ok := c.imageCache.Get(); ok { return result, nil } @@ -195,9 +195,21 @@ func (c *ImageClient) Images(ctx context.Context) ([]images.Image, error) { return nil, err } + c.imageCache.Set(result) + + return result, nil +} + +// Images returns a list of images. +func (c *ImageClient) Images(ctx context.Context) ([]images.Image, error) { + result, err := c.images(ctx) + if err != nil { + return nil, err + } + // Filter out images that aren't compatible. result = slices.DeleteFunc(result, func(image images.Image) bool { - return !c.imageValid(&image) + return c.filterImage(&image) }) // Sort by age, the newest should have the fewest CVEs! @@ -205,7 +217,5 @@ func (c *ImageClient) Images(ctx context.Context) ([]images.Image, error) { return a.CreatedAt.Compare(b.CreatedAt) }) - c.imageCache.Set(result) - return result, nil } diff --git a/pkg/providers/openstack/network.go b/pkg/providers/openstack/network.go index 0f1b7f2..7e0b1f6 100644 --- a/pkg/providers/openstack/network.go +++ b/pkg/providers/openstack/network.go @@ -20,6 +20,7 @@ package openstack import ( "context" "errors" + "slices" "time" "github.com/gophercloud/gophercloud/v2" @@ -48,9 +49,9 @@ var ( type NetworkClient struct { client *gophercloud.ServiceClient - externalNetworkCache *cache.TimeoutCache[[]networks.Network] - options *unikornv1.RegionOpenstackNetworkSpec + + externalNetworkCache *cache.TimeoutCache[[]networks.Network] } // NewNetworkClient provides a simple one-liner to start networking. @@ -67,6 +68,7 @@ func NewNetworkClient(ctx context.Context, provider CredentialProvider, options c := &NetworkClient{ client: client, + options: options, externalNetworkCache: cache.New[[]networks.Network](time.Hour), } @@ -79,8 +81,8 @@ func NewTestNetworkClient(options *unikornv1.RegionOpenstackNetworkSpec) *Networ } } -// ExternalNetworks returns a list of external networks. -func (c *NetworkClient) ExternalNetworks(ctx context.Context) ([]networks.Network, error) { +// externalNetworks does a memoized lookup of external networks. +func (c *NetworkClient) externalNetworks(ctx context.Context) ([]networks.Network, error) { if result, ok := c.externalNetworkCache.Get(); ok { return result, nil } @@ -97,15 +99,52 @@ func (c *NetworkClient) ExternalNetworks(ctx context.Context) ([]networks.Networ return nil, err } - var results []networks.Network + var result []networks.Network + + if err := networks.ExtractNetworksInto(page, &result); err != nil { + return nil, err + } + + c.externalNetworkCache.Set(result) + + return result, nil +} + +// filterExternalNetwork returns true if the image should be filtered. +func (c *NetworkClient) filterExternalNetwork(network *networks.Network) bool { + if c.options == nil || c.options.ExternalNetworks == nil || c.options.ExternalNetworks.Selector == nil { + return false + } + + if c.options.ExternalNetworks.Selector.IDs != nil { + if !slices.Contains(c.options.ExternalNetworks.Selector.IDs, network.ID) { + return true + } + } - if err := networks.ExtractNetworksInto(page, &results); err != nil { + if c.options.ExternalNetworks.Selector.Tags != nil { + for _, tag := range c.options.ExternalNetworks.Selector.Tags { + if !slices.Contains(network.Tags, tag) { + return true + } + } + } + + return false +} + +// ExternalNetworks returns a list of external networks. +func (c *NetworkClient) ExternalNetworks(ctx context.Context) ([]networks.Network, error) { + result, err := c.externalNetworks(ctx) + if err != nil { return nil, err } - c.externalNetworkCache.Set(results) + result = slices.DeleteFunc(result, func(network networks.Network) bool { + return c.filterExternalNetwork(&network) + }) - return results, nil + return result, nil } // AllocateVLAN does exactly that using configured ID ranges and existing networks. @@ -114,12 +153,12 @@ func (c *NetworkClient) AllocateVLAN(ctx context.Context) (int, error) { // If no configuration is given, own all of the IDs. If there are a list // of segments, only allow those. - if c.options == nil || c.options.VLAN == nil || c.options.VLAN.Segments == nil { + if c.options == nil || c.options.ProviderNetworks == nil || c.options.ProviderNetworks.VLAN == nil || c.options.ProviderNetworks.VLAN.Segments == nil { for i := 1; i < 4096; i++ { allocatable[i] = true } } else { - for _, segment := range c.options.VLAN.Segments { + for _, segment := range c.options.ProviderNetworks.VLAN.Segments { for i := segment.StartID; i < segment.EndID+1; i++ { allocatable[i] = true } @@ -138,7 +177,7 @@ func (c *NetworkClient) AllocateVLAN(ctx context.Context) (int, error) { // CreateVLANProviderNetwork creates a VLAN provider network for a project. func (c *NetworkClient) CreateVLANProviderNetwork(ctx context.Context, name string, projectID string) (int, *networks.Network, error) { - if c.options == nil || c.options.PhysicalNetwork == nil { + if c.options == nil || c.options.ProviderNetworks == nil || c.options.ProviderNetworks.PhysicalNetwork == nil { return -1, nil, ErrConfiguration } @@ -161,7 +200,7 @@ func (c *NetworkClient) CreateVLANProviderNetwork(ctx context.Context, name stri Segments: []provider.Segment{ { NetworkType: "vlan", - PhysicalNetwork: *c.options.PhysicalNetwork, + PhysicalNetwork: *c.options.ProviderNetworks.PhysicalNetwork, SegmentationID: vlanID, }, }, diff --git a/pkg/providers/openstack/network_test.go b/pkg/providers/openstack/network_test.go index 46deee6..fa871a4 100644 --- a/pkg/providers/openstack/network_test.go +++ b/pkg/providers/openstack/network_test.go @@ -40,11 +40,13 @@ func TestVLANAllocateRanges(t *testing.T) { { name: "SingleSegment", options: &unikornv1.RegionOpenstackNetworkSpec{ - VLAN: &unikornv1.VLANSpec{ - Segments: []unikornv1.VLANSegment{ - { - StartID: 100, - EndID: 200, + ProviderNetworks: &unikornv1.ProviderNetworks{ + VLAN: &unikornv1.VLANSpec{ + Segments: []unikornv1.VLANSegment{ + { + StartID: 100, + EndID: 200, + }, }, }, }, diff --git a/pkg/providers/openstack/provider.go b/pkg/providers/openstack/provider.go index 0d9711c..88ec509 100644 --- a/pkg/providers/openstack/provider.go +++ b/pkg/providers/openstack/provider.go @@ -288,14 +288,6 @@ func (p *Provider) Flavors(ctx context.Context) (providers.FlavorList, error) { return result, nil } -func semver(in string) string { - if !strings.HasPrefix(in, "v") { - return "v" + in - } - - return in -} - // Images lists all available images. func (p *Provider) Images(ctx context.Context) (providers.ImageList, error) { imageService, err := p.image(ctx) @@ -308,20 +300,44 @@ func (p *Provider) Images(ctx context.Context) (providers.ImageList, error) { return nil, err } - result := make(providers.ImageList, 0, len(resources)) + result := make(providers.ImageList, len(resources)) for i := range resources { image := &resources[i] - kubernetesVersion, _ := image.Properties["k8s"].(string) + virtualization, _ := image.Properties["unikorn:virtualization"].(string) + kubernetesVersion, _ := image.Properties["unikorn:kubernetes_version"].(string) - result = append(result, providers.Image{ + providerImage := providers.Image{ ID: image.ID, Name: image.Name, Created: image.CreatedAt, Modified: image.UpdatedAt, - KubernetesVersion: semver(kubernetesVersion), - }) + Virtualization: providers.ImageVirtualization(virtualization), + KubernetesVersion: kubernetesVersion, + } + + if gpuVendor, ok := image.Properties["unikorn:gpu_vendor"].(string); ok { + gpuDriver, ok := image.Properties["unikorn:gpu_driver_version"].(string) + if !ok { + // TODO: it's perhaps better to just skip this one, rather than + // kill the entire service?? + return nil, fmt.Errorf("%w: GPU driver is not defined for image %s", ErrKeyUndefined, image.ID) + } + + gpu := &providers.ImageGPU{ + Vendor: providers.GPUVendor(gpuVendor), + Driver: gpuDriver, + } + + if models, ok := image.Properties["unikorn:gpu_models"].(string); ok { + gpu.Models = strings.Split(models, ",") + } + + providerImage.GPU = gpu + } + + result[i] = providerImage } return result, nil diff --git a/pkg/providers/types.go b/pkg/providers/types.go index e344a34..a46663b 100644 --- a/pkg/providers/types.go +++ b/pkg/providers/types.go @@ -66,6 +66,14 @@ type GPU struct { // FlavorList allows us to attach sort functions and the like. type FlavorList []Flavor +type ImageVirtualization string + +const ( + Virtualized ImageVirtualization = "virtualized" + Baremetal ImageVirtualization = "baremetal" + Any ImageVirtualization = "any" +) + // Image represents an operating system image. type Image struct { // ID must be an immutable ID, preferably a UUID. @@ -78,11 +86,25 @@ type Image struct { Created time.Time // Modified is when the image was modified. Modified time.Time + // ImageVirtualization defines how the image can be used. + Virtualization ImageVirtualization // KubernetesVersion is only populated if the image contains a pre-installed // version of Kubernetes, this acts as a cache and improves provisioning performance. // This is pretty much the only source of truth about Kubernetes versions at // present, so should be populated. It must be a semver (starts with a vN.N.N). KubernetesVersion string + // GPU is any GPU specific configuration for scheduling on a specific flavor type. + GPU *ImageGPU +} + +// ImageGPU defines image specific GPU compatibility information. +type ImageGPU struct { + // Vendor is the vendor a GPU is compatible with. + Vendor GPUVendor + // Driver is the driver version string. + Driver string + // Models is a list of GPU models a driver is certified with. + Models []string } // ImageList allows us to attach sort functions and the like.