diff --git a/dbox b/dbox index 7afcbb4..592c1c4 100755 --- a/dbox +++ b/dbox @@ -16,16 +16,20 @@ da) kubectl delete -f deploy/crds/hypercloud.tmaxanc.com_virtualmachinevolumes_crd.yaml --ignore-not-found=true kubectl delete -f deploy/crds/hypercloud.tmaxanc.com_v1alpha1_virtualmachinevolumeexport_cr.yaml --ignore-not-found=true kubectl delete -f deploy/crds/hypercloud.tmaxanc.com_virtualmachinevolumeexports_crd.yaml --ignore-not-found=true + kubectl delete -f deploy/crds/hypercloud.tmaxanc.com_v1alpha1_localuploadproxy_cr.yaml --ignore-not-found=true + kubectl delete -f deploy/crds/hypercloud.tmaxanc.com_localuploadproxies_crd.yaml --ignore-not-found=true ;; dcr) kubectl delete -f deploy/crds/hypercloud.tmaxanc.com_v1alpha1_virtualmachineimage_cr.yaml --ignore-not-found=true kubectl delete -f deploy/crds/hypercloud.tmaxanc.com_v1alpha1_virtualmachinevolume_cr.yaml --ignore-not-found=true kubectl delete -f deploy/crds/hypercloud.tmaxanc.com_v1alpha1_virtualmachinevolumeexport_cr.yaml --ignore-not-found=true + kubectl delete -f deploy/crds/hypercloud.tmaxanc.com_v1alpha1_localuploadproxy_cr.yaml --ignore-not-found=true ;; dcrd) kubectl delete -f deploy/crds/hypercloud.tmaxanc.com_virtualmachineimages_crd.yaml --ignore-not-found=true kubectl delete -f deploy/crds/hypercloud.tmaxanc.com_virtualmachinevolumes_crd.yaml --ignore-not-found=true kubectl delete -f deploy/crds/hypercloud.tmaxanc.com_virtualmachinevolumeexports_crd.yaml --ignore-not-found=true + kubectl delete -f deploy/crds/hypercloud.tmaxanc.com_localuploadproxies_crd.yaml --ignore-not-found=true ;; do) ;; @@ -36,16 +40,20 @@ aa) kubectl apply -f deploy/crds/hypercloud.tmaxanc.com_v1alpha1_virtualmachinevolume_cr.yaml kubectl apply -f deploy/crds/hypercloud.tmaxanc.com_virtualmachinevolumeexports_crd.yaml kubectl apply -f deploy/crds/hypercloud.tmaxanc.com_v1alpha1_virtualmachinevolumeexport_cr.yaml + kubectl apply -f deploy/crds/hypercloud.tmaxanc.com_localuploadproxies_crd.yaml + kubectl apply -f deploy/crds/hypercloud.tmaxanc.com_v1alpha1_localuploadproxy_cr.yaml ;; acr) kubectl apply -f deploy/crds/hypercloud.tmaxanc.com_v1alpha1_virtualmachineimage_cr.yaml kubectl apply -f deploy/crds/hypercloud.tmaxanc.com_v1alpha1_virtualmachinevolume_cr.yaml kubectl apply -f deploy/crds/hypercloud.tmaxanc.com_v1alpha1_virtualmachinevolumeexport_cr.yaml + kubectl apply -f deploy/crds/hypercloud.tmaxanc.com_v1alpha1_localuploadproxy_cr.yaml ;; acrd) kubectl apply -f deploy/crds/hypercloud.tmaxanc.com_virtualmachineimages_crd.yaml kubectl apply -f deploy/crds/hypercloud.tmaxanc.com_virtualmachinevolumes_crd.yaml kubectl apply -f deploy/crds/hypercloud.tmaxanc.com_virtualmachinevolumeexports_crd.yaml + kubectl apply -f deploy/crds/hypercloud.tmaxanc.com_localuploadproxies_crd.yaml ;; *) echo " $0 [command] diff --git a/deploy/crds/hypercloud.tmaxanc.com_localuploadproxies_crd.yaml b/deploy/crds/hypercloud.tmaxanc.com_localuploadproxies_crd.yaml new file mode 100644 index 0000000..6cd3780 --- /dev/null +++ b/deploy/crds/hypercloud.tmaxanc.com_localuploadproxies_crd.yaml @@ -0,0 +1,230 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: localuploadproxies.hypercloud.tmaxanc.com +spec: + additionalPrinterColumns: + - JSONPath: .status.conditions[0].status + description: Current status of LocalUploadProxy + name: ReadyToUse + type: string + - JSONPath: .status.command + description: Command used for image upload + name: Command + type: string + group: hypercloud.tmaxanc.com + names: + kind: LocalUploadProxy + listKind: LocalUploadProxyList + plural: localuploadproxies + shortNames: + - lup + singular: localuploadproxy + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + description: LocalUploadProxy is the Schema for the localuploadproxies API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: LocalUploadProxySpec defines the desired state of LocalUploadProxy + properties: + pvc: + description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + Important: Run "operator-sdk generate k8s" to regenerate code after + modifying this file Add custom validation using kubebuilder tags: + https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html' + properties: + accessModes: + description: 'AccessModes contains the desired access modes the + volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + items: + type: string + type: array + dataSource: + description: This field requires the VolumeSnapshotDataSource alpha + feature gate to be enabled and currently VolumeSnapshot is the + only supported data source. If the provisioner can support VolumeSnapshot + data source, it will create a new volume and data will be restored + to the volume at the same time. If the provisioner does not support + VolumeSnapshot data source, volume will not be created and the + failure will be reported as an event. In the future, we plan to + support more data source types and the behavior of the provisioner + may change. + properties: + apiGroup: + description: APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in + the core API group. For any other third-party types, APIGroup + is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + resources: + description: 'Resources represents the minimum resources the volume + should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount of compute + resources required. If Requests is omitted for a container, + it defaults to Limits if that is explicitly specified, otherwise + to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + type: object + selector: + description: A label query over volumes to consider for binding. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the + key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object + storageClassName: + description: 'Name of the StorageClass required by the claim. More + info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + type: string + volumeMode: + description: volumeMode defines what type of volume is required + by the claim. Value of Filesystem is implied when not included + in claim spec. This is a beta feature. + type: string + volumeName: + description: VolumeName is the binding reference to the PersistentVolume + backing this claim. + type: string + type: object + required: + - pvc + type: object + status: + description: LocalUploadProxyStatus defines the observed state of LocalUploadProxy + properties: + command: + type: string + conditions: + description: Conditions indicate current conditions of LocalUploadProxy + items: + description: Condition indicates observed condition of an object + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. This should be when the underlying condition changed. If + that is not known, then using the time when the API field changed + is acceptable. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. This field may be empty. + type: string + observedGeneration: + description: If set, this represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.condition[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + type: integer + reason: + description: The reason for the condition's last transition in + CamelCase. The specific API may choose whether or not this field + is considered a guaranteed API. This field may not be empty. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition in CamelCase or in foo.example.com/CamelCase. + Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + version: v1alpha1 + versions: + - name: v1alpha1 + served: true + storage: true diff --git a/deploy/crds/hypercloud.tmaxanc.com_v1alpha1_localuploadproxy_cr.yaml b/deploy/crds/hypercloud.tmaxanc.com_v1alpha1_localuploadproxy_cr.yaml new file mode 100644 index 0000000..1138aeb --- /dev/null +++ b/deploy/crds/hypercloud.tmaxanc.com_v1alpha1_localuploadproxy_cr.yaml @@ -0,0 +1,13 @@ +apiVersion: hypercloud.tmaxanc.com/v1alpha1 +kind: LocalUploadProxy +metadata: + name: myubuntu +spec: + pvc: + volumeMode: Block + accessModes: + - ReadWriteMany + resources: + requests: + storage: "3Gi" + storageClassName: rook-ceph-block diff --git a/deploy/crds/hypercloud.tmaxanc.com_v1alpha1_virtualmachineimage_local_cr.yaml b/deploy/crds/hypercloud.tmaxanc.com_v1alpha1_virtualmachineimage_local_cr.yaml new file mode 100644 index 0000000..1baa25a --- /dev/null +++ b/deploy/crds/hypercloud.tmaxanc.com_v1alpha1_virtualmachineimage_local_cr.yaml @@ -0,0 +1,10 @@ +apiVersion: hypercloud.tmaxanc.com/v1alpha1 +kind: VirtualMachineImage +metadata: + name: localvmim +spec: + source: + # image file을 업로드 한 local upload proxy의 이름 + local: myubuntu + # 스냅샷 프로비저닝을 위해 사용 할 CSI를 담은 객체(snapshotClass)의 이름 + snapshotClassName: csi-rbdplugin-snapclass diff --git a/deploy/crds/hypercloud.tmaxanc.com_virtualmachineimages_crd.yaml b/deploy/crds/hypercloud.tmaxanc.com_virtualmachineimages_crd.yaml index da6e499..adbf822 100644 --- a/deploy/crds/hypercloud.tmaxanc.com_virtualmachineimages_crd.yaml +++ b/deploy/crds/hypercloud.tmaxanc.com_virtualmachineimages_crd.yaml @@ -169,11 +169,10 @@ spec: properties: http: type: string - required: - - http + local: + type: string type: object required: - - pvc - snapshotClassName - source type: object @@ -227,6 +226,8 @@ spec: - type type: object type: array + pvcName: + type: string state: description: State is the current state of VirtualMachineImage type: string diff --git a/deploy/role.yaml b/deploy/role.yaml index 039d7fe..a1a818b 100644 --- a/deploy/role.yaml +++ b/deploy/role.yaml @@ -73,6 +73,7 @@ rules: - '*' - virtualmachinevolumes - virtualmachinevolumeexports + - localuploadproxies verbs: - create - delete diff --git a/docs/USERGUIDE.md b/docs/USERGUIDE.md index 7458bc5..8eab97d 100644 --- a/docs/USERGUIDE.md +++ b/docs/USERGUIDE.md @@ -40,6 +40,7 @@ $ git checkout v1.0.0 $ kubectl apply -f deploy/crds/hypercloud.tmaxanc.com_virtualmachineimages_crd.yaml $ kubectl apply -f deploy/crds/hypercloud.tmaxanc.com_virtualmachinevolumes_crd.yaml $ kubectl apply -f deploy/crds/hypercloud.tmaxanc.com_virtualmachinevolumeexports_crd.yaml +$ kubectl apply -f deploy/crds/hypercloud.tmaxanc.com_localuploadproxies_crd.yaml # Deploy operator $ kubectl apply -f deploy/role.yaml @@ -57,7 +58,9 @@ kubevirt-image-service 3/3 3 3 23s # Use Kubevirt-Image-Service -## Import image from HTTP source +## Import image + +### 1. Import image from HTTP source vmim is the shortname for `VirtualMachineImage`. @@ -71,6 +74,29 @@ NAME STATE myubuntu Available ``` +### 2. Import image from Local source + +```shell +# Deploy local upload proxy CR. +$ kubectl apply -f deploy/crds/hypercloud.tmaxanc.com_v1alpha1_localuploadproxy_cr.yaml + +# Wait until local upload proxy is ready to use and the command is not null +$ kubectl get lup +NAME READYTOUSE COMMAND +myubuntu True < /filePath/file.name kubectl exec -i myubuntu-upload-pod -n default -- dd of=/dev/block-volume + +# Change the `/filePath/file.name` in the command string and execute the command to upload the raw type image +$ < image.raw kubectl exec -i myubuntu-upload-pod -n default -- dd of=/dev/block-volume + +# Deploy image CR. +$ kubectl apply -f deploy/crds/hypercloud.tmaxanc.com_v1alpha1_virtualmachineimage_local_cr.yaml + +# Wait until image state is ready to use +$ kubectl get vmim +NAME STATE +localvmim Available +``` + ## Create volume from image vmv is the shortname for `VirtualMachineVolume`. diff --git a/e2e/localuploadproxy.go b/e2e/localuploadproxy.go new file mode 100644 index 0000000..39d48ca --- /dev/null +++ b/e2e/localuploadproxy.go @@ -0,0 +1,83 @@ +package e2e + +import ( + "context" + framework "github.com/operator-framework/operator-sdk/pkg/test" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" + "kubevirt-image-service/pkg/apis" + "kubevirt-image-service/pkg/apis/hypercloud/v1alpha1" + "kubevirt-image-service/pkg/util" + "testing" +) + +func localUploadProxyTest(t *testing.T, ctx *framework.Context) error { + ns, err := ctx.GetWatchNamespace() + if err != nil { + return err + } + sc := &v1alpha1.LocalUploadProxy{} + err = framework.AddToFrameworkScheme(apis.AddToScheme, sc) + if err != nil { + return err + } + if err := testLup(t, ns); err != nil { + return err + } + return nil +} + +func testLup(t *testing.T, namespace string) error { + lupName := "availlup" + lup := newLup(namespace, lupName) + if err := framework.Global.Client.Create(context.Background(), lup, &cleanupOptions); err != nil { + return err + } + return waitForLup(t, namespace, lupName) +} + +func waitForLup(t *testing.T, namespace, name string) error { + return wait.Poll(retryInterval, timeout, func() (done bool, err error) { + t.Logf("Waiting for creating lup: %s in Namespace: %s \n", name, namespace) + lup := &v1alpha1.LocalUploadProxy{} + if err := framework.Global.Client.Get(context.Background(), types.NamespacedName{Namespace: namespace, Name: name}, lup); err != nil { + if errors.IsNotFound(err) { + return false, nil + } + return false, err + } + found, cond := util.GetConditionByType(lup.Status.Conditions, v1alpha1.ConditionReadyToUse) + if found { + // TODO: check error condition + return cond.Status == corev1.ConditionTrue, nil + } + return false, nil + }) +} + +func newLup(ns, name string) *v1alpha1.LocalUploadProxy { + scName := storageClassName + volumeMode := corev1.PersistentVolumeBlock + return &v1alpha1.LocalUploadProxy{ + ObjectMeta: v1.ObjectMeta{ + Name: name, + Namespace: ns, + }, + Spec: v1alpha1.LocalUploadProxySpec{ + PVC: corev1.PersistentVolumeClaimSpec{ + VolumeMode: &volumeMode, + AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteMany}, + Resources: corev1.ResourceRequirements{ + Requests: map[corev1.ResourceName]resource.Quantity{ + corev1.ResourceStorage: resource.MustParse("3Gi"), + }, + }, + StorageClassName: &scName, + }, + }, + } +} diff --git a/e2e/main_test.go b/e2e/main_test.go index 8493632..c1b638b 100644 --- a/e2e/main_test.go +++ b/e2e/main_test.go @@ -30,6 +30,9 @@ func TestKubevirtImageService(t *testing.T) { if err := virtualMachineVolumeExportTest(t, ctx); err != nil { t.Fatal(err) } + if err := localUploadProxyTest(t, ctx); err != nil { + t.Fatal(err) + } } func deployResources(t *testing.T, ctx *framework.Context) error { diff --git a/e2e/virtualmachinimage.go b/e2e/virtualmachinimage.go index ace13f5..6d5e36f 100644 --- a/e2e/virtualmachinimage.go +++ b/e2e/virtualmachinimage.go @@ -116,7 +116,7 @@ func newVmi(ns, name string) *v1alpha1.VirtualMachineImage { Source: v1alpha1.VirtualMachineImageSource{ HTTP: "https://download.cirros-cloud.net/contrib/0.3.0/cirros-0.3.0-i386-disk.img", }, - PVC: corev1.PersistentVolumeClaimSpec{ + PVC: &corev1.PersistentVolumeClaimSpec{ VolumeMode: &volumeMode, AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce}, Resources: corev1.ResourceRequirements{ diff --git a/pkg/apis/hypercloud/v1alpha1/localuploadproxy_types.go b/pkg/apis/hypercloud/v1alpha1/localuploadproxy_types.go new file mode 100644 index 0000000..bd659bf --- /dev/null +++ b/pkg/apis/hypercloud/v1alpha1/localuploadproxy_types.go @@ -0,0 +1,53 @@ +package v1alpha1 + +import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// LocalUploadProxySpec defines the desired state of LocalUploadProxy +type LocalUploadProxySpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file + // Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html + PVC corev1.PersistentVolumeClaimSpec `json:"pvc"` +} + +// LocalUploadProxyStatus defines the observed state of LocalUploadProxy +type LocalUploadProxyStatus struct { + // Conditions indicate current conditions of LocalUploadProxy + // +optional + Conditions []Condition `json:"conditions,omitempty"` + Command string `json:"command,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// LocalUploadProxy is the Schema for the localuploadproxies API +// +kubebuilder:subresource:status +// +kubebuilder:resource:path=localuploadproxies,scope=Namespaced,shortName=lup +// +kubebuilder:printcolumn:name="ReadyToUse",type="string",JSONPath=".status.conditions[0].status",description="Current status of LocalUploadProxy" +// +kubebuilder:printcolumn:name="Command",type="string",JSONPath=".status.command",description="Command used for image upload" +type LocalUploadProxy struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec LocalUploadProxySpec `json:"spec,omitempty"` + Status LocalUploadProxyStatus `json:"status,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// LocalUploadProxyList contains a list of LocalUploadProxy +type LocalUploadProxyList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []LocalUploadProxy `json:"items"` +} + +func init() { + SchemeBuilder.Register(&LocalUploadProxy{}, &LocalUploadProxyList{}) +} diff --git a/pkg/apis/hypercloud/v1alpha1/virtualmachineimage_types.go b/pkg/apis/hypercloud/v1alpha1/virtualmachineimage_types.go index 9c5a5a5..88797c5 100644 --- a/pkg/apis/hypercloud/v1alpha1/virtualmachineimage_types.go +++ b/pkg/apis/hypercloud/v1alpha1/virtualmachineimage_types.go @@ -7,14 +7,15 @@ import ( // VirtualMachineImageSource provides parameters to create a VirtualMachineImage from an HTTP source type VirtualMachineImageSource struct { - HTTP string `json:"http"` + HTTP string `json:"http,omitempty"` + Local string `json:"local,omitempty"` } // VirtualMachineImageSpec defines the desired state of VirtualMachineImage type VirtualMachineImageSpec struct { - Source VirtualMachineImageSource `json:"source"` - PVC corev1.PersistentVolumeClaimSpec `json:"pvc"` - SnapshotClassName string `json:"snapshotClassName"` + Source VirtualMachineImageSource `json:"source"` + PVC *corev1.PersistentVolumeClaimSpec `json:"pvc,omitempty"` + SnapshotClassName string `json:"snapshotClassName"` } // VirtualMachineImageState is the current state of VirtualMachineImage @@ -44,6 +45,7 @@ type VirtualMachineImageStatus struct { // Conditions indicate current conditions of VirtualMachineImage // +optional Conditions []Condition `json:"conditions,omitempty"` + PvcName string `json:"pvcName,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/apis/hypercloud/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/hypercloud/v1alpha1/zz_generated.deepcopy.go index 04c31c4..e9a7269 100644 --- a/pkg/apis/hypercloud/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/hypercloud/v1alpha1/zz_generated.deepcopy.go @@ -26,6 +26,107 @@ func (in *Condition) DeepCopy() *Condition { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LocalUploadProxy) DeepCopyInto(out *LocalUploadProxy) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalUploadProxy. +func (in *LocalUploadProxy) DeepCopy() *LocalUploadProxy { + if in == nil { + return nil + } + out := new(LocalUploadProxy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *LocalUploadProxy) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LocalUploadProxyList) DeepCopyInto(out *LocalUploadProxyList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]LocalUploadProxy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalUploadProxyList. +func (in *LocalUploadProxyList) DeepCopy() *LocalUploadProxyList { + if in == nil { + return nil + } + out := new(LocalUploadProxyList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *LocalUploadProxyList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LocalUploadProxySpec) DeepCopyInto(out *LocalUploadProxySpec) { + *out = *in + in.PVC.DeepCopyInto(&out.PVC) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalUploadProxySpec. +func (in *LocalUploadProxySpec) DeepCopy() *LocalUploadProxySpec { + if in == nil { + return nil + } + out := new(LocalUploadProxySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LocalUploadProxyStatus) DeepCopyInto(out *LocalUploadProxyStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalUploadProxyStatus. +func (in *LocalUploadProxyStatus) DeepCopy() *LocalUploadProxyStatus { + if in == nil { + return nil + } + out := new(LocalUploadProxyStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VirtualMachineImage) DeepCopyInto(out *VirtualMachineImage) { *out = *in @@ -123,7 +224,11 @@ func (in *VirtualMachineImageSource) DeepCopy() *VirtualMachineImageSource { func (in *VirtualMachineImageSpec) DeepCopyInto(out *VirtualMachineImageSpec) { *out = *in out.Source = in.Source - in.PVC.DeepCopyInto(&out.PVC) + if in.PVC != nil { + in, out := &in.PVC, &out.PVC + *out = new(v1.PersistentVolumeClaimSpec) + (*in).DeepCopyInto(*out) + } return } diff --git a/pkg/controller/add_localuploadproxy.go b/pkg/controller/add_localuploadproxy.go new file mode 100644 index 0000000..806cda5 --- /dev/null +++ b/pkg/controller/add_localuploadproxy.go @@ -0,0 +1,10 @@ +package controller + +import ( + "kubevirt-image-service/pkg/controller/localuploadproxy" +) + +func init() { + // AddToManagerFuncs is a list of functions to create controllers and add them to a manager. + AddToManagerFuncs = append(AddToManagerFuncs, localuploadproxy.Add) +} diff --git a/pkg/controller/localuploadproxy/local_pod.go b/pkg/controller/localuploadproxy/local_pod.go new file mode 100644 index 0000000..015c71e --- /dev/null +++ b/pkg/controller/localuploadproxy/local_pod.go @@ -0,0 +1,113 @@ +package localuploadproxy + +import ( + "context" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/klog" + hc "kubevirt-image-service/pkg/apis/hypercloud/v1alpha1" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +const ( + // DataVolName provides a const to use for creating volumes in pod specs + DataVolName = "data-vol" + // WriteBlockPath provides a constant for the path where the PV is mounted. + WriteBlockPath = "/dev/block-volume" +) + +func (r *ReconcileLocalUploadProxy) syncLocalPod() error { + if err := r.getPvc(); err != nil && !errors.IsNotFound(err) { + return err + } + + localPod := &corev1.Pod{} + err := r.client.Get(context.Background(), types.NamespacedName{Namespace: r.lup.Namespace, Name: GetLocalPodNameFromLupName(r.lup.Name)}, localPod) + if err != nil && !errors.IsNotFound(err) { + return err + } + existsLocalPod := err == nil + + if !existsLocalPod { + klog.Infof("Create a new local pod for lup %s", r.lup.Name) + newPod, err := newLocalPod(r.lup, r.scheme) + if err != nil { + return err + } + if err = r.client.Create(context.TODO(), newPod); err != nil && !errors.IsAlreadyExists(err) { + return err + } + if err := r.updateStatusWithCommand(); err != nil { + return err + } + } else if localPod.Status.Phase == corev1.PodRunning { + if err := r.updateStateWithReadyToUse(corev1.ConditionTrue, "LocalUploadProxyIsReady", "LocalUploadProxy is ready to use. Use `status.command` to upload raw image."); err != nil { + return err + } + } + + return nil +} + +// GetLocalPodNameFromLupName returns localPod name from localUploadProxyName +func GetLocalPodNameFromLupName(lupName string) string { + return lupName + "-upload-pod" +} + +func newLocalPod(lup *hc.LocalUploadProxy, scheme *runtime.Scheme) (*corev1.Pod, error) { + lp := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: GetLocalPodNameFromLupName(lup.Name), + Namespace: lup.Namespace, + }, + Spec: corev1.PodSpec{ + RestartPolicy: corev1.RestartPolicyAlways, + Containers: []corev1.Container{ + { + Name: "busybox", + Image: "busybox", + Command: []string{"sleep", "infinity"}, + ImagePullPolicy: corev1.PullPolicy("IfNotPresent"), + Resources: corev1.ResourceRequirements{ + Limits: map[corev1.ResourceName]resource.Quantity{ + corev1.ResourceCPU: resource.MustParse("0"), + corev1.ResourceMemory: resource.MustParse("0")}, + Requests: map[corev1.ResourceName]resource.Quantity{ + corev1.ResourceCPU: resource.MustParse("0"), + corev1.ResourceMemory: resource.MustParse("0")}, + }, + VolumeDevices: []corev1.VolumeDevice{ + {Name: DataVolName, DevicePath: WriteBlockPath}, + }, + }, + }, + Volumes: []corev1.Volume{ + { + Name: DataVolName, + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: GetPvcNameFromLupName(lup.Name), + }, + }, + }, + }, + SecurityContext: &corev1.PodSecurityContext{ + RunAsUser: &[]int64{0}[0], + }, + }, + } + if err := controllerutil.SetControllerReference(lup, lp, scheme); err != nil { + return nil, err + } + return lp, nil +} + +func (r *ReconcileLocalUploadProxy) updateStatusWithCommand() error { + r.lup.Status.Command = "< /filePath/file.name kubectl exec -i " + GetLocalPodNameFromLupName(r.lup.Name) + " -n " + r.lup.Namespace + + " -- dd of=" + WriteBlockPath + return r.client.Status().Update(context.TODO(), r.lup) +} diff --git a/pkg/controller/localuploadproxy/localuploadproxy_controller.go b/pkg/controller/localuploadproxy/localuploadproxy_controller.go new file mode 100644 index 0000000..1c17c61 --- /dev/null +++ b/pkg/controller/localuploadproxy/localuploadproxy_controller.go @@ -0,0 +1,111 @@ +package localuploadproxy + +import ( + "context" + "k8s.io/klog" + "kubevirt-image-service/pkg/util" + + hc "kubevirt-image-service/pkg/apis/hypercloud/v1alpha1" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" +) + +// Add creates a new LocalUploadProxy Controller and adds it to the Manager +func Add(mgr manager.Manager) error { + return add(mgr, newReconciler(mgr)) +} + +// newReconciler returns a new reconcile.Reconciler +func newReconciler(mgr manager.Manager) reconcile.Reconciler { + return &ReconcileLocalUploadProxy{client: mgr.GetClient(), scheme: mgr.GetScheme()} +} + +// add adds a new Controller to mgr with r as the reconcile.Reconciler +func add(mgr manager.Manager, r reconcile.Reconciler) error { + // Create a new controller + c, err := controller.New("localuploadproxy-controller", mgr, controller.Options{Reconciler: r}) + if err != nil { + return err + } + + // Watch for changes to primary resource LocalUploadProxy + if err := c.Watch(&source.Kind{Type: &hc.LocalUploadProxy{}}, &handler.EnqueueRequestForObject{}); err != nil { + return err + } + + // Watch for changes to secondary resource Pods and requeue the owner LocalUploadProxy + if err := c.Watch(&source.Kind{Type: &corev1.Pod{}}, + &handler.EnqueueRequestForOwner{IsController: true, OwnerType: &hc.LocalUploadProxy{}}); err != nil { + return err + } + if err := c.Watch(&source.Kind{Type: &corev1.PersistentVolumeClaim{}}, + &handler.EnqueueRequestForOwner{IsController: true, OwnerType: &hc.LocalUploadProxy{}}); err != nil { + return err + } + return nil +} + +// blank assignment to verify that ReconcileLocalUploadProxy implements reconcile.Reconciler +var _ reconcile.Reconciler = &ReconcileLocalUploadProxy{} + +// ReconcileLocalUploadProxy reconciles a LocalUploadProxy object +type ReconcileLocalUploadProxy struct { + // This client, initialized using mgr.Client() above, is a split client + // that reads objects from the cache and writes to the apiserver + client client.Client + scheme *runtime.Scheme + lup *hc.LocalUploadProxy +} + +// Reconcile reads that state of the cluster for a LocalUploadProxy object and makes changes based on the state read +// and what is in the LocalUploadProxy.Spec +// Note: +// The Controller will requeue the Request to be processed again if the returned error is non-nil or +// Result.Requeue is true, otherwise upon completion it will remove the work from the queue. +func (r *ReconcileLocalUploadProxy) Reconcile(request reconcile.Request) (reconcile.Result, error) { + klog.Infof("Start sync LocalUploadProxy %s", request.NamespacedName) + defer func() { + klog.Infof("End sync LocalUploadProxy %s", request.NamespacedName) + }() + + cachedLup := &hc.LocalUploadProxy{} + if err := r.client.Get(context.TODO(), request.NamespacedName, cachedLup); err != nil { + if errors.IsNotFound(err) { + return reconcile.Result{}, nil // Deleted localUploadProxy. Return and don't requeue. + } + return reconcile.Result{}, err + } + r.lup = cachedLup.DeepCopy() + + syncAll := func() error { + if err := r.syncPvc(); err != nil { + return err + } + if err := r.syncLocalPod(); err != nil { + return err + } + return nil + } + if err := syncAll(); err != nil { + // TODO: Setup Error reason + if err2 := r.updateStateWithReadyToUse(corev1.ConditionFalse, "SeeMessages", err.Error()); err2 != nil { + return reconcile.Result{}, err2 + } + return reconcile.Result{}, err + } + return reconcile.Result{}, nil +} + +// updateStateWithReadyToUse updates readyToUse and State. Other Status fields are not affected. lup must be DeepCopy to avoid polluting the cache. +func (r *ReconcileLocalUploadProxy) updateStateWithReadyToUse(readyToUseStatus corev1.ConditionStatus, reason, message string) error { + r.lup.Status.Conditions = util.SetConditionByType(r.lup.Status.Conditions, hc.ConditionReadyToUse, readyToUseStatus, reason, message) + return r.client.Status().Update(context.TODO(), r.lup) +} diff --git a/pkg/controller/localuploadproxy/pvc.go b/pkg/controller/localuploadproxy/pvc.go new file mode 100644 index 0000000..4038ffd --- /dev/null +++ b/pkg/controller/localuploadproxy/pvc.go @@ -0,0 +1,62 @@ +package localuploadproxy + +import ( + "context" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/klog" + hc "kubevirt-image-service/pkg/apis/hypercloud/v1alpha1" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +func (r *ReconcileLocalUploadProxy) syncPvc() error { + if err := r.getPvc(); err == nil { + return nil + } else if !errors.IsNotFound(err) { + return err + } + + klog.Infof("Create a new pvc for lup %s", r.lup.Name) + if err := r.updateStateWithReadyToUse(corev1.ConditionFalse, "LocalUploadProxyIsCreating", "LocalUploadProxy is in creating"); err != nil { + return err + } + + newPvc, err := newPvc(r.lup, r.scheme) + if err != nil { + return err + } + if err := r.client.Create(context.TODO(), newPvc); err != nil && !errors.IsAlreadyExists(err) { + return err + } + return nil +} + +func (r *ReconcileLocalUploadProxy) getPvc() error { + pvc := &corev1.PersistentVolumeClaim{} + if err := r.client.Get(context.TODO(), types.NamespacedName{Name: GetPvcNameFromLupName(r.lup.Name), Namespace: r.lup.Namespace}, pvc); err != nil { + return err + } + return nil +} + +// GetPvcNameFromLupName is return pvcName for localUploadProxyName +func GetPvcNameFromLupName(lupName string) string { + return lupName + "-upload-pvc" +} + +func newPvc(lup *hc.LocalUploadProxy, scheme *runtime.Scheme) (*corev1.PersistentVolumeClaim, error) { + pvc := &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: GetPvcNameFromLupName(lup.Name), + Namespace: lup.Namespace, + }, + Spec: lup.Spec.PVC, + } + if err := controllerutil.SetControllerReference(lup, pvc, scheme); err != nil { + return nil, err + } + return pvc, nil +} diff --git a/pkg/controller/virtualmachineimage/local_pvc.go b/pkg/controller/virtualmachineimage/local_pvc.go new file mode 100644 index 0000000..7333238 --- /dev/null +++ b/pkg/controller/virtualmachineimage/local_pvc.go @@ -0,0 +1,83 @@ +package virtualmachineimage + +import ( + "context" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/json" + "k8s.io/klog" + hc "kubevirt-image-service/pkg/apis/hypercloud/v1alpha1" + "kubevirt-image-service/pkg/controller/localuploadproxy" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" +) + +func (r *ReconcileVirtualMachineImage) syncLocalUploadProxyPvc() error { + pvc := &corev1.PersistentVolumeClaim{} + err := r.client.Get(context.TODO(), types.NamespacedName{Name: localuploadproxy.GetPvcNameFromLupName(r.vmi.Spec.Source.Local), Namespace: r.vmi.Namespace}, pvc) + if err != nil { + return err + } + pvcIsVmiOwner := pvc.GetOwnerReferences()[0].Kind == "VirtualMachineImage" + + if !pvcIsVmiOwner { + klog.Infof("Update the owner of pvc to vmi %s", r.vmi.Name) + if err := r.updateStateWithReadyToUse(hc.VirtualMachineImageStateCreating, corev1.ConditionFalse, "VmiIsCreating", "VMI is in creating"); err != nil { + return err + } + + if err := r.updateOwnerToVmi(pvc); err != nil { + return err + } + + if err := r.deleteLocalUploadProxy(); err != nil { + return err + } + } + + return nil +} + +func (r *ReconcileVirtualMachineImage) updateOwnerToVmi(pvc *corev1.PersistentVolumeClaim) error { + gvk, err := apiutil.GVKForObject(r.vmi, r.scheme) + if err != nil { + return err + } + vmiOwner, err := json.Marshal(map[string]interface{}{ + "metadata": map[string]interface{}{ + "ownerReferences": []map[string]interface{}{ + { + "apiVersion": gvk.GroupVersion().String(), + "kind": gvk.Kind, + "name": r.vmi.GetName(), + "uid": r.vmi.GetUID(), + "blockOwnerDeletion": true, + "controller": true, + }, + }, + }, + }) + if err != nil { + return err + } + if err := r.client.Patch(context.TODO(), pvc, client.RawPatch(types.MergePatchType, vmiOwner)); err != nil { + return err + } + klog.Info(pvc.GetOwnerReferences()) + return nil +} + +func (r *ReconcileVirtualMachineImage) deleteLocalUploadProxy() error { + localUploadProxy := &hc.LocalUploadProxy{} + if err := r.client.Get(context.TODO(), types.NamespacedName{Name: r.vmi.Spec.Source.Local, Namespace: r.vmi.Namespace}, localUploadProxy); err != nil { + if errors.IsNotFound(err) { + return nil + } + return err + } + if err := r.client.Delete(context.TODO(), localUploadProxy); err != nil && !errors.IsNotFound(err) { + return err + } + return nil +} diff --git a/pkg/controller/virtualmachineimage/pvc.go b/pkg/controller/virtualmachineimage/pvc.go index 92a7f80..b93d918 100644 --- a/pkg/controller/virtualmachineimage/pvc.go +++ b/pkg/controller/virtualmachineimage/pvc.go @@ -56,7 +56,7 @@ func newPvc(vmi *hc.VirtualMachineImage, scheme *runtime.Scheme) (*corev1.Persis Namespace: vmi.Namespace, Annotations: map[string]string{"imported": "no"}, }, - Spec: vmi.Spec.PVC, + Spec: *vmi.Spec.PVC, } if err := controllerutil.SetControllerReference(vmi, pvc, scheme); err != nil { return nil, err diff --git a/pkg/controller/virtualmachineimage/snapshot.go b/pkg/controller/virtualmachineimage/snapshot.go index 8d21dd5..2d8da90 100644 --- a/pkg/controller/virtualmachineimage/snapshot.go +++ b/pkg/controller/virtualmachineimage/snapshot.go @@ -16,7 +16,9 @@ import ( func (r *ReconcileVirtualMachineImage) syncSnapshot() error { imported, found, err := r.isPvcImported() - if err != nil { + if r.vmi.Spec.Source.Local != "" { + imported = true + } else if err != nil { return err } else if !found { return nil @@ -72,7 +74,7 @@ func newSnapshot(vmi *hc.VirtualMachineImage, scheme *runtime.Scheme) (*snapshot Spec: snapshotv1alpha1.VolumeSnapshotSpec{ Source: &corev1.TypedLocalObjectReference{ Kind: "PersistentVolumeClaim", - Name: GetPvcNameFromVmiName(vmi.Name), + Name: vmi.Status.PvcName, }, VolumeSnapshotClassName: &vmi.Spec.SnapshotClassName, }, diff --git a/pkg/controller/virtualmachineimage/test_util.go b/pkg/controller/virtualmachineimage/test_util.go index c4e775e..2fe3959 100644 --- a/pkg/controller/virtualmachineimage/test_util.go +++ b/pkg/controller/virtualmachineimage/test_util.go @@ -38,7 +38,7 @@ func newTestVmi() *hc.VirtualMachineImage { Source: hc.VirtualMachineImageSource{ HTTP: "https://download.cirros-cloud.net/contrib/0.3.0/cirros-0.3.0-i386-disk.img", }, - PVC: corev1.PersistentVolumeClaimSpec{ + PVC: &corev1.PersistentVolumeClaimSpec{ AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce}, Resources: corev1.ResourceRequirements{ Requests: map[corev1.ResourceName]resource.Quantity{ diff --git a/pkg/controller/virtualmachineimage/virtualmachineimage_controller.go b/pkg/controller/virtualmachineimage/virtualmachineimage_controller.go index ba65e3d..a0f72bf 100644 --- a/pkg/controller/virtualmachineimage/virtualmachineimage_controller.go +++ b/pkg/controller/virtualmachineimage/virtualmachineimage_controller.go @@ -9,6 +9,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/klog" hc "kubevirt-image-service/pkg/apis/hypercloud/v1alpha1" + "kubevirt-image-service/pkg/controller/localuploadproxy" "kubevirt-image-service/pkg/util" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -82,6 +83,20 @@ func (r *ReconcileVirtualMachineImage) Reconcile(request reconcile.Request) (rec if err := r.validateVirtualMachineImageSpec(); err != nil { return err } + if err := r.updateStateWithPvcName(); err != nil { + return err + } + if r.vmi.Spec.Source.Local != "" { + // update pvc owner and delete local upload proxy + if err := r.syncLocalUploadProxyPvc(); err != nil { + return err + } + // create snapshot + if err := r.syncSnapshot(); err != nil { + return err + } + return nil + } // pvc가 없으면 상태를 업데이트하고 pvc를 생성한다. if err := r.syncPvc(); err != nil { return err @@ -119,12 +134,26 @@ func (r *ReconcileVirtualMachineImage) updateStateWithReadyToUse(state hc.Virtua } func (r *ReconcileVirtualMachineImage) validateVirtualMachineImageSpec() error { - if r.vmi.Spec.PVC.VolumeMode == nil || *r.vmi.Spec.PVC.VolumeMode != corev1.PersistentVolumeBlock { - return goerrors.New("VolumeMode in pvc is invalid. Only 'Block' can be used") - } - _, found := r.vmi.Spec.PVC.Resources.Requests[corev1.ResourceStorage] - if !found { - return goerrors.New("storage request in pvc is missing") + if r.vmi.Spec.Source.Local == "" { + if r.vmi.Spec.PVC == nil { + return goerrors.New("spec.pvc must not be null when source is not 'local'") + } + if r.vmi.Spec.PVC.VolumeMode == nil || *r.vmi.Spec.PVC.VolumeMode != corev1.PersistentVolumeBlock { + return goerrors.New("VolumeMode in pvc is invalid. Only 'Block' can be used") + } + _, found := r.vmi.Spec.PVC.Resources.Requests[corev1.ResourceStorage] + if !found { + return goerrors.New("storage request in pvc is missing") + } } return nil } + +func (r *ReconcileVirtualMachineImage) updateStateWithPvcName() error { + if r.vmi.Spec.Source.Local != "" { + r.vmi.Status.PvcName = localuploadproxy.GetPvcNameFromLupName(r.vmi.Spec.Source.Local) + } else { + r.vmi.Status.PvcName = GetPvcNameFromVmiName(r.vmi.Name) + } + return r.client.Status().Update(context.TODO(), r.vmi) +} diff --git a/pkg/controller/virtualmachinevolume/test_util.go b/pkg/controller/virtualmachinevolume/test_util.go index 10f6644..65ee6f3 100644 --- a/pkg/controller/virtualmachinevolume/test_util.go +++ b/pkg/controller/virtualmachinevolume/test_util.go @@ -98,7 +98,7 @@ func newTestImage() *hc.VirtualMachineImage { Source: hc.VirtualMachineImageSource{ HTTP: "https://kr.tmaxsoft.com/main.do", }, - PVC: corev1.PersistentVolumeClaimSpec{ + PVC: &corev1.PersistentVolumeClaimSpec{ AccessModes: []corev1.PersistentVolumeAccessMode{ corev1.ReadWriteOnce, },