Validate the network mapping configuration (#46)

- A new `apiGroups` is added to the `ClusterRole` to be able to list `network-attachment-definitions`.
- The `network-attachment-definitions` CRD has to be generated in the test env.

Related to: https://github.com/harvester/harvester/issues/6491

Signed-off-by: Volker Theile <vtheile@suse.com>
This commit is contained in:
Volker Theile 2024-10-09 11:39:48 +02:00 committed by GitHub
parent 0eff95eb0b
commit ea161c2fc1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 192 additions and 17 deletions

View File

@ -26,7 +26,14 @@ rules:
resources:
- customresourcedefinitions
verbs:
- "*"
- "*"
- apiGroups:
- "k8s.cni.cncf.io"
resources:
- "network-attachment-definitions"
verbs:
- "list"
- "watch"
- apiGroups:
- ""
resources:

2
go.mod
View File

@ -6,6 +6,7 @@ require (
github.com/google/uuid v1.6.0
github.com/gophercloud/gophercloud v1.11.0
github.com/harvester/harvester v1.3.0
github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.3.0
github.com/onsi/ginkgo/v2 v2.17.1
github.com/onsi/gomega v1.32.0
github.com/ory/dockertest/v3 v3.9.1
@ -65,7 +66,6 @@ require (
github.com/imdario/mergo v0.3.15 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.3.0 // indirect
github.com/kubernetes-csi/external-snapshotter/client/v4 v4.2.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect

View File

@ -5,6 +5,7 @@ import (
"time"
harvester "github.com/harvester/harvester/pkg/generated/controllers/harvesterhci.io"
cniv1 "github.com/harvester/harvester/pkg/generated/controllers/k8s.cni.cncf.io"
"github.com/harvester/harvester/pkg/generated/controllers/kubevirt.io"
"github.com/rancher/lasso/pkg/cache"
"github.com/rancher/lasso/pkg/client"
@ -86,13 +87,20 @@ func Register(ctx context.Context, restConfig *rest.Config) error {
scCache := storageFactory.Storage().V1().StorageClass().Cache()
cniFactory, err := cniv1.NewFactoryFromConfigWithOptions(restConfig, &core.FactoryOptions{
SharedControllerFactory: scf,
})
if err != nil {
return err
}
sc.RegisterVmwareController(ctx, migrationFactory.Migration().V1beta1().VmwareSource(), 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(),
coreFactory.Core().V1().Secret(), migrationFactory.Migration().V1beta1().VirtualMachineImport(),
harvesterFactory.Harvesterhci().V1beta1().VirtualMachineImage(), kubevirtFactory.Kubevirt().V1().VirtualMachine(),
coreFactory.Core().V1().PersistentVolumeClaim(), scCache)
coreFactory.Core().V1().PersistentVolumeClaim(), scCache, cniFactory.K8s().V1().NetworkAttachmentDefinition().Cache())
return start.All(ctx, 1, migrationFactory, coreFactory, harvesterFactory, kubevirtFactory, storageFactory)
return start.All(ctx, 1, migrationFactory, coreFactory, harvesterFactory, kubevirtFactory, storageFactory, cniFactory)
}

View File

@ -33,12 +33,16 @@ func RegisterOpenstackController(ctx context.Context, os migrationController.Ope
os.OnChange(ctx, "openstack-migration-change", oHandler.OnSourceChange)
}
func (h *openstackHandler) OnSourceChange(key string, o *migration.OpenstackSource) (*migration.OpenstackSource, error) {
func (h *openstackHandler) OnSourceChange(_ string, o *migration.OpenstackSource) (*migration.OpenstackSource, error) {
if o == nil || o.DeletionTimestamp != nil {
return o, nil
}
logrus.Infof("reconcilling openstack soure :%s", key)
logrus.WithFields(logrus.Fields{
"kind": o.Kind,
"name": o.Name,
"namespace": o.Namespace,
}).Info("Reconciling migration source")
if o.Status.Status != migration.ClusterReady {
// process migration logic
secretObj, err := h.secret.Get(o.Spec.Credentials.Namespace, o.Spec.Credentials.Name, metav1.GetOptions{})
@ -59,7 +63,7 @@ func (h *openstackHandler) OnSourceChange(key string, o *migration.OpenstackSour
"name": o.Name,
"namespace": o.Namespace,
"err": err,
}).Error("failed to verfiy client for openstack migration")
}).Error("failed to verify client for openstack migration")
conds := []common.Condition{
{
Type: migration.ClusterErrorCondition,

View File

@ -19,6 +19,7 @@ import (
harvesterv1beta1 "github.com/harvester/harvester/pkg/apis/harvesterhci.io/v1beta1"
harvester "github.com/harvester/harvester/pkg/generated/controllers/harvesterhci.io/v1beta1"
ctlcniv1 "github.com/harvester/harvester/pkg/generated/controllers/k8s.cni.cncf.io/v1"
kubevirtv1 "github.com/harvester/harvester/pkg/generated/controllers/kubevirt.io/v1"
"github.com/harvester/harvester/pkg/ref"
coreControllers "github.com/rancher/wrangler/pkg/generated/controllers/core/v1"
@ -46,13 +47,16 @@ type VirtualMachineOperations interface {
// Any image format conversion will be performed by the VM Operation
ExportVirtualMachine(vm *migration.VirtualMachineImport) error
// PowerOffVirtualMachine is responsible for the powering off the virtualmachine
// PowerOffVirtualMachine is responsible for the powering off the virtual machine
PowerOffVirtualMachine(vm *migration.VirtualMachineImport) error
// IsPoweredOff will check the status of VM Power and return true if machine is powered off
IsPoweredOff(vm *migration.VirtualMachineImport) (bool, error)
GenerateVirtualMachine(vm *migration.VirtualMachineImport) (*kubevirt.VirtualMachine, error)
// PreFlightChecks checks the cluster specific configurations.
PreFlightChecks(vm *migration.VirtualMachineImport) error
}
type virtualMachineHandler struct {
@ -65,10 +69,11 @@ type virtualMachineHandler struct {
kubevirt kubevirtv1.VirtualMachineController
pvc coreControllers.PersistentVolumeClaimController
sc storageControllers.StorageClassCache
nadCache ctlcniv1.NetworkAttachmentDefinitionCache
}
func RegisterVMImportController(ctx context.Context, vmware migrationController.VmwareSourceController, openstack migrationController.OpenstackSourceController,
secret coreControllers.SecretController, importVM migrationController.VirtualMachineImportController, vmi harvester.VirtualMachineImageController, kubevirt kubevirtv1.VirtualMachineController, pvc coreControllers.PersistentVolumeClaimController, scCache storageControllers.StorageClassCache) {
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,
@ -80,6 +85,7 @@ func RegisterVMImportController(ctx context.Context, vmware migrationController.
kubevirt: kubevirt,
pvc: pvc,
sc: scCache,
nadCache: nadCache,
}
relatedresource.Watch(ctx, "virtualmachineimage-change", vmHandler.ReconcileVMI, importVM, vmi)
@ -237,6 +243,55 @@ func (h *virtualMachineHandler) preFlightChecks(vm *migration.VirtualMachineImpo
return fmt.Errorf("source network %s appears multiple times in vm spec", network.SourceNetwork)
}
// Validate the destination network configuration.
for _, nm := range vm.Spec.Mapping {
// The destination network supports the following format:
// - <networkName>
// - <namespace>/<networkName>
// See `MultusNetwork.NetworkName` for more details.
parts := strings.Split(nm.DestinationNetwork, "/")
switch len(parts) {
case 1:
// If namespace is not specified, `VirtualMachineImport` namespace is assumed.
parts = append([]string{vm.Namespace}, parts[0])
fallthrough
case 2:
_, err := h.nadCache.Get(parts[0], parts[1])
if err != nil {
logrus.WithFields(logrus.Fields{
"name": vm.Name,
"namespace": vm.Namespace,
"spec.sourcecluster.kind": vm.Spec.SourceCluster.Kind,
}).Errorf("Failed to get destination network '%s/%s': %v",
parts[0], parts[1], err)
return err
}
default:
logrus.WithFields(logrus.Fields{
"name": vm.Name,
"namespace": vm.Namespace,
"spec.sourcecluster.kind": vm.Spec.SourceCluster.Kind,
}).Errorf("Invalid destination network '%s'", nm.DestinationNetwork)
return fmt.Errorf("invalid destination network '%s'", nm.DestinationNetwork)
}
}
// Validate the source network as part of the source cluster preflight
// checks.
vmo, err := h.generateVMO(vm)
if err != nil {
return fmt.Errorf("error generating VMO in preFlightChecks: %v", err)
}
err = vmo.PreFlightChecks(vm)
if err != nil {
logrus.WithFields(logrus.Fields{
"name": vm.Name,
"namespace": vm.Namespace,
"spec.sourcecluster.kind": vm.Spec.SourceCluster.Kind,
}).Errorf("Failed to perform source cluster specific preflight checks: %v", err)
return err
}
return nil
}

View File

@ -33,12 +33,16 @@ func RegisterVmwareController(ctx context.Context, vc migrationController.Vmware
vc.OnChange(ctx, "vmware-migration-change", vHandler.OnSourceChange)
}
func (h *vmwareHandler) OnSourceChange(key string, v *migration.VmwareSource) (*migration.VmwareSource, error) {
func (h *vmwareHandler) OnSourceChange(_ string, v *migration.VmwareSource) (*migration.VmwareSource, error) {
if v == nil || v.DeletionTimestamp != nil {
return v, nil
}
logrus.Infof("reoncilling vmware migration %s", key)
logrus.WithFields(logrus.Fields{
"kind": v.Kind,
"name": v.Name,
"namespace": v.Namespace,
}).Info("Reconciling migration source")
if v.Status.Status != migration.ClusterReady {
secretObj, err := h.secret.Get(v.Spec.Credentials.Namespace, v.Spec.Credentials.Name, metav1.GetOptions{})
if err != nil {
@ -57,7 +61,7 @@ func (h *vmwareHandler) OnSourceChange(key string, v *migration.VmwareSource) (*
"name": v.Name,
"namespace": v.Namespace,
"err": err,
}).Error("failed to verfiy client for vmware migration")
}).Error("failed to verify client for vmware migration")
// unable to find specific datacenter
conds := []common.Condition{
{

View File

@ -22,6 +22,7 @@ import (
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/imagedata"
"github.com/gophercloud/gophercloud/openstack/imageservice/v2/images"
"github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
"github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
@ -176,6 +177,16 @@ func (c *Client) Verify() error {
return nil
}
func (c *Client) PreFlightChecks(vm *migration.VirtualMachineImport) (err error) {
for _, nm := range vm.Spec.Mapping {
_, err := networks.Get(c.computeClient, nm.SourceNetwork).Extract()
if err != nil {
return fmt.Errorf("error getting source network '%s': %v", nm.SourceNetwork, err)
}
}
return nil
}
func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) error {
vmObj, err := c.findVM(vm.Spec.VirtualMachineName)
if err != nil {

View File

@ -123,6 +123,23 @@ func (c *Client) Verify() error {
return nil
}
func (c *Client) PreFlightChecks(vm *migration.VirtualMachineImport) (err error) {
f := find.NewFinder(c.Client.Client, true)
dc := c.dc
if !strings.HasPrefix(c.dc, "/") {
dc = fmt.Sprintf("/%s", c.dc)
}
for _, nm := range vm.Spec.Mapping {
// The path looks like `/<datacenter>/network/<network-name>`.
path := filepath.Join(dc, "/network", nm.SourceNetwork)
_, err := f.Network(c.ctx, path)
if err != nil {
return fmt.Errorf("error getting source network '%s': %v", nm.SourceNetwork, err)
}
}
return nil
}
func (c *Client) ExportVirtualMachine(vm *migration.VirtualMachineImport) (err error) {
var (
tmpPath string

View File

@ -20,6 +20,8 @@ import (
log "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
k8scnicncfiov1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
migration "github.com/harvester/vm-import-controller/pkg/apis/migration.harvesterhci.io/v1beta1"
"github.com/harvester/vm-import-controller/pkg/controllers"
"github.com/harvester/vm-import-controller/pkg/server"
@ -81,8 +83,9 @@ var _ = BeforeSuite(func() {
testEnv = &envtest.Environment{}
if !useExisting {
crds, err := setup.GenerateKubeVirtCRD()
crds, err := setup.GenerateCRD()
Expect(err).ToNot(HaveOccurred())
testEnv.CRDInstallOptions = envtest.CRDInstallOptions{
CRDs: crds,
}
@ -107,6 +110,9 @@ var _ = BeforeSuite(func() {
err = kubevirtv1.AddToScheme(scheme)
Expect(err).NotTo(HaveOccurred())
err = k8scnicncfiov1.AddToScheme(scheme)
Expect(err).NotTo(HaveOccurred())
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme})
Expect(err).NotTo(HaveOccurred())

View File

@ -4,6 +4,7 @@ import (
"context"
extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/rest"
"kubevirt.io/kubevirt/pkg/virt-operator/resource/generate/components"
@ -11,8 +12,8 @@ import (
"github.com/harvester/harvester/pkg/util/crd"
)
// InstallCRD will install the core harvester CRD's
// copied from harvester/harvester/pkg/data/crd.go
// InstallCRD will install the core Harvester CRD's
// partly copied from harvester/harvester/pkg/data/crd.go
func InstallCRD(ctx context.Context, cfg *rest.Config) error {
factory, err := crd.NewFactoryFromClient(ctx, cfg)
if err != nil {
@ -37,10 +38,23 @@ func InstallCRD(ctx context.Context, cfg *rest.Config) error {
BatchWait()
}
// GenerateCRD will generate other required CRD's
func GenerateCRD() ([]*extv1.CustomResourceDefinition, error) {
kubeVirtCrds, err := generateKubeVirtCRD()
if err != nil {
return nil, err
}
k8sCniCncfIoCrds, err := generateK8sCniCncfIoCRD()
if err != nil {
return nil, err
}
return append(kubeVirtCrds, k8sCniCncfIoCrds...), nil
}
type kubeVirtCRDGenerator func() (*extv1.CustomResourceDefinition, error)
// GenerateKubeVirtCRD's will generate kubevirt CRDs
func GenerateKubeVirtCRD() ([]*extv1.CustomResourceDefinition, error) {
// generateKubeVirtCRD will generate kubevirt CRDs
func generateKubeVirtCRD() ([]*extv1.CustomResourceDefinition, error) {
v := []kubeVirtCRDGenerator{
components.NewVirtualMachineCrd,
components.NewVirtualMachineInstanceCrd,
@ -65,3 +79,52 @@ func GenerateKubeVirtCRD() ([]*extv1.CustomResourceDefinition, error) {
return results, nil
}
// generateK8sCniCncfIoCRD will generate k8s.cni.cncf.io CRDs
func generateK8sCniCncfIoCRD() ([]*extv1.CustomResourceDefinition, error) {
var results []*extv1.CustomResourceDefinition
results = append(results,
// See https://github.com/k8snetworkplumbingwg/network-attachment-definition-client/blob/v1.3.0/artifacts/networks-crd.yaml
&extv1.CustomResourceDefinition{
TypeMeta: metav1.TypeMeta{
APIVersion: extv1.SchemeGroupVersion.String(),
Kind: "CustomResourceDefinition",
},
ObjectMeta: metav1.ObjectMeta{
Name: "network-attachment-definitions.k8s.cni.cncf.io",
},
Spec: extv1.CustomResourceDefinitionSpec{
Group: "k8s.cni.cncf.io",
Scope: "Namespaced",
Names: extv1.CustomResourceDefinitionNames{
Plural: "network-attachment-definitions",
Singular: "network-attachment-definition",
Kind: "NetworkAttachmentDefinition",
ShortNames: []string{"net-attach-def"},
},
Versions: []extv1.CustomResourceDefinitionVersion{
{
Name: "v1",
Served: true,
Storage: true,
Schema: &extv1.CustomResourceValidation{
OpenAPIV3Schema: &extv1.JSONSchemaProps{
Type: "object",
Properties: map[string]extv1.JSONSchemaProps{
"spec": {
Type: "object",
Properties: map[string]extv1.JSONSchemaProps{
"config": {
Type: "string",
},
},
},
},
},
},
},
},
},
})
return results, nil
}