diff --git a/.gitignore b/.gitignore index a27498f1..e20f8611 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,3 @@ *.swp .idea *.DS_Store -.env diff --git a/README.md b/README.md index c43c3a3b..bd372696 100644 --- a/README.md +++ b/README.md @@ -91,45 +91,6 @@ NAME STATUS devstack clusterReady ``` -For KVM based source clusters a sample definition is as follows: - -```yaml -apiVersion: migration.harvesterhci.io/v1beta1 -kind: KVMSource -metadata: - name: kvm - namespace: default -spec: - libvirtURI: qemu+ssh:///system - credentials: - name: kvm-credentials - namespace: default -``` - -The secret contains the credentials for the KVM host: - -```yaml -apiVersion: v1 -kind: Secret -metadata: - name: kvm-credentials - namespace: default -type: Opaque -stringData: - privateKey: | - -----BEGIN RSA PRIVATE KEY----- - ... - -----END RSA PRIVATE KEY----- -``` - -KVM source reconcile process, attempts to list VM's in the cluster, and marks the source as ready - -```shell -$ kubectl get kvmsource.migration -NAME STATUS -kvm clusterReady -``` - ### VirtualMachimeImport The VirtualMachineImport crd provides a way for users to define the source VM and mapping to the actual source cluster to perform the VM export-import from. @@ -170,7 +131,7 @@ $ kubectl get virtualmachineimport.migration NAME STATUS alpine-export-test virtualMachineRunning openstack-cirros-test virtualMachineRunning -kvm-export-test virtualMachineRunning + ``` Similarly, users can define a VirtualMachineImport for Openstack source as well: @@ -197,25 +158,6 @@ spec: *NOTE:* Openstack allows users to have multiple instances with the same name. In such a scenario the users are advised to use the Instance ID. The reconcile logic tries to perform a lookup from name to ID when a name is used. -And VirtualMachineImport for KMV as well: - -```yaml -apiVersion: migration.harvesterhci.io/v1beta1 -kind: VirtualMachineImport -metadata: - name: kvm-demo - namespace: default -spec: - virtualMachineName: kvm-demo - networkMapping: - - sourceNetwork: "default" - destinationNetwork: "default/vlan1" - sourceCluster: - kind: KVMSource - name: kvm - namespace: default - apiVersion: migration.harvesterhci.io/v1beta1 -``` ## Testing Currently basic integration tests are available under `tests/integration` @@ -241,9 +183,5 @@ export OS_USERNAME="openstack-username" export OS_PASSWORD="openstack-password" export OS_VM_NAME="openstack-export-test-vm-name" export OS_REGION_NAME="openstack-region" -export KVM_LIBVIRT_URI="qemu+ssh://@" -export KVM_SSH_USER="KVM user" -export KVM_SSH_PRIVATE_KEY_PATH="path to KVM ssh key" -export SKIP_VCSIM=true #When testing for KVM only export KUBECONFIG="kubeconfig-for-harvester-cluster" ``` \ No newline at end of file diff --git a/go.mod b/go.mod index c5b5fc9e..922fba2d 100644 --- a/go.mod +++ b/go.mod @@ -13,13 +13,11 @@ require ( github.com/onsi/ginkgo/v2 v2.23.3 github.com/onsi/gomega v1.37.0 github.com/ory/dockertest/v3 v3.9.1 - github.com/pkg/sftp v1.13.10 github.com/rancher/lasso v0.2.3 github.com/rancher/wrangler/v3 v3.2.2 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.10.0 github.com/vmware/govmomi v0.52.0 - golang.org/x/crypto v0.41.0 golang.org/x/sync v0.16.0 k8s.io/api v0.33.1 k8s.io/apiextensions-apiserver v0.33.1 @@ -28,7 +26,6 @@ require ( k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 kubevirt.io/api v1.5.0 kubevirt.io/kubevirt v1.5.0 - libvirt.org/go/libvirtxml v1.11010.0 sigs.k8s.io/cluster-api v1.9.4 sigs.k8s.io/controller-runtime v0.20.2 ) @@ -78,7 +75,6 @@ require ( github.com/jinzhu/copier v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/kr/fs v0.1.0 // indirect github.com/kubernetes-csi/external-snapshotter/client/v4 v4.2.0 // indirect github.com/longhorn/go-common-libs v0.0.0-20250215052214-151615b29f8e // indirect github.com/mailru/easyjson v0.9.0 // indirect @@ -116,12 +112,13 @@ require ( go.uber.org/mock v0.5.2 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect + golang.org/x/crypto v0.40.0 // indirect golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect golang.org/x/mod v0.26.0 // indirect golang.org/x/net v0.42.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sys v0.35.0 // indirect - golang.org/x/term v0.34.0 // indirect + golang.org/x/sys v0.34.0 // indirect + golang.org/x/term v0.33.0 // indirect golang.org/x/text v0.28.0 // indirect golang.org/x/time v0.11.0 // indirect golang.org/x/tools v0.35.0 // indirect diff --git a/go.sum b/go.sum index ff28ded8..c635c85c 100644 --- a/go.sum +++ b/go.sum @@ -200,8 +200,6 @@ github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.7.5 h1:C github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.7.5/go.mod h1:CM7HAH5PNuIsqjMN0fGc1ydM74Uj+0VZFhob620nklw= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -320,8 +318,6 @@ github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.10 h1:+5FbKNTe5Z9aspU88DPIKJ9z2KZoaGCu6Sr6kKR/5mU= -github.com/pkg/sftp v1.13.10/go.mod h1:bJ1a7uDhrX/4OII+agvy28lzRvQrmIQuaHrcI1HbeGA= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -438,8 +434,8 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= -golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= -golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI= golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= @@ -569,8 +565,8 @@ golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20240208230135-b75ee8823808/go.mod h1:KG1lNk5ZFNssSZLrpVb4sMXKMpGwGXOxSG3rnu2gZQQ= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= @@ -596,8 +592,8 @@ golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= -golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= -golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= +golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= +golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -751,8 +747,6 @@ kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6 kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90/go.mod h1:018lASpFYBsYN6XwmA2TIrPCx6e0gviTd/ZNtSitKgc= kubevirt.io/kubevirt v1.6.0 h1:xk7NgCHB3PVedEbuR7J+ehfR6ihmoDrjpl8tG3on3VE= kubevirt.io/kubevirt v1.6.0/go.mod h1:tu6AWqWL1BdGQccdsy8aNsmoqzWWHBf1yDpiZrgSEQo= -libvirt.org/go/libvirtxml v1.11010.0 h1:lGUv6OQ4gz5Hm7F40G+swxmK/kcrMZGQ3M8/S+UyhME= -libvirt.org/go/libvirtxml v1.11010.0/go.mod h1:7Oq2BLDstLr/XtoQD8Fr3mfDNrzlI3utYKySXF2xkng= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.32.1 h1:Cf+ed5N8038zbsaXFO7mKQDi/+VcSRafb0jM84KX5so= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.32.1/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= sigs.k8s.io/cluster-api v1.9.4 h1:pa2Ho50F9Js/Vv/Jy11TcpmGiqY2ukXCoDj/dY25Y7M= diff --git a/pkg/apis/migration.harvesterhci.io/v1beta1/common.go b/pkg/apis/migration.harvesterhci.io/v1beta1/common.go index 30cd6348..c9f5ab30 100644 --- a/pkg/apis/migration.harvesterhci.io/v1beta1/common.go +++ b/pkg/apis/migration.harvesterhci.io/v1beta1/common.go @@ -18,7 +18,6 @@ const ( KindVmwareSource string = "vmwaresource" KindOvaSource string = "ovasource" KindOpenstackSource string = "openstacksource" - KindKVMSource string = "kvmsource" ) type ClusterStatus string diff --git a/pkg/apis/migration.harvesterhci.io/v1beta1/kvm.go b/pkg/apis/migration.harvesterhci.io/v1beta1/kvm.go deleted file mode 100644 index e10efeb3..00000000 --- a/pkg/apis/migration.harvesterhci.io/v1beta1/kvm.go +++ /dev/null @@ -1,106 +0,0 @@ -package v1beta1 - -import ( - "time" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/utils/ptr" - - "github.com/harvester/vm-import-controller/pkg/apis/common" -) - -const ( - DefaultSSHTimeoutSeconds = 30 - DefaultVirshConnectionURI = "" -) - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -type KVMSource struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - Spec KVMSourceSpec `json:"spec"` - Status KVMSourceStatus `json:"status,omitempty"` -} - -type KVMSourceSpec struct { - // The SSH host URI to connect to. If no port is specified, then default - // SSH port 22 will be used. - // E.g., `ssh://user@hostname:2222` - EndpointAddress string `json:"endpoint"` - - // Additional options. - KVMSourceOptions `json:",inline"` - - // The referenced `Secret` should contain the following keys: - // - username: (optional) The username to authenticate at the specified server. - // - password: (optional) The password to authenticate at the specified server. - // - privateKey: (optional) The private key to authenticate at the specified server. - // One of the authentication fields password or privateKey must be specified. - Credentials corev1.SecretReference `json:"credentials"` -} - -type KVMSourceStatus struct { - Status ClusterStatus `json:"status,omitempty"` - // +optional - Conditions []common.Condition `json:"conditions,omitempty"` -} - -type KVMSourceOptions struct { - // +optional - // Timeout is the maximum amount of time in seconds for the SSH connection - // to establish. A timeout of zero means no timeout. - // Defaults to 30 seconds. - SSHTimeoutSeconds *int `json:"sshTimeoutSeconds,omitempty"` - - // +optional - // The connection URI to be used by the `virsh` command that is executed on - // the host specified by the endpoint address. - // E.g., `qemu:///system` - // See https://libvirt.org/uri.html#local-hypervisor-uris for more information. - VirshConnectionURI *string `json:"virshConnectionURI"` -} - -func (s *KVMSource) NamespacedName() string { - return types.NamespacedName{ - Namespace: s.Namespace, - Name: s.Name, - }.String() -} - -func (s *KVMSource) ClusterStatus() ClusterStatus { - return s.Status.Status -} - -func (s *KVMSource) HasSecret() bool { - return true -} - -func (s *KVMSource) SecretReference() *corev1.SecretReference { - return &s.Spec.Credentials -} - -func (s *KVMSource) GetKind() string { - return KindKVMSource -} - -func (s *KVMSource) GetConnectionInfo() (string, string) { - return s.Spec.EndpointAddress, "" -} - -func (s *KVMSource) GetOptions() interface{} { - return s.Spec.KVMSourceOptions -} - -// GetSSHTimeout returns the SSH timeout duration. -func (s *KVMSourceOptions) GetSSHTimeout() time.Duration { - return time.Duration(ptr.Deref(s.SSHTimeoutSeconds, DefaultSSHTimeoutSeconds)) * time.Second -} - -// GetVirshConnectionURI returns the virsh connection URI. -func (s *KVMSourceOptions) GetVirshConnectionURI() string { - return ptr.Deref(s.VirshConnectionURI, DefaultVirshConnectionURI) -} diff --git a/pkg/apis/migration.harvesterhci.io/v1beta1/zz_generated_deepcopy.go b/pkg/apis/migration.harvesterhci.io/v1beta1/zz_generated_deepcopy.go index e26d7feb..9f2e162e 100644 --- a/pkg/apis/migration.harvesterhci.io/v1beta1/zz_generated_deepcopy.go +++ b/pkg/apis/migration.harvesterhci.io/v1beta1/zz_generated_deepcopy.go @@ -49,132 +49,6 @@ func (in *DiskInfo) DeepCopy() *DiskInfo { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *KVMSource) DeepCopyInto(out *KVMSource) { - *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 KVMSource. -func (in *KVMSource) DeepCopy() *KVMSource { - if in == nil { - return nil - } - out := new(KVMSource) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *KVMSource) 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 *KVMSourceList) DeepCopyInto(out *KVMSourceList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]KVMSource, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KVMSourceList. -func (in *KVMSourceList) DeepCopy() *KVMSourceList { - if in == nil { - return nil - } - out := new(KVMSourceList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *KVMSourceList) 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 *KVMSourceOptions) DeepCopyInto(out *KVMSourceOptions) { - *out = *in - if in.SSHTimeoutSeconds != nil { - in, out := &in.SSHTimeoutSeconds, &out.SSHTimeoutSeconds - *out = new(int) - **out = **in - } - if in.VirshConnectionURI != nil { - in, out := &in.VirshConnectionURI, &out.VirshConnectionURI - *out = new(string) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KVMSourceOptions. -func (in *KVMSourceOptions) DeepCopy() *KVMSourceOptions { - if in == nil { - return nil - } - out := new(KVMSourceOptions) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *KVMSourceSpec) DeepCopyInto(out *KVMSourceSpec) { - *out = *in - in.KVMSourceOptions.DeepCopyInto(&out.KVMSourceOptions) - out.Credentials = in.Credentials - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KVMSourceSpec. -func (in *KVMSourceSpec) DeepCopy() *KVMSourceSpec { - if in == nil { - return nil - } - out := new(KVMSourceSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *KVMSourceStatus) DeepCopyInto(out *KVMSourceStatus) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]common.Condition, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KVMSourceStatus. -func (in *KVMSourceStatus) DeepCopy() *KVMSourceStatus { - if in == nil { - return nil - } - out := new(KVMSourceStatus) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NetworkMapping) DeepCopyInto(out *NetworkMapping) { *out = *in diff --git a/pkg/apis/migration.harvesterhci.io/v1beta1/zz_generated_list_types.go b/pkg/apis/migration.harvesterhci.io/v1beta1/zz_generated_list_types.go index b5ef4fa0..42e30d45 100644 --- a/pkg/apis/migration.harvesterhci.io/v1beta1/zz_generated_list_types.go +++ b/pkg/apis/migration.harvesterhci.io/v1beta1/zz_generated_list_types.go @@ -26,23 +26,6 @@ import ( // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// KVMSourceList is a list of KVMSource resources -type KVMSourceList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata"` - - Items []KVMSource `json:"items"` -} - -func NewKVMSource(namespace, name string, obj KVMSource) *KVMSource { - obj.APIVersion, obj.Kind = SchemeGroupVersion.WithKind("KVMSource").ToAPIVersionAndKind() - obj.Name = name - obj.Namespace = namespace - return &obj -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - // OpenstackSourceList is a list of OpenstackSource resources type OpenstackSourceList struct { metav1.TypeMeta `json:",inline"` diff --git a/pkg/apis/migration.harvesterhci.io/v1beta1/zz_generated_register.go b/pkg/apis/migration.harvesterhci.io/v1beta1/zz_generated_register.go index 08eae87e..4bf5b400 100644 --- a/pkg/apis/migration.harvesterhci.io/v1beta1/zz_generated_register.go +++ b/pkg/apis/migration.harvesterhci.io/v1beta1/zz_generated_register.go @@ -28,7 +28,6 @@ import ( ) var ( - KVMSourceResourceName = "kvmsources" OpenstackSourceResourceName = "openstacksources" OvaSourceResourceName = "ovasources" VirtualMachineImportResourceName = "virtualmachineimports" @@ -56,8 +55,6 @@ var ( // Adds the list of known types to Scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, - &KVMSource{}, - &KVMSourceList{}, &OpenstackSource{}, &OpenstackSourceList{}, &OvaSource{}, diff --git a/pkg/controllers/controllers.go b/pkg/controllers/controllers.go index 7e48521f..df01fd87 100644 --- a/pkg/controllers/controllers.go +++ b/pkg/controllers/controllers.go @@ -95,13 +95,10 @@ func Register(ctx context.Context, restConfig *rest.Config) error { } sc.RegisterVmwareController(ctx, migrationFactory.Migration().V1beta1().VmwareSource(), coreFactory.Core().V1().Secret()) - sc.RegisterKVMController(ctx, migrationFactory.Migration().V1beta1().KVMSource(), coreFactory.Core().V1().Secret()) sc.RegisterOvaController(ctx, migrationFactory.Migration().V1beta1().OvaSource(), coreFactory.Core().V1().Secret()) sc.RegisterOpenstackController(ctx, migrationFactory.Migration().V1beta1().OpenstackSource(), coreFactory.Core().V1().Secret()) - sc.RegisterVMImportController(ctx, - migrationFactory.Migration().V1beta1().VmwareSource(), migrationFactory.Migration().V1beta1().OpenstackSource(), - migrationFactory.Migration().V1beta1().OvaSource(), migrationFactory.Migration().V1beta1().KVMSource(), - coreFactory.Core().V1().Secret(), migrationFactory.Migration().V1beta1().VirtualMachineImport(), + sc.RegisterVMImportController(ctx, migrationFactory.Migration().V1beta1().VmwareSource(), migrationFactory.Migration().V1beta1().OpenstackSource(), + migrationFactory.Migration().V1beta1().OvaSource(), coreFactory.Core().V1().Secret(), migrationFactory.Migration().V1beta1().VirtualMachineImport(), harvesterFactory.Harvesterhci().V1beta1().VirtualMachineImage(), kubevirtFactory.Kubevirt().V1().VirtualMachine(), coreFactory.Core().V1().PersistentVolumeClaim(), scCache, cniFactory.K8s().V1().NetworkAttachmentDefinition().Cache()) diff --git a/pkg/controllers/migration/kvm.go b/pkg/controllers/migration/kvm.go deleted file mode 100644 index ecdbadc0..00000000 --- a/pkg/controllers/migration/kvm.go +++ /dev/null @@ -1,113 +0,0 @@ -package migration - -import ( - "context" - "fmt" - "time" - - corecontrollers "github.com/rancher/wrangler/v3/pkg/generated/controllers/core/v1" - "github.com/sirupsen/logrus" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/harvester/vm-import-controller/pkg/apis/common" - migration "github.com/harvester/vm-import-controller/pkg/apis/migration.harvesterhci.io/v1beta1" - migrationController "github.com/harvester/vm-import-controller/pkg/generated/controllers/migration.harvesterhci.io/v1beta1" - "github.com/harvester/vm-import-controller/pkg/source/kvm" - "github.com/harvester/vm-import-controller/pkg/util" -) - -type kvmHandler struct { - ctx context.Context - source migrationController.KVMSourceController - secret corecontrollers.SecretController -} - -func RegisterKVMController(ctx context.Context, source migrationController.KVMSourceController, secret corecontrollers.SecretController) { - kHandler := &kvmHandler{ - ctx: ctx, - source: source, - secret: secret, - } - source.OnChange(ctx, "kvm-source-change", kHandler.OnSourceChange) -} - -func (h *kvmHandler) OnSourceChange(_ string, s *migration.KVMSource) (*migration.KVMSource, error) { - if s == nil || s.DeletionTimestamp != nil { - return nil, nil - } - - logrus.WithFields(logrus.Fields{ - "kind": s.Kind, - "name": s.Name, - "namespace": s.Namespace, - }).Info("Reconciling source") - - if s.Status.Status != migration.ClusterReady { - var client *kvm.Client - var err error - - secretObj, err := h.secret.Get(s.SecretReference().Namespace, s.SecretReference().Name, metav1.GetOptions{}) - if err != nil { - return nil, fmt.Errorf("failed to lookup secret for %s migration %s: %w", s.Kind, s.NamespacedName(), err) - } - - client, err = kvm.NewClient(h.ctx, s.Spec.EndpointAddress, secretObj, s.GetOptions().(migration.KVMSourceOptions)) - if err != nil { - return nil, fmt.Errorf("failed to generate client for %s migration %s: %w", s.Kind, s.NamespacedName(), err) - } - defer client.Close() //nolint:errcheck - - if err := client.Verify(); err != nil { - logrus.WithFields(logrus.Fields{ - "kind": s.Kind, - "name": s.Name, - "namespace": s.Namespace, - "err": err, - }).Error("Failed to verify source for migration") - - conds := []common.Condition{ - { - Type: migration.ClusterErrorCondition, - Status: corev1.ConditionTrue, - LastUpdateTime: metav1.Now().Format(time.RFC3339), - LastTransitionTime: metav1.Now().Format(time.RFC3339), - }, { - Type: migration.ClusterReadyCondition, - Status: corev1.ConditionFalse, - LastUpdateTime: metav1.Now().Format(time.RFC3339), - LastTransitionTime: metav1.Now().Format(time.RFC3339), - }, - } - - s.Status.Conditions = util.MergeConditions(s.Status.Conditions, conds) - s.Status.Status = migration.ClusterNotReady - return h.source.UpdateStatus(s) - } - - // Verify connection (NewClient already dials SSH, but we can run a simple command to be sure) - // We can reuse PreFlightChecks logic or just run a simple command - // But NewClient already dials, so if it succeeds, we are connected. - // Let's just assume ready if NewClient succeeded. - conds := []common.Condition{ - { - Type: migration.ClusterReadyCondition, - Status: corev1.ConditionTrue, - LastUpdateTime: metav1.Now().Format(time.RFC3339), - LastTransitionTime: metav1.Now().Format(time.RFC3339), - }, { - Type: migration.ClusterErrorCondition, - Status: corev1.ConditionFalse, - LastUpdateTime: metav1.Now().Format(time.RFC3339), - LastTransitionTime: metav1.Now().Format(time.RFC3339), - }, - } - - s.Status.Conditions = util.MergeConditions(s.Status.Conditions, conds) - s.Status.Status = migration.ClusterReady - - return h.source.UpdateStatus(s) - } - - return nil, nil -} diff --git a/pkg/controllers/migration/virtualmachine.go b/pkg/controllers/migration/virtualmachine.go index bff7de41..ac375fb0 100644 --- a/pkg/controllers/migration/virtualmachine.go +++ b/pkg/controllers/migration/virtualmachine.go @@ -13,7 +13,6 @@ import ( migration "github.com/harvester/vm-import-controller/pkg/apis/migration.harvesterhci.io/v1beta1" migrationController "github.com/harvester/vm-import-controller/pkg/generated/controllers/migration.harvesterhci.io/v1beta1" "github.com/harvester/vm-import-controller/pkg/server" - "github.com/harvester/vm-import-controller/pkg/source/kvm" "github.com/harvester/vm-import-controller/pkg/source/openstack" "github.com/harvester/vm-import-controller/pkg/source/ova" "github.com/harvester/vm-import-controller/pkg/source/vmware" @@ -79,7 +78,6 @@ type virtualMachineHandler struct { vmware migrationController.VmwareSourceController ova migrationController.OvaSourceController openstack migrationController.OpenstackSourceController - kvm migrationController.KVMSourceController secret coreControllers.SecretController importVM migrationController.VirtualMachineImportController vmi harvester.VirtualMachineImageController @@ -89,17 +87,12 @@ type virtualMachineHandler struct { nadCache ctlcniv1.NetworkAttachmentDefinitionCache } -func RegisterVMImportController(ctx context.Context, - vmware migrationController.VmwareSourceController, openstack migrationController.OpenstackSourceController, - ova migrationController.OvaSourceController, kvm migrationController.KVMSourceController, secret coreControllers.SecretController, - importVM migrationController.VirtualMachineImportController, vmi harvester.VirtualMachineImageController, kubevirt kubevirtv1.VirtualMachineController, - pvc coreControllers.PersistentVolumeClaimController, scCache storageControllers.StorageClassCache, nadCache ctlcniv1.NetworkAttachmentDefinitionCache) { +func RegisterVMImportController(ctx context.Context, vmware migrationController.VmwareSourceController, openstack migrationController.OpenstackSourceController, ova migrationController.OvaSourceController, secret coreControllers.SecretController, importVM migrationController.VirtualMachineImportController, vmi harvester.VirtualMachineImageController, kubevirt kubevirtv1.VirtualMachineController, pvc coreControllers.PersistentVolumeClaimController, scCache storageControllers.StorageClassCache, nadCache ctlcniv1.NetworkAttachmentDefinitionCache) { vmHandler := &virtualMachineHandler{ ctx: ctx, vmware: vmware, openstack: openstack, ova: ova, - kvm: kvm, secret: secret, importVM: importVM, vmi: vmi, @@ -248,7 +241,7 @@ func (h *virtualMachineHandler) preFlightChecks(vm *migration.VirtualMachineImpo var err error switch strings.ToLower(vm.Spec.SourceCluster.Kind) { - case migration.KindVmwareSource, migration.KindOvaSource, migration.KindOpenstackSource, migration.KindKVMSource: + case migration.KindVmwareSource, migration.KindOvaSource, migration.KindOpenstackSource: ss, err = h.generateSource(vm) if err != nil { return fmt.Errorf("error generating migration in preflight checks: %v", err) @@ -567,10 +560,6 @@ func (h *virtualMachineHandler) generateVMO(vm *migration.VirtualMachineImport) endpoint, region := source.GetConnectionInfo() options := source.GetOptions().(migration.OpenstackSourceOptions) return openstack.NewClient(h.ctx, endpoint, region, secret, options) - case migration.KindKVMSource: - endpoint, _ := source.GetConnectionInfo() - options := source.GetOptions().(migration.KVMSourceOptions) - return kvm.NewClient(h.ctx, endpoint, secret, options) } return nil, fmt.Errorf("source kind %q not supported", source.GetKind()) @@ -587,8 +576,6 @@ func (h *virtualMachineHandler) generateSource(vm *migration.VirtualMachineImpor si, err = h.ova.Get(vm.Spec.SourceCluster.Namespace, vm.Spec.SourceCluster.Name, metav1.GetOptions{}) case migration.KindOpenstackSource: si, err = h.openstack.Get(vm.Spec.SourceCluster.Namespace, vm.Spec.SourceCluster.Name, metav1.GetOptions{}) - case migration.KindKVMSource: - si, err = h.kvm.Get(vm.Spec.SourceCluster.Namespace, vm.Spec.SourceCluster.Name, metav1.GetOptions{}) default: err = fmt.Errorf("source kind %q not supported", vm.Spec.SourceCluster.Kind) } diff --git a/pkg/crd/crd.go b/pkg/crd/crd.go index e2404ff8..51a710b7 100644 --- a/pkg/crd/crd.go +++ b/pkg/crd/crd.go @@ -24,10 +24,6 @@ func List() []crd.CRD { return c. WithColumn("Status", ".status.status") }), - newCRD("migration.harvesterhci.io", &migration.KVMSource{}, func(c crd.CRD) crd.CRD { - return c. - WithColumn("Status", ".status.status") - }), newCRD("migration.harvesterhci.io", &migration.VirtualMachineImport{}, func(c crd.CRD) crd.CRD { return c. WithColumn("Status", ".status.importStatus") diff --git a/pkg/generated/controllers/migration.harvesterhci.io/v1beta1/interface.go b/pkg/generated/controllers/migration.harvesterhci.io/v1beta1/interface.go index 1e7af719..83784b33 100644 --- a/pkg/generated/controllers/migration.harvesterhci.io/v1beta1/interface.go +++ b/pkg/generated/controllers/migration.harvesterhci.io/v1beta1/interface.go @@ -31,7 +31,6 @@ func init() { } type Interface interface { - KVMSource() KVMSourceController OpenstackSource() OpenstackSourceController OvaSource() OvaSourceController VirtualMachineImport() VirtualMachineImportController @@ -48,10 +47,6 @@ type version struct { controllerFactory controller.SharedControllerFactory } -func (v *version) KVMSource() KVMSourceController { - return generic.NewController[*v1beta1.KVMSource, *v1beta1.KVMSourceList](schema.GroupVersionKind{Group: "migration.harvesterhci.io", Version: "v1beta1", Kind: "KVMSource"}, "kvmsources", true, v.controllerFactory) -} - func (v *version) OpenstackSource() OpenstackSourceController { return generic.NewController[*v1beta1.OpenstackSource, *v1beta1.OpenstackSourceList](schema.GroupVersionKind{Group: "migration.harvesterhci.io", Version: "v1beta1", Kind: "OpenstackSource"}, "openstacksources", true, v.controllerFactory) } diff --git a/pkg/generated/controllers/migration.harvesterhci.io/v1beta1/kvmsource.go b/pkg/generated/controllers/migration.harvesterhci.io/v1beta1/kvmsource.go deleted file mode 100644 index 1cc8ccd0..00000000 --- a/pkg/generated/controllers/migration.harvesterhci.io/v1beta1/kvmsource.go +++ /dev/null @@ -1,208 +0,0 @@ -/* -Copyright 2026 SUSE, LLC. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -// Code generated by main. DO NOT EDIT. - -package v1beta1 - -import ( - "context" - "sync" - "time" - - v1beta1 "github.com/harvester/vm-import-controller/pkg/apis/migration.harvesterhci.io/v1beta1" - "github.com/rancher/wrangler/v3/pkg/apply" - "github.com/rancher/wrangler/v3/pkg/condition" - "github.com/rancher/wrangler/v3/pkg/generic" - "github.com/rancher/wrangler/v3/pkg/kv" - "k8s.io/apimachinery/pkg/api/equality" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -// KVMSourceController interface for managing KVMSource resources. -type KVMSourceController interface { - generic.ControllerInterface[*v1beta1.KVMSource, *v1beta1.KVMSourceList] -} - -// KVMSourceClient interface for managing KVMSource resources in Kubernetes. -type KVMSourceClient interface { - generic.ClientInterface[*v1beta1.KVMSource, *v1beta1.KVMSourceList] -} - -// KVMSourceCache interface for retrieving KVMSource resources in memory. -type KVMSourceCache interface { - generic.CacheInterface[*v1beta1.KVMSource] -} - -// KVMSourceStatusHandler is executed for every added or modified KVMSource. Should return the new status to be updated -type KVMSourceStatusHandler func(obj *v1beta1.KVMSource, status v1beta1.KVMSourceStatus) (v1beta1.KVMSourceStatus, error) - -// KVMSourceGeneratingHandler is the top-level handler that is executed for every KVMSource event. It extends KVMSourceStatusHandler by a returning a slice of child objects to be passed to apply.Apply -type KVMSourceGeneratingHandler func(obj *v1beta1.KVMSource, status v1beta1.KVMSourceStatus) ([]runtime.Object, v1beta1.KVMSourceStatus, error) - -// RegisterKVMSourceStatusHandler configures a KVMSourceController to execute a KVMSourceStatusHandler for every events observed. -// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution -func RegisterKVMSourceStatusHandler(ctx context.Context, controller KVMSourceController, condition condition.Cond, name string, handler KVMSourceStatusHandler) { - statusHandler := &kVMSourceStatusHandler{ - client: controller, - condition: condition, - handler: handler, - } - controller.AddGenericHandler(ctx, name, generic.FromObjectHandlerToHandler(statusHandler.sync)) -} - -// RegisterKVMSourceGeneratingHandler configures a KVMSourceController to execute a KVMSourceGeneratingHandler for every events observed, passing the returned objects to the provided apply.Apply. -// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution -func RegisterKVMSourceGeneratingHandler(ctx context.Context, controller KVMSourceController, apply apply.Apply, - condition condition.Cond, name string, handler KVMSourceGeneratingHandler, opts *generic.GeneratingHandlerOptions) { - statusHandler := &kVMSourceGeneratingHandler{ - KVMSourceGeneratingHandler: handler, - apply: apply, - name: name, - gvk: controller.GroupVersionKind(), - } - if opts != nil { - statusHandler.opts = *opts - } - controller.OnChange(ctx, name, statusHandler.Remove) - RegisterKVMSourceStatusHandler(ctx, controller, condition, name, statusHandler.Handle) -} - -type kVMSourceStatusHandler struct { - client KVMSourceClient - condition condition.Cond - handler KVMSourceStatusHandler -} - -// sync is executed on every resource addition or modification. Executes the configured handlers and sends the updated status to the Kubernetes API -func (a *kVMSourceStatusHandler) sync(key string, obj *v1beta1.KVMSource) (*v1beta1.KVMSource, error) { - if obj == nil { - return obj, nil - } - - origStatus := obj.Status.DeepCopy() - obj = obj.DeepCopy() - newStatus, err := a.handler(obj, obj.Status) - if err != nil { - // Revert to old status on error - newStatus = *origStatus.DeepCopy() - } - - if a.condition != "" { - if errors.IsConflict(err) { - a.condition.SetError(&newStatus, "", nil) - } else { - a.condition.SetError(&newStatus, "", err) - } - } - if !equality.Semantic.DeepEqual(origStatus, &newStatus) { - if a.condition != "" { - // Since status has changed, update the lastUpdatedTime - a.condition.LastUpdated(&newStatus, time.Now().UTC().Format(time.RFC3339)) - } - - var newErr error - obj.Status = newStatus - newObj, newErr := a.client.UpdateStatus(obj) - if err == nil { - err = newErr - } - if newErr == nil { - obj = newObj - } - } - return obj, err -} - -type kVMSourceGeneratingHandler struct { - KVMSourceGeneratingHandler - apply apply.Apply - opts generic.GeneratingHandlerOptions - gvk schema.GroupVersionKind - name string - seen sync.Map -} - -// Remove handles the observed deletion of a resource, cascade deleting every associated resource previously applied -func (a *kVMSourceGeneratingHandler) Remove(key string, obj *v1beta1.KVMSource) (*v1beta1.KVMSource, error) { - if obj != nil { - return obj, nil - } - - obj = &v1beta1.KVMSource{} - obj.Namespace, obj.Name = kv.RSplit(key, "/") - obj.SetGroupVersionKind(a.gvk) - - if a.opts.UniqueApplyForResourceVersion { - a.seen.Delete(key) - } - - return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts). - WithOwner(obj). - WithSetID(a.name). - ApplyObjects() -} - -// Handle executes the configured KVMSourceGeneratingHandler and pass the resulting objects to apply.Apply, finally returning the new status of the resource -func (a *kVMSourceGeneratingHandler) Handle(obj *v1beta1.KVMSource, status v1beta1.KVMSourceStatus) (v1beta1.KVMSourceStatus, error) { - if !obj.DeletionTimestamp.IsZero() { - return status, nil - } - - objs, newStatus, err := a.KVMSourceGeneratingHandler(obj, status) - if err != nil { - return newStatus, err - } - if !a.isNewResourceVersion(obj) { - return newStatus, nil - } - - err = generic.ConfigureApplyForObject(a.apply, obj, &a.opts). - WithOwner(obj). - WithSetID(a.name). - ApplyObjects(objs...) - if err != nil { - return newStatus, err - } - a.storeResourceVersion(obj) - return newStatus, nil -} - -// isNewResourceVersion detects if a specific resource version was already successfully processed. -// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions -func (a *kVMSourceGeneratingHandler) isNewResourceVersion(obj *v1beta1.KVMSource) bool { - if !a.opts.UniqueApplyForResourceVersion { - return true - } - - // Apply once per resource version - key := obj.Namespace + "/" + obj.Name - previous, ok := a.seen.Load(key) - return !ok || previous != obj.ResourceVersion -} - -// storeResourceVersion keeps track of the latest resource version of an object for which Apply was executed -// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions -func (a *kVMSourceGeneratingHandler) storeResourceVersion(obj *v1beta1.KVMSource) { - if !a.opts.UniqueApplyForResourceVersion { - return - } - - key := obj.Namespace + "/" + obj.Name - a.seen.Store(key, obj.ResourceVersion) -} diff --git a/pkg/qemu/gemu_test.go b/pkg/qemu/gemu_test.go index 82ecd566..63fc6ab8 100644 --- a/pkg/qemu/gemu_test.go +++ b/pkg/qemu/gemu_test.go @@ -17,7 +17,7 @@ func Test_ConvertVMDKToRaw(t *testing.T) { err = createVMDK(tmpVMDK, "512M") assert.NoError(err, "expected no error during tmp vmdk creation") destRaw := filepath.Join(tmpDir, "vmdktest.img") - err = ConvertToRAW(tmpVMDK, destRaw, "vmdk") + err = ConvertVMDKtoRAW(tmpVMDK, destRaw) assert.NoError(err, "expected no error during disk conversion") f, err := os.Stat(destRaw) assert.NoError(err, "expected no error during check for raw file") diff --git a/pkg/qemu/qemu.go b/pkg/qemu/qemu.go index d5cc4086..ee47ee6d 100644 --- a/pkg/qemu/qemu.go +++ b/pkg/qemu/qemu.go @@ -4,19 +4,19 @@ import ( "fmt" "io" "os/exec" + "syscall" "github.com/sirupsen/logrus" ) const defaultCommand = "qemu-wrapper.sh" -func ConvertToRAW(source, target string, format string) error { +func ConvertVMDKtoRAW(source, target string) error { logrus.WithFields(logrus.Fields{ "source": source, "target": target, - "format": format, - }).Info("Converting VMDK image ...") - args := []string{"convert", "-f", format, "-O", "raw", source, target} + }).Info("Converting VMDK image to RAW ...") + args := []string{"convert", "-f", "vmdk", "-O", "raw", source, target} return runCommand(defaultCommand, args...) } @@ -27,7 +27,9 @@ func createVMDK(path string, size string) error { func runCommand(command string, args ...string) error { cmd := exec.Command(command, args...) + cmd.SysProcAttr = &syscall.SysProcAttr{} stderr, err := cmd.StderrPipe() + if err != nil { return fmt.Errorf("error creating stderr pipe: %v", err) } diff --git a/pkg/source/helper.go b/pkg/source/helper.go index 1ea06c05..a8e78bec 100644 --- a/pkg/source/helper.go +++ b/pkg/source/helper.go @@ -31,11 +31,10 @@ type Hardware struct { NumCPU uint32 // The type is adapted to KubeVirt CPU NumCoresPerSocket uint32 // The type is adapted to KubeVirt CPU MemoryMB int64 - CPUModel string } -func NewHardware(numCPU, numCoresPerSocket uint32, memoryMB int64, cpuModel string) *Hardware { - return &Hardware{NumCPU: numCPU, NumCoresPerSocket: numCoresPerSocket, MemoryMB: memoryMB, CPUModel: cpuModel} +func NewHardware(numCPU, numCoresPerSocket uint32, memoryMB int64) *Hardware { + return &Hardware{NumCPU: numCPU, NumCoresPerSocket: numCoresPerSocket, MemoryMB: memoryMB} } type VirtualMachineSpecConfig struct { @@ -59,7 +58,6 @@ func NewVirtualMachineSpec(cfg VirtualMachineSpecConfig) *kubevirtv1.VirtualMach Cores: cfg.Hardware.NumCPU, Sockets: cfg.Hardware.NumCoresPerSocket, Threads: 1, - Model: cfg.Hardware.CPUModel, }, Memory: &kubevirtv1.Memory{ Guest: ptr.To(resource.MustParse(fmt.Sprintf("%dM", cfg.Hardware.MemoryMB))), @@ -128,9 +126,3 @@ func RemoveTempImageFiles(dis []migration.DiskInfo) error { return nil } - -// GenerateRawImageFileName Generate the raw image file name based on the VM name and -// index of the attached volume. -func GenerateRawImageFileName(vmName string, index int) string { - return fmt.Sprintf("%s-%d.img", vmName, index) -} diff --git a/pkg/source/helper_test.go b/pkg/source/helper_test.go index 8dd94bb5..05ed2c11 100644 --- a/pkg/source/helper_test.go +++ b/pkg/source/helper_test.go @@ -77,7 +77,7 @@ func Test_NewVirtualMachineSpec(t *testing.T) { desc: "Basic configuration", config: VirtualMachineSpecConfig{ Name: "basic-vm", - Hardware: *NewHardware(4, 2, 8192, ""), + Hardware: *NewHardware(4, 2, 8192), }, expectedCPUCores: 4, expectedCPUSockets: 2, @@ -87,7 +87,7 @@ func Test_NewVirtualMachineSpec(t *testing.T) { desc: "High CPU and memory configuration", config: VirtualMachineSpecConfig{ Name: "high-performance-vm", - Hardware: *NewHardware(64, 32, 65536, ""), + Hardware: *NewHardware(64, 32, 65536), }, expectedCPUCores: 64, expectedCPUSockets: 32, @@ -97,7 +97,7 @@ func Test_NewVirtualMachineSpec(t *testing.T) { desc: "Minimal hardware configuration", config: VirtualMachineSpecConfig{ Name: "minimal-vm", - Hardware: *NewHardware(1, 1, 512, ""), + Hardware: *NewHardware(1, 1, 512), }, expectedCPUCores: 1, expectedCPUSockets: 1, diff --git a/pkg/source/kvm/client.go b/pkg/source/kvm/client.go deleted file mode 100644 index 617007ed..00000000 --- a/pkg/source/kvm/client.go +++ /dev/null @@ -1,382 +0,0 @@ -package kvm - -import ( - "bytes" - "context" - "fmt" - "io" - "net/url" - "os" - "os/exec" - "path/filepath" - "strings" - - "github.com/pkg/sftp" - "github.com/sirupsen/logrus" - "golang.org/x/crypto/ssh" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - kubevirt "kubevirt.io/api/core/v1" - libvirtxml "libvirt.org/go/libvirtxml" - - migration "github.com/harvester/vm-import-controller/pkg/apis/migration.harvesterhci.io/v1beta1" - "github.com/harvester/vm-import-controller/pkg/qemu" - "github.com/harvester/vm-import-controller/pkg/server" - "github.com/harvester/vm-import-controller/pkg/source" -) - -type Client struct { - ctx context.Context - options migration.KVMSourceOptions - sshClient *ssh.Client -} - -func NewClient(ctx context.Context, endpoint string, secret *corev1.Secret, options migration.KVMSourceOptions) (*Client, error) { - endpointURL, err := url.Parse(endpoint) - if err != nil { - return nil, fmt.Errorf("error parsing endpoint URL: %w", err) - } - - host := endpointURL.Host - user := endpointURL.User.Username() - if user == "" { - if secretUser, ok := secret.Data["username"]; ok { - user = string(secretUser) - } else { - return nil, fmt.Errorf("username not found in endpoint URL or secret") - } - } - - authMethods := []ssh.AuthMethod{} - if privateKey, ok := secret.Data["privateKey"]; ok { - signer, err := ssh.ParsePrivateKey(privateKey) - if err != nil { - return nil, fmt.Errorf("failed to parse private key: %w", err) - } - authMethods = append(authMethods, ssh.PublicKeys(signer)) - } - if password, ok := secret.Data["password"]; ok { - authMethods = append(authMethods, ssh.Password(string(password))) - } - - if len(authMethods) == 0 { - return nil, fmt.Errorf("no authentication methods provided in secret") - } - - sshClientConfig := &ssh.ClientConfig{ - User: user, - Auth: authMethods, - HostKeyCallback: ssh.InsecureIgnoreHostKey(), // nolint:gosec - Timeout: options.GetSSHTimeout(), - } - - if !strings.Contains(host, ":") { - host = fmt.Sprintf("%s:22", host) - } - - logrus.WithFields(logrus.Fields{ - "host": host, - "user": user, - "timeout": options.GetSSHTimeout().String(), - }).Info("Dialing endpoint ...") - sshClient, err := ssh.Dial("tcp", host, sshClientConfig) - if err != nil { - return nil, fmt.Errorf("failed to dial endpoint: %w", err) - } - - return &Client{ - ctx: ctx, - options: options, - sshClient: sshClient, - }, nil -} - -func (c *Client) Close() error { - if c.sshClient != nil { - return c.sshClient.Close() - } - return nil -} - -func (c *Client) Verify() error { - // We run a simple command to verify that the connection is working, and we can talk to libvirt. - // "virsh list --name" is lightweight and lists running domains. - _, err := c.runCommand([]string{"list", "--name"}) - if err != nil { - return fmt.Errorf("failed to verify connection to %s: %w", c.sshClient.RemoteAddr().String(), err) - } - logrus.Infof("Connection verified to %s", c.sshClient.RemoteAddr().String()) - return nil -} - -func (c *Client) runCommand(args []string) (string, error) { - session, err := c.sshClient.NewSession() - if err != nil { - return "", err - } - defer session.Close() - - var stdout, stderr bytes.Buffer - session.Stdout = &stdout - session.Stderr = &stderr - - uri := c.options.GetVirshConnectionURI() - if uri != "" { - args = append([]string{"-c", uri}, args...) - } - - cmd := exec.Command("virsh", args...) - cmdLine := cmd.String() - - err = session.Run(cmdLine) - if err != nil { - return "", fmt.Errorf("command %q failed: %v, stderr: %s", cmdLine, err, stderr.String()) - } - - return stdout.String(), nil -} - -func (c *Client) getDomainXML(vmName string) (*libvirtxml.Domain, error) { - out, err := c.runCommand([]string{"dumpxml", vmName}) - if err != nil { - return nil, err - } - - var dom libvirtxml.Domain - if err := dom.Unmarshal(out); err != nil { - return nil, fmt.Errorf("failed to unmarshal domain xml: %w", err) - } - return &dom, nil -} - -func (c *Client) SanitizeVirtualMachineImport(vm *migration.VirtualMachineImport) error { - vm.Status.ImportedVirtualMachineName = strings.Split(strings.ToLower(vm.Spec.VirtualMachineName), ".")[0] - return nil -} - -func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) error { - dom, err := c.getDomainXML(vm.Spec.VirtualMachineName) - if err != nil { - return err - } - - sftpClient, err := sftp.NewClient(c.sshClient) - if err != nil { - return fmt.Errorf("failed to create sftp client: %w", err) - } - defer sftpClient.Close() - - if dom.Devices == nil { - return fmt.Errorf("no devices found in domain XML") - } - - for i, disk := range dom.Devices.Disks { - if disk.Device != "disk" { - continue - } - var sourceFile string - if disk.Source != nil { - if disk.Source.File != nil { - sourceFile = disk.Source.File.File - } else if disk.Source.Block != nil { - sourceFile = disk.Source.Block.Dev - } - } - - if sourceFile == "" { - continue - } - - // Create a temporary file to store the downloaded disk - tmpFile, err := os.CreateTemp("", fmt.Sprintf("%s-disk-%d-", vm.Name, i)) - if err != nil { - return fmt.Errorf("failed to create temporary file for download: %w", err) - } - defer os.Remove(tmpFile.Name()) - - logrus.Infof("Downloading disk %s to %s", sourceFile, tmpFile.Name()) - - // Open the remote file - remoteFile, err := sftpClient.Open(sourceFile) - if err != nil { - return fmt.Errorf("failed to open remote file %s: %w", sourceFile, err) - } - defer remoteFile.Close() - - // Copy the remote file to the temporary local file - if _, err := io.Copy(tmpFile, remoteFile); err != nil { - return fmt.Errorf("failed to download remote file: %w", err) - } - tmpFile.Close() // Close the file so qemu-img can access it - - // Local destination path for the converted RAW image - rawDiskName := source.GenerateRawImageFileName(vm.Name, i) - destFile := filepath.Join(server.TempDir(), rawDiskName) - - logrus.Infof("Converting downloaded disk %s to %s", tmpFile.Name(), destFile) - - // Use qemu-img convert on the local, downloaded file - format := "qcow2" // Default assumption - if disk.Driver != nil && disk.Driver.Type != "" { - format = disk.Driver.Type - } - - if err := qemu.ConvertToRAW(tmpFile.Name(), destFile, format); err != nil { - return fmt.Errorf("qemu convert failed: %w", err) - } - - // Update status - busType := kubevirt.DiskBusVirtio - if disk.Target != nil { - switch disk.Target.Bus { - case "sata", "ide": - busType = kubevirt.DiskBusSATA - case "scsi": - busType = kubevirt.DiskBusSCSI - } - } - - // Get the size of the converted image - destFileInfo, err := os.Stat(destFile) - if err != nil { - return fmt.Errorf("failed to get stats for converted disk: %w", err) - } - - vm.Status.DiskImportStatus = append(vm.Status.DiskImportStatus, migration.DiskInfo{ - Name: rawDiskName, - DiskSize: destFileInfo.Size(), - BusType: busType, - DiskLocalPath: server.TempDir(), - }) - } - - return nil -} - -func (c *Client) ShutdownGuest(vm *migration.VirtualMachineImport) error { - powerOff, err := c.IsPoweredOff(vm) - if err != nil { - return err - } - if powerOff { - logrus.Infof("VM %s is already powered off, skipping shutdown.", vm.Spec.VirtualMachineName) - return nil - } - _, err = c.runCommand([]string{"shutdown", vm.Spec.VirtualMachineName}) - return err -} - -func (c *Client) PowerOff(vm *migration.VirtualMachineImport) error { - _, err := c.runCommand([]string{"destroy", vm.Spec.VirtualMachineName}) - return err -} - -func (c *Client) IsPowerOffSupported() bool { - return true -} - -func (c *Client) IsPoweredOff(vm *migration.VirtualMachineImport) (bool, error) { - out, err := c.runCommand([]string{"domstate", vm.Spec.VirtualMachineName}) - if err != nil { - return false, err - } - return strings.TrimSpace(out) == "shut off", nil -} - -func (c *Client) GenerateVirtualMachine(vm *migration.VirtualMachineImport) (*kubevirt.VirtualMachine, error) { - dom, err := c.getDomainXML(vm.Spec.VirtualMachineName) - if err != nil { - return nil, err - } - - newVM := &kubevirt.VirtualMachine{ - ObjectMeta: metav1.ObjectMeta{ - Name: vm.Status.ImportedVirtualMachineName, - Namespace: vm.Namespace, - }, - } - - cpuModel := "" - if dom.CPU != nil && dom.CPU.Model != nil { - cpuModel = dom.CPU.Model.Value - } - - // Firmware settings - fw := source.NewFirmware(false, false, false) - if dom.OS != nil && dom.OS.Loader != nil { - fw.UEFI = true // The presence of a loader usually indicates UEFI - } - if dom.Devices != nil && len(dom.Devices.TPMs) > 0 { - fw.TPM = true - } - - vmSpec := source.NewVirtualMachineSpec(source.VirtualMachineSpecConfig{ - Name: vm.Status.ImportedVirtualMachineName, - Hardware: source.Hardware{ - NumCPU: uint32(dom.VCPU.Value), // nolint:gosec - MemoryMB: int64(dom.Memory.Value / 1024), // nolint:gosec // XML memory is usually in KiB - CPUModel: cpuModel, - }, - }) - - // Network mapping - var networkInfos []source.NetworkInfo - if dom.Devices != nil { - for _, iface := range dom.Devices.Interfaces { - model := migration.NetworkInterfaceModelVirtio - if iface.Model != nil { - switch iface.Model.Type { - case "e1000": - model = migration.NetworkInterfaceModelE1000 - case "e1000e": - model = migration.NetworkInterfaceModelE1000e - } - } - - networkName := "" - if iface.Source != nil { - if iface.Source.Network != nil { - networkName = iface.Source.Network.Network - } else if iface.Source.Bridge != nil { - networkName = iface.Source.Bridge.Bridge - } - } - - macAddr := "" - if iface.MAC != nil { - macAddr = iface.MAC.Address - } - - networkInfos = append(networkInfos, source.NetworkInfo{ - NetworkName: networkName, - MAC: macAddr, - Model: model, - }) - } - } - - mappedNetwork := source.MapNetworks(networkInfos, vm.Spec.Mapping) - networkConfig, interfaceConfig := source.GenerateNetworkInterfaceConfigs(mappedNetwork, vm.GetDefaultNetworkInterfaceModel()) - - // Apply firmware settings - source.ApplyFirmwareSettings(vmSpec, fw) - - vmSpec.Template.Spec.Networks = networkConfig - vmSpec.Template.Spec.Domain.Devices.Interfaces = interfaceConfig - newVM.Spec = *vmSpec - - return newVM, nil -} - -func (c *Client) PreFlightChecks(vm *migration.VirtualMachineImport) error { - // Check if VM exists - _, err := c.runCommand([]string{"dominfo", vm.Spec.VirtualMachineName}) - if err != nil { - return fmt.Errorf("failed to find VM %s: %w", vm.Spec.VirtualMachineName, err) - } - return nil -} - -func (c *Client) Cleanup(vm *migration.VirtualMachineImport) error { - return source.RemoveTempImageFiles(vm.Status.DiskImportStatus) -} diff --git a/pkg/source/kvm/client_test.go b/pkg/source/kvm/client_test.go deleted file mode 100644 index 944a5f56..00000000 --- a/pkg/source/kvm/client_test.go +++ /dev/null @@ -1,74 +0,0 @@ -package kvm - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - corev1 "k8s.io/api/core/v1" - libvirtxml "libvirt.org/go/libvirtxml" - - migration "github.com/harvester/vm-import-controller/pkg/apis/migration.harvesterhci.io/v1beta1" -) - -func TestParseDomainXML(t *testing.T) { - xmlData := ` - - test-vm - a75aca4b-42f6-4447-9262-4b9562d3d95c - 4194304 - 2 - - hvm - - - - IvyBridge-IBRS - - - - - - - - - - - - - - -` - - var dom libvirtxml.Domain - err := dom.Unmarshal(xmlData) - assert.NoError(t, err) - - assert.Equal(t, "test-vm", dom.Name) - assert.Equal(t, "a75aca4b-42f6-4447-9262-4b9562d3d95c", dom.UUID) - assert.Equal(t, uint(4194304), dom.Memory.Value) - assert.Equal(t, uint(2), dom.VCPU.Value) - assert.Len(t, dom.Devices.Disks, 1) - assert.Equal(t, "/var/lib/libvirt/images/test-vm.qcow2", dom.Devices.Disks[0].Source.File.File) - assert.Equal(t, "qcow2", dom.Devices.Disks[0].Driver.Type) - assert.Len(t, dom.Devices.Interfaces, 1) - assert.Equal(t, "52:54:00:6b:3c:58", dom.Devices.Interfaces[0].MAC.Address) - assert.Equal(t, "default", dom.Devices.Interfaces[0].Source.Network.Network) - assert.Equal(t, "IvyBridge-IBRS", dom.CPU.Model.Value) -} - -func TestNewClient(t *testing.T) { - ctx := context.TODO() - secret := &corev1.Secret{ - Data: map[string][]byte{ - "username": []byte("user"), - "password": []byte("pass"), - }, - } - - // Test with a dummy URI. Since we are using standard ssh.Dial, - // checking valid URI parsing is still useful, even if Dial fails. - _, err := NewClient(ctx, "ssh://user@localhost:2222", secret, migration.KVMSourceOptions{}) - assert.Error(t, err) - assert.Contains(t, err.Error(), "failed to dial endpoint") -} diff --git a/pkg/source/openstack/client.go b/pkg/source/openstack/client.go index 7aed3925..7b0fe07e 100644 --- a/pkg/source/openstack/client.go +++ b/pkg/source/openstack/client.go @@ -440,7 +440,7 @@ func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) error return fmt.Errorf("error downloading image %s: %w", volumeImage.ImageID, err) } - rawImageFileName := source.GenerateRawImageFileName(vm.Status.ImportedVirtualMachineName, index) + rawImageFileName := generateRawImageFileName(vm.Status.ImportedVirtualMachineName, index) logrus.WithFields(logrus.Fields{ "name": vm.Name, @@ -539,7 +539,7 @@ func (c *Client) GenerateVirtualMachine(vm *migration.VirtualMachineImport) (*ku fw, err := c.getFirmwareSettings(&vmObj.Server) if err != nil { - return nil, fmt.Errorf("error getting firmware settings: %w", err) + return nil, fmt.Errorf("error getting firware settings: %w", err) } networkInfos, err := generateNetworkInfos(vmObj.Addresses, vm.GetDefaultNetworkInterfaceModel()) @@ -823,3 +823,8 @@ func writeRawImageFile(name string, src io.ReadCloser) error { _, err = io.Copy(dst, src) return err } + +// generateRawImageFileName Generate the raw image file name based on the VM name and index of the attached volume. +func generateRawImageFileName(vmName string, index int) string { + return fmt.Sprintf("%s-%d.img", vmName, index) +} diff --git a/pkg/source/ova/client.go b/pkg/source/ova/client.go index 3a208d15..f6780314 100644 --- a/pkg/source/ova/client.go +++ b/pkg/source/ova/client.go @@ -414,7 +414,7 @@ func (c *Client) extractAndConvertVMDKToRAW(archivePath, name, dstPath string, c } if convert { - err = qemu.ConvertToRAW(vmdkFile.Name(), dstPath, "vmdk") + err = qemu.ConvertVMDKtoRAW(vmdkFile.Name(), dstPath) if err != nil { return fmt.Errorf("failed to convert VMDK file %q to RAW %q: %w", vmdkFile.Name(), dstPath, err) } @@ -561,7 +561,7 @@ func detectDiskBusType(rasd *ovf.ResourceAllocationSettingData) (kubevirtv1.Disk // parseEnvelope retrieves the firmware, virtual hardware and network settings from the OVF envelope. func parseEnvelope(e *ovf.Envelope, defaultInterfaceModel string, defaultDiskBusType kubevirtv1.DiskBus) (*source.Firmware, *source.Hardware, []source.NetworkInfo, []migration.DiskInfo) { fw := source.NewFirmware(false, false, false) - hw := source.NewHardware(0, 0, 0, "") + hw := source.NewHardware(0, 0, 0) nis := make([]source.NetworkInfo, 0) dis := make([]migration.DiskInfo, 0) diff --git a/pkg/source/vmware/client.go b/pkg/source/vmware/client.go index b4ee5413..ec270234 100644 --- a/pkg/source/vmware/client.go +++ b/pkg/source/vmware/client.go @@ -57,7 +57,7 @@ func NewClient(ctx context.Context, endpoint string, dc string, secret *corev1.S endpointURL, err := url.Parse(endpoint) if err != nil { - return nil, fmt.Errorf("error parsing endpoint URL: %v", err) + return nil, fmt.Errorf("error parsing endpoint url: %v", err) } sc := soap.NewClient(endpointURL, insecure) @@ -292,7 +292,7 @@ func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) (err e rawDiskName := util.BaseName(d.Name) + ".img" destFile := filepath.Join(server.TempDir(), rawDiskName) - err = qemu.ConvertToRAW(sourceFile, destFile, "vmdk") + err = qemu.ConvertVMDKtoRAW(sourceFile, destFile) if err != nil { return fmt.Errorf("error during conversion of VMDK to RAW disk: %v", err) } diff --git a/tests/integration/kvm_test.go b/tests/integration/kvm_test.go deleted file mode 100644 index 90d68817..00000000 --- a/tests/integration/kvm_test.go +++ /dev/null @@ -1,181 +0,0 @@ -package integration - -import ( - "fmt" - - harvesterv1beta1 "github.com/harvester/harvester/pkg/apis/harvesterhci.io/v1beta1" - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" - kubevirt "kubevirt.io/api/core/v1" - - migration "github.com/harvester/vm-import-controller/pkg/apis/migration.harvesterhci.io/v1beta1" - "github.com/harvester/vm-import-controller/pkg/util" - "github.com/harvester/vm-import-controller/tests/setup" -) - -var _ = Describe("test kvm export/import integration", func() { - BeforeEach(func() { - if !useExisting { - return - } - err := setup.SetupKVM(ctx, k8sClient) - Expect(err).ToNot(HaveOccurred()) - }) - - It("reconcile object status", func() { - if !useExisting { - Skip("skipping kvm integration tests as not using an existing environment") - } - - By("checking if kvm migration is ready", func() { - Eventually(func() error { - v := &migration.KVMSource{} - err := k8sClient.Get(ctx, setup.KVMSourceNamespacedName, v) - if err != nil { - return err - } - if v.Status.Status != migration.ClusterReady { - return fmt.Errorf("waiting for cluster migration to be ready. current condition is %s", v.Status.Status) - } - - return nil - }, "30s", "10s").ShouldNot(HaveOccurred()) - }) - - By("vm importjob has the correct conditions", func() { - Eventually(func() error { - v := &migration.VirtualMachineImport{} - err := k8sClient.Get(ctx, setup.KVMVMNamespacedName, v) - if err != nil { - return err - } - if !util.ConditionExists(v.Status.ImportConditions, migration.VirtualMachinePoweringOff, v1.ConditionTrue) { - return fmt.Errorf("expected virtualmachinepoweringoff condition to be present") - } - - if !util.ConditionExists(v.Status.ImportConditions, migration.VirtualMachinePoweredOff, v1.ConditionTrue) { - return fmt.Errorf("expected virtualmachinepoweredoff condition to be present") - } - - if !util.ConditionExists(v.Status.ImportConditions, migration.VirtualMachineExported, v1.ConditionTrue) { - return fmt.Errorf("expected virtualmachineexported condition to be present") - } - - return nil - }, "300s", "10s").ShouldNot(HaveOccurred()) - }) - - By("checking status of virtualmachineimage objects", func() { - Eventually(func() error { - v := &migration.VirtualMachineImport{} - err := k8sClient.Get(ctx, setup.KVMVMNamespacedName, v) - if err != nil { - return err - } - - if len(v.Status.DiskImportStatus) == 0 { - return fmt.Errorf("waiting for DiskImportStatus to be populated") - } - for _, d := range v.Status.DiskImportStatus { - if d.VirtualMachineImage == "" { - return fmt.Errorf("waiting for VMI to be populated") - } - vmi := &harvesterv1beta1.VirtualMachineImage{} - err := k8sClient.Get(ctx, types.NamespacedName{Namespace: setup.KVMVMNamespacedName.Namespace, - Name: d.VirtualMachineImage}, vmi) - if err != nil { - return err - } - - if vmi.Status.Progress != 100 { - return fmt.Errorf("vmi %s not yet ready", vmi.Name) - } - } - return nil - }, "300s", "10s").ShouldNot(HaveOccurred()) - }) - - By("checking that PVC claim has been created", func() { - Eventually(func() error { - v := &migration.VirtualMachineImport{} - err := k8sClient.Get(ctx, setup.KVMVMNamespacedName, v) - if err != nil { - return err - } - if len(v.Status.DiskImportStatus) == 0 { - return fmt.Errorf("diskimportstatus should have image details available") - } - for _, d := range v.Status.DiskImportStatus { - if d.VirtualMachineImage == "" { - return fmt.Errorf("waiting for VMI to be populated") - } - pvc := &v1.PersistentVolumeClaim{} - err := k8sClient.Get(ctx, types.NamespacedName{Namespace: setup.KVMVMNamespacedName.Namespace, - Name: d.VirtualMachineImage}, pvc) - if err != nil { - return err - } - - if pvc.Status.Phase != v1.ClaimBound { - return fmt.Errorf("waiting for pvc claim to be in state bound") - } - } - - return nil - }, "120s", "10s").ShouldNot(HaveOccurred()) - }) - - By("checking that the virtualmachine has been created", func() { - Eventually(func() error { - v := &migration.VirtualMachineImport{} - err := k8sClient.Get(ctx, setup.KVMVMNamespacedName, v) - if err != nil { - return err - } - - vm := &kubevirt.VirtualMachine{} - err = k8sClient.Get(ctx, types.NamespacedName{ - Namespace: setup.KVMVMNamespacedName.Namespace, - Name: v.Spec.VirtualMachineName, - }, vm) - - return err - }, "300s", "10s").ShouldNot(HaveOccurred()) - }) - - // can take upto 5 mins for the VM to be marked as running - By("checking that the virtualmachineimage ownership has been removed", func() { - Eventually(func() error { - v := &migration.VirtualMachineImport{} - err := k8sClient.Get(ctx, setup.KVMVMNamespacedName, v) - if err != nil { - return err - } - - for _, d := range v.Status.DiskImportStatus { - vmi := &harvesterv1beta1.VirtualMachineImage{} - err := k8sClient.Get(ctx, types.NamespacedName{Namespace: setup.KVMVMNamespacedName.Namespace, - Name: d.VirtualMachineImage}, vmi) - if err != nil { - return err - } - - if len(vmi.OwnerReferences) != 0 { - return fmt.Errorf("waiting for ownerRef to be cleared") - } - } - - return nil - }, "600s", "30s").ShouldNot(HaveOccurred()) - }) - - }) - - AfterEach(func() { - if !useExisting { - return - } - err := setup.CleanupKVM(ctx, k8sClient) - Expect(err).ToNot(HaveOccurred()) - }) -}) diff --git a/tests/integration/suite_test.go b/tests/integration/suite_test.go index cf9321a9..1e0bb996 100644 --- a/tests/integration/suite_test.go +++ b/tests/integration/suite_test.go @@ -132,22 +132,20 @@ var _ = BeforeSuite(func() { return eg.Wait() }) - if os.Getenv("SKIP_VCSIM") != "true" { - pool, err = dockertest.NewPool("") - Expect(err).NotTo(HaveOccurred()) - runOpts := &dockertest.RunOptions{ - Name: "vcsim-integration", - Repository: "vmware/vcsim", - Tag: "v0.29.0", - } + pool, err = dockertest.NewPool("") + Expect(err).NotTo(HaveOccurred()) + runOpts := &dockertest.RunOptions{ + Name: "vcsim-integration", + Repository: "vmware/vcsim", + Tag: "v0.29.0", + } - vcsimMock, err = pool.RunWithOptions(runOpts) - Expect(err).NotTo(HaveOccurred()) + vcsimMock, err = pool.RunWithOptions(runOpts) + Expect(err).NotTo(HaveOccurred()) - vcsimPort = vcsimMock.GetPort("8989/tcp") + vcsimPort = vcsimMock.GetPort("8989/tcp") - time.Sleep(30 * time.Second) - } + time.Sleep(30 * time.Second) }) diff --git a/tests/setup/setup_kvm.go b/tests/setup/setup_kvm.go deleted file mode 100644 index 8614c889..00000000 --- a/tests/setup/setup_kvm.go +++ /dev/null @@ -1,188 +0,0 @@ -package setup - -import ( - "context" - "fmt" - "os" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - - migration "github.com/harvester/vm-import-controller/pkg/apis/migration.harvesterhci.io/v1beta1" -) - -const ( - kvmSecret = "kvm-integration" - kvmSourceCluster = "kvm-integration" - kvmVirtualMachine = "kvm-export-test" - kvmDefaultNamespace = "default" - kvmDefaultKind = "KVMSource" - kvmDefaultAPIVersion = "migration.harvesterhci.io/v1beta1" -) - -var ( - KVMSourceNamespacedName types.NamespacedName - KVMVMNamespacedName types.NamespacedName -) - -// SetupKVM will try and set up a KVM migration based on environment variables. -// It will check the following environment variables to build migration and importjob CRD's -// KVM_LIBVIRT_URI: Identify libvirt endpoint (e.g., qemu+ssh://user@host/system) -// KVM_SSH_USER: Username for migration secret -// KVM_SSH_PASSWORD: Password for migration secret -// SVC_ADDRESS: local machine address, used to generate the URL that Harvester downloads the exported images from -// VM_NAME: name of VM to be exported -func SetupKVM(ctx context.Context, k8sClient client.Client) error { - KVMSourceNamespacedName = types.NamespacedName{ - Name: kvmSourceCluster, - Namespace: kvmDefaultNamespace, - } - - KVMVMNamespacedName = types.NamespacedName{ - Name: kvmVirtualMachine, - Namespace: kvmDefaultNamespace, - } - - fnList := []applyObject{ - setupKVMSecret, - setupKVMSource, - setupKVMVMExport, - } - - for _, v := range fnList { - if err := v(ctx, k8sClient); err != nil { - return err - } - } - - return nil -} - -func setupKVMSecret(ctx context.Context, k8sClient client.Client) error { - username, ok := os.LookupEnv("KVM_SSH_USER") - if !ok { - return fmt.Errorf("env variable KVM_SSH_USER not set") - } - - stringData := map[string]string{ - "username": username, - } - - password, hasPassword := os.LookupEnv("KVM_SSH_PASSWORD") - if hasPassword { - stringData["password"] = password - } - - keyPath, hasKey := os.LookupEnv("KVM_SSH_PRIVATE_KEY_PATH") - if hasKey { - keyContent, err := os.ReadFile(keyPath) - if err != nil { - return fmt.Errorf("failed to read private key file %s: %v", keyPath, err) - } - stringData["privateKey"] = string(keyContent) - } - - if !hasPassword && !hasKey { - return fmt.Errorf("neither KVM_SSH_PASSWORD nor KVM_SSH_PRIVATE_KEY_PATH set") - } - - s := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: kvmSecret, - Namespace: kvmDefaultNamespace, - }, - StringData: stringData, - } - - return k8sClient.Create(ctx, s) -} - -func setupKVMSource(ctx context.Context, k8sClient client.Client) error { - endpoint, ok := os.LookupEnv("KVM_URL") - if !ok { - return fmt.Errorf("env variable KVM_URL not set") - } - - s := &migration.KVMSource{ - ObjectMeta: metav1.ObjectMeta{ - Name: kvmSourceCluster, - Namespace: kvmDefaultNamespace, - }, - Spec: migration.KVMSourceSpec{ - EndpointAddress: endpoint, - Credentials: corev1.SecretReference{ - Name: kvmSecret, - Namespace: kvmDefaultNamespace, - }, - }, - } - - return k8sClient.Create(ctx, s) - -} - -func setupKVMVMExport(ctx context.Context, k8sClient client.Client) error { - vm, ok := os.LookupEnv("VM_NAME") - if !ok { - return fmt.Errorf("env variable VM_NAME not specified") - } - - _, ok = os.LookupEnv("SVC_ADDRESS") - if !ok { - return fmt.Errorf("env variable SVC_ADDRESS not specified") - } - - j := &migration.VirtualMachineImport{ - ObjectMeta: metav1.ObjectMeta{ - Name: kvmVirtualMachine, - Namespace: kvmDefaultNamespace, - }, - Spec: migration.VirtualMachineImportSpec{ - SourceCluster: corev1.ObjectReference{ - Name: kvmSourceCluster, - Namespace: kvmDefaultNamespace, - Kind: kvmDefaultKind, - APIVersion: kvmDefaultAPIVersion, - }, - VirtualMachineName: vm, - }, - } - - return k8sClient.Create(ctx, j) -} - -func CleanupKVM(ctx context.Context, k8sClient client.Client) error { - s := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: kvmSecret, - Namespace: kvmDefaultNamespace, - }, - } - err := k8sClient.Delete(ctx, s) - if err != nil { - return err - } - - kvm := &migration.KVMSource{ - ObjectMeta: metav1.ObjectMeta{ - Name: kvmSourceCluster, - Namespace: kvmDefaultNamespace, - }, - } - - err = k8sClient.Delete(ctx, kvm) - if err != nil { - return err - } - - i := &migration.VirtualMachineImport{ - ObjectMeta: metav1.ObjectMeta{ - Name: kvmVirtualMachine, - Namespace: kvmDefaultNamespace, - }, - } - - return k8sClient.Delete(ctx, i) -}