From 099b8e354ea90b841d6078e3ee9fdfdba2de5d34 Mon Sep 17 00:00:00 2001 From: Sankalp Rangare Date: Mon, 21 Nov 2022 09:14:30 +0100 Subject: [PATCH] add http source for kubevirt diskImage (#1470) Signed-off-by: Sankalp Rangare Signed-off-by: Sankalp Rangare --- .../provider/kubevirt/provider.go | 80 +++++++++++-------- .../provider/kubevirt/provider_test.go | 14 ++++ .../kubevirt/testdata/http-image-source.yaml | 72 +++++++++++++++++ .../kubevirt/testdata/pvc-image-source.yaml | 73 +++++++++++++++++ .../provider/kubevirt/types/types.go | 2 + 5 files changed, 208 insertions(+), 33 deletions(-) create mode 100644 pkg/cloudprovider/provider/kubevirt/testdata/http-image-source.yaml create mode 100644 pkg/cloudprovider/provider/kubevirt/testdata/pvc-image-source.yaml diff --git a/pkg/cloudprovider/provider/kubevirt/provider.go b/pkg/cloudprovider/provider/kubevirt/provider.go index 483f13fcb..9f5358509 100644 --- a/pkg/cloudprovider/provider/kubevirt/provider.go +++ b/pkg/cloudprovider/provider/kubevirt/provider.go @@ -60,12 +60,18 @@ func init() { } } +type imageSource string + const ( // topologyKeyHostname defines the topology key for the node hostname. topologyKeyHostname = "kubernetes.io/hostname" // machineDeploymentLabelKey defines the label key used to contains as value the MachineDeployment name // which machine comes from. machineDeploymentLabelKey = "md" + // httpSource defines the http source type for VM Disk Image. + httpSource imageSource = "http" + // pvcSource defines the pvc source type for VM Disk Image. + pvcSource imageSource = "pvc" ) var supportedOS = map[providerconfigtypes.OperatingSystem]*struct{}{ @@ -93,7 +99,7 @@ type Config struct { CPUs string Memory string Namespace string - OsImage OSImage + OSImageSource *cdiv1beta1.DataVolumeSource StorageClassName string PVCSize resource.Quantity FlavorName string @@ -148,11 +154,6 @@ type SecondaryDisks struct { StorageClassName string } -type OSImage struct { - URL string - DataVolumeName string -} - type kubeVirtServer struct { vmi kubevirtv1.VirtualMachineInstance } @@ -247,15 +248,12 @@ func (p *provider) getConfig(provSpec clusterv1alpha1.ProviderSpec) (*Config, *p return nil, nil, fmt.Errorf(`failed to get value of "memory" field: %w`, err) } config.Namespace = getNamespace() - osImage, err := p.configVarResolver.GetConfigVarStringValue(rawConfig.VirtualMachine.Template.PrimaryDisk.OsImage) + + config.OSImageSource, err = p.parseOSImageSource(rawConfig.VirtualMachine.Template.PrimaryDisk, config.Namespace) if err != nil { - return nil, nil, fmt.Errorf(`failed to get value of "sourceURL" field: %w`, err) - } - if _, err = url.ParseRequestURI(osImage); err == nil { - config.OsImage.URL = osImage - } else { - config.OsImage.DataVolumeName = osImage + return nil, nil, fmt.Errorf(`failed to get value of "osImageSource" field: %w`, err) } + pvcSize, err := p.configVarResolver.GetConfigVarStringValue(rawConfig.VirtualMachine.Template.PrimaryDisk.Size) if err != nil { return nil, nil, fmt.Errorf(`failed to get value of "pvcSize" field: %w`, err) @@ -374,6 +372,35 @@ func (p *provider) parseTopologySpreadConstraint(topologyConstraints []kubevirtt return parsedTopologyConstraints, nil } +func (p *provider) parseOSImageSource(primaryDisk kubevirttypes.PrimaryDisk, nameSpace string) (*cdiv1beta1.DataVolumeSource, error) { + osImage, err := p.configVarResolver.GetConfigVarStringValue(primaryDisk.OsImage) + if err != nil { + return nil, fmt.Errorf(`failed to get value of "primaryDisk.osImage" field: %w`, err) + } + osImageSource, err := p.configVarResolver.GetConfigVarStringValue(primaryDisk.Source) + if err != nil { + return nil, fmt.Errorf(`failed to get value of "primaryDisk.source" field: %w`, err) + } + switch imageSource(osImageSource) { + case httpSource: + return &cdiv1beta1.DataVolumeSource{HTTP: &cdiv1beta1.DataVolumeSourceHTTP{URL: osImage}}, nil + case pvcSource: + if namespaceAndName := strings.Split(osImage, "/"); len(namespaceAndName) >= 2 { + return &cdiv1beta1.DataVolumeSource{PVC: &cdiv1beta1.DataVolumeSourcePVC{Name: namespaceAndName[1], Namespace: namespaceAndName[0]}}, nil + } + return &cdiv1beta1.DataVolumeSource{PVC: &cdiv1beta1.DataVolumeSourcePVC{Name: osImage, Namespace: nameSpace}}, nil + default: + // handle old API for backward compatibility. + if _, err = url.ParseRequestURI(osImage); err == nil { + return &cdiv1beta1.DataVolumeSource{HTTP: &cdiv1beta1.DataVolumeSourceHTTP{URL: osImage}}, nil + } + if namespaceAndName := strings.Split(osImage, "/"); len(namespaceAndName) >= 2 { + return &cdiv1beta1.DataVolumeSource{PVC: &cdiv1beta1.DataVolumeSourcePVC{Name: namespaceAndName[1], Namespace: namespaceAndName[0]}}, nil + } + return &cdiv1beta1.DataVolumeSource{PVC: &cdiv1beta1.DataVolumeSourcePVC{Name: osImage, Namespace: nameSpace}}, nil + } +} + // getNamespace returns the namespace where the VM is created. // VM is created in a dedicated namespace // which is the namespace where the machine-controller pod is running. @@ -506,7 +533,11 @@ func (p *provider) MachineMetricsLabels(machine *clusterv1alpha1.Machine) (map[s if err == nil { labels["cpus"] = c.CPUs labels["memoryMIB"] = c.Memory - labels["osImage"] = c.OsImage.URL + if c.OSImageSource.HTTP != nil { + labels["osImage"] = c.OSImageSource.HTTP.URL + } else if c.OSImageSource.PVC != nil { + labels["osImage"] = c.OSImageSource.PVC.Name + } } return labels, err @@ -787,7 +818,6 @@ func getVMVolumes(config *Config, dataVolumeName string, userDataSecretName stri } func getDataVolumeTemplates(config *Config, dataVolumeName string) []kubevirtv1.DataVolumeTemplateSpec { - dataVolumeSource := getDataVolumeSource(config.OsImage) pvcRequest := corev1.ResourceList{corev1.ResourceStorage: config.PVCSize} dataVolumeTemplates := []kubevirtv1.DataVolumeTemplateSpec{ { @@ -804,7 +834,7 @@ func getDataVolumeTemplates(config *Config, dataVolumeName string) []kubevirtv1. Requests: pvcRequest, }, }, - Source: dataVolumeSource, + Source: config.OSImageSource, }, }, } @@ -823,29 +853,13 @@ func getDataVolumeTemplates(config *Config, dataVolumeName string) []kubevirtv1. Requests: corev1.ResourceList{corev1.ResourceStorage: sd.Size}, }, }, - Source: dataVolumeSource, + Source: config.OSImageSource, }, }) } return dataVolumeTemplates } -// getDataVolumeSource returns DataVolumeSource, HTTP or PVC. -func getDataVolumeSource(osImage OSImage) *cdiv1beta1.DataVolumeSource { - dataVolumeSource := &cdiv1beta1.DataVolumeSource{} - if osImage.URL != "" { - dataVolumeSource.HTTP = &cdiv1beta1.DataVolumeSourceHTTP{URL: osImage.URL} - } else if osImage.DataVolumeName != "" { - if nameSpaceAndName := strings.Split(osImage.DataVolumeName, "/"); len(nameSpaceAndName) >= 2 { - dataVolumeSource.PVC = &cdiv1beta1.DataVolumeSourcePVC{ - Namespace: nameSpaceAndName[0], - Name: nameSpaceAndName[1], - } - } - } - return dataVolumeSource -} - func getAffinity(config *Config, matchKey, matchValue string) *corev1.Affinity { affinity := &corev1.Affinity{} diff --git a/pkg/cloudprovider/provider/kubevirt/provider_test.go b/pkg/cloudprovider/provider/kubevirt/provider_test.go index 3af0d314c..38e8933c4 100644 --- a/pkg/cloudprovider/provider/kubevirt/provider_test.go +++ b/pkg/cloudprovider/provider/kubevirt/provider_test.go @@ -103,6 +103,7 @@ type kubevirtProviderSpecConf struct { TopologySpreadConstraint bool Affinity bool SecondaryDisks bool + OsImageSource imageSource } func (k kubevirtProviderSpecConf) rawProviderSpec(t *testing.T) []byte { @@ -167,7 +168,12 @@ func (k kubevirtProviderSpecConf) rawProviderSpec(t *testing.T) []byte { "osImage": "http://x.y.z.t/ubuntu.img", {{- end }} "size": "10Gi", + {{- if .OsImageSource }} + "storageClassName": "longhorn", + "source": "{{ .OsImageSource }}" + {{- else }} "storageClassName": "longhorn" + {{- end }} } } } @@ -267,6 +273,14 @@ func TestNewVirtualMachine(t *testing.T) { name: "custom-local-disk", specConf: kubevirtProviderSpecConf{OsImageDV: "ns/dvname"}, }, + { + name: "http-image-source", + specConf: kubevirtProviderSpecConf{OsImageSource: httpSource}, + }, + { + name: "pvc-image-source", + specConf: kubevirtProviderSpecConf{OsImageSource: pvcSource, OsImageDV: "ns/dvname"}, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/cloudprovider/provider/kubevirt/testdata/http-image-source.yaml b/pkg/cloudprovider/provider/kubevirt/testdata/http-image-source.yaml new file mode 100644 index 000000000..b488ce0c4 --- /dev/null +++ b/pkg/cloudprovider/provider/kubevirt/testdata/http-image-source.yaml @@ -0,0 +1,72 @@ +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + annotations: + labels: + kubevirt.io/vm: http-image-source + md: md-name + name: http-image-source + namespace: test-namespace +spec: + dataVolumeTemplates: + - metadata: + name: http-image-source + spec: + pvc: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi + storageClassName: longhorn + source: + http: + url: http://x.y.z.t/ubuntu.img + running: true + template: + metadata: + creationTimestamp: null + labels: + kubevirt.io/vm: http-image-source + md: md-name + spec: + affinity: {} + domain: + devices: + disks: + - disk: + bus: virtio + name: datavolumedisk + - disk: + bus: virtio + name: cloudinitdisk + interfaces: + - macAddress: b6:f5:b4:fe:45:1d + name: default + bridge: {} + resources: + limits: + cpu: "2" + memory: 2Gi + requests: + cpu: "2" + memory: 2Gi + networks: + - name: default + pod: {} + terminationGracePeriodSeconds: 30 + topologyspreadconstraints: + - maxskew: 1 + topologykey: kubernetes.io/hostname + whenunsatisfiable: ScheduleAnyway + labelselector: + matchlabels: + md: md-name + volumes: + - dataVolume: + name: http-image-source + name: datavolumedisk + - cloudInitNoCloud: + secretRef: + name: udsn + name: cloudinitdisk diff --git a/pkg/cloudprovider/provider/kubevirt/testdata/pvc-image-source.yaml b/pkg/cloudprovider/provider/kubevirt/testdata/pvc-image-source.yaml new file mode 100644 index 000000000..809c87ab9 --- /dev/null +++ b/pkg/cloudprovider/provider/kubevirt/testdata/pvc-image-source.yaml @@ -0,0 +1,73 @@ +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + annotations: + labels: + kubevirt.io/vm: pvc-image-source + md: md-name + name: pvc-image-source + namespace: test-namespace +spec: + dataVolumeTemplates: + - metadata: + name: pvc-image-source + spec: + pvc: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi + storageClassName: longhorn + source: + pvc: + namespace: ns + name: dvname + running: true + template: + metadata: + creationTimestamp: null + labels: + kubevirt.io/vm: pvc-image-source + md: md-name + spec: + affinity: {} + domain: + devices: + disks: + - disk: + bus: virtio + name: datavolumedisk + - disk: + bus: virtio + name: cloudinitdisk + interfaces: + - macAddress: b6:f5:b4:fe:45:1d + name: default + bridge: {} + resources: + limits: + cpu: "2" + memory: 2Gi + requests: + cpu: "2" + memory: 2Gi + networks: + - name: default + pod: {} + terminationGracePeriodSeconds: 30 + topologyspreadconstraints: + - maxskew: 1 + topologykey: kubernetes.io/hostname + whenunsatisfiable: ScheduleAnyway + labelselector: + matchlabels: + md: md-name + volumes: + - dataVolume: + name: pvc-image-source + name: datavolumedisk + - cloudInitNoCloud: + secretRef: + name: udsn + name: cloudinitdisk diff --git a/pkg/cloudprovider/provider/kubevirt/types/types.go b/pkg/cloudprovider/provider/kubevirt/types/types.go index 365171d08..a0c6d35a6 100644 --- a/pkg/cloudprovider/provider/kubevirt/types/types.go +++ b/pkg/cloudprovider/provider/kubevirt/types/types.go @@ -67,6 +67,8 @@ type Template struct { type PrimaryDisk struct { Disk OsImage providerconfigtypes.ConfigVarString `json:"osImage,omitempty"` + // Source describes the VM Disk Image source. + Source providerconfigtypes.ConfigVarString `json:"source,omitempty"` } // SecondaryDisks.