From d8d50c6923b46e5e205d415bbaf30a0bd4d52885 Mon Sep 17 00:00:00 2001 From: Isteb4k Date: Thu, 19 Sep 2024 16:38:35 +0200 Subject: [PATCH] fix(api): wait for uploader to be ready to process user's upload Signed-off-by: Isteb4k --- .../typed/core/v1alpha2/core_client.go | 5 + .../core/v1alpha2/fake/fake_core_client.go | 4 + .../fake/fake_virtualmachinerestore.go | 141 +++++++++ .../fake/fake_virtualmachinesnapshot.go | 3 +- .../core/v1alpha2/generated_expansion.go | 2 + .../core/v1alpha2/virtualmachinerestore.go | 195 ++++++++++++ .../core/v1alpha2/virtualmachinesnapshot.go | 3 +- .../core/v1alpha2/interface.go | 7 + .../core/v1alpha2/virtualmachinerestore.go | 90 ++++++ .../core/v1alpha2/virtualmachinesnapshot.go | 3 +- .../informers/externalversions/generic.go | 2 + .../core/v1alpha2/expansion_generated.go | 8 + .../core/v1alpha2/virtualmachinerestore.go | 99 ++++++ .../core/v1alpha2/virtualmachinesnapshot.go | 3 +- api/core/v1alpha2/finalizers.go | 1 + api/core/v1alpha2/register.go | 2 + .../virtual_machine_block_disk_attachment.go | 4 +- .../v1alpha2/virtual_machine_ip_address.go | 5 + api/core/v1alpha2/virtual_machine_restore.go | 105 +++++++ api/core/v1alpha2/virtual_machine_snapshot.go | 14 +- .../vm-restore-condition/condition.go | 64 ++++ api/core/v1alpha2/vmcondition/condition.go | 2 +- api/core/v1alpha2/vmscondition/condition.go | 18 +- api/core/v1alpha2/zz_generated.deepcopy.go | 142 ++++++++- .../generated/openapi/zz_generated.openapi.go | 247 ++++++++++++++- api/scripts/update-codegen.sh | 2 +- crds/doc-ru-virtualmachinerestores.yaml | 70 +++++ crds/doc-ru-virtualmachinesnapshots.yaml | 4 +- crds/virtualmachinerestores.yaml | 197 ++++++++++++ crds/virtualmachinesnapshots.yaml | 7 +- .../cmd/virtualization-controller/main.go | 9 + .../pkg/controller/conditions/builder.go | 10 +- .../pkg/controller/cvi/cvi_controller.go | 8 +- .../pkg/controller/gc/gc_reconciler.go | 6 +- .../pkg/controller/indexer/indexer.go | 8 + .../controller/indexer/vm_restore_indexer.go | 37 +++ .../controller/indexer/vm_snapshot_indexer.go | 48 +++ .../pkg/controller/service/restore_service.go | 68 ++++ .../pkg/controller/service/restorer/keys.go | 40 +++ .../controller/service/restorer/restorer.go | 291 ++++++++++++++++++ .../internal/source/object_ref_vdsnapshot.go | 7 +- .../pkg/controller/vd/vd_controller.go | 8 +- .../vdsnapshot/internal/life_cycle.go | 17 +- .../vdsnapshot/vdsnapshot_controller.go | 8 +- .../vdsnapshot/vdsnapshot_webhook.go | 15 +- .../pkg/controller/vi/vi_controller.go | 8 +- .../controller/vm/internal/snapshotting.go | 2 +- .../vm/internal/watcher/vmsnapshot_watcher.go | 90 ++++++ .../pkg/controller/vm/vm_controller.go | 8 +- .../pkg/controller/vm/vm_reconciler.go | 23 +- .../pkg/controller/vmbda/vmbda_controller.go | 8 +- .../controller/vmclass/vmclass_controller.go | 8 +- .../pkg/controller/vmip/vmip_controller.go | 8 +- .../vmiplease/vmiplease_controller.go | 7 +- .../pkg/controller/vmop/vmop_controller.go | 9 +- .../controller/vmrestore/internal/deletion.go | 42 +++ .../vmrestore/internal/interfaces.go | 34 ++ .../vmrestore/internal/life_cycle.go | 282 +++++++++++++++++ .../vmrestore/internal/restorer/errors.go | 21 ++ .../vmrestore/internal/restorer/overrider.go | 37 +++ .../internal/restorer/provisioner_restorer.go | 85 +++++ .../internal/restorer/vd_restorer.go | 74 +++++ .../internal/restorer/vm_restorer.go | 83 +++++ .../internal/restorer/vmbda_restorer.go | 79 +++++ .../internal/restorer/vmip_restorer.go | 88 ++++++ .../internal/vm_snapshot_ready_to_use.go | 102 ++++++ .../internal/watcher/vmrestore_watcher.go | 53 ++++ .../internal/watcher}/vmsnapshot_watcher.go | 34 +- .../vmrestore/vmrestore_controller.go | 76 +++++ .../vmrestore/vmrestore_reconciler.go | 119 +++++++ .../controller/vmrestore/vmrestore_webhook.go | 70 +++++ .../vmsnapshot/internal/interfaces.go | 8 +- .../vmsnapshot/internal/life_cycle.go | 160 +++++----- .../vmsnapshot/internal/life_cycle_test.go | 63 ++-- .../controller/vmsnapshot/internal/mock.go | 170 +++++----- .../vmsnapshot/internal/secret_builder.go | 235 -------------- .../internal/virtual_machine_ready.go | 55 ++-- .../vmsnapshot/internal/watcher/vd_watcher.go | 1 - .../internal/watcher/vdsnapshot_watcher.go | 7 +- .../vmsnapshot/internal/watcher/vm_watcher.go | 8 +- .../vmsnapshot/vmsnapshot_controller.go | 12 +- .../vmsnapshot/vmsnapshot_webhook.go | 11 +- .../rbac-for-us.yaml | 3 + .../validation-webhook.yaml | 38 ++- 84 files changed, 3650 insertions(+), 600 deletions(-) create mode 100644 api/client/generated/clientset/versioned/typed/core/v1alpha2/fake/fake_virtualmachinerestore.go create mode 100644 api/client/generated/clientset/versioned/typed/core/v1alpha2/virtualmachinerestore.go create mode 100644 api/client/generated/informers/externalversions/core/v1alpha2/virtualmachinerestore.go create mode 100644 api/client/generated/listers/core/v1alpha2/virtualmachinerestore.go create mode 100644 api/core/v1alpha2/virtual_machine_restore.go create mode 100644 api/core/v1alpha2/vm-restore-condition/condition.go create mode 100644 crds/doc-ru-virtualmachinerestores.yaml create mode 100644 crds/virtualmachinerestores.yaml create mode 100644 images/virtualization-artifact/pkg/controller/indexer/vm_restore_indexer.go create mode 100644 images/virtualization-artifact/pkg/controller/indexer/vm_snapshot_indexer.go create mode 100644 images/virtualization-artifact/pkg/controller/service/restore_service.go create mode 100644 images/virtualization-artifact/pkg/controller/service/restorer/keys.go create mode 100644 images/virtualization-artifact/pkg/controller/service/restorer/restorer.go create mode 100644 images/virtualization-artifact/pkg/controller/vm/internal/watcher/vmsnapshot_watcher.go create mode 100644 images/virtualization-artifact/pkg/controller/vmrestore/internal/deletion.go create mode 100644 images/virtualization-artifact/pkg/controller/vmrestore/internal/interfaces.go create mode 100644 images/virtualization-artifact/pkg/controller/vmrestore/internal/life_cycle.go create mode 100644 images/virtualization-artifact/pkg/controller/vmrestore/internal/restorer/errors.go create mode 100644 images/virtualization-artifact/pkg/controller/vmrestore/internal/restorer/overrider.go create mode 100644 images/virtualization-artifact/pkg/controller/vmrestore/internal/restorer/provisioner_restorer.go create mode 100644 images/virtualization-artifact/pkg/controller/vmrestore/internal/restorer/vd_restorer.go create mode 100644 images/virtualization-artifact/pkg/controller/vmrestore/internal/restorer/vm_restorer.go create mode 100644 images/virtualization-artifact/pkg/controller/vmrestore/internal/restorer/vmbda_restorer.go create mode 100644 images/virtualization-artifact/pkg/controller/vmrestore/internal/restorer/vmip_restorer.go create mode 100644 images/virtualization-artifact/pkg/controller/vmrestore/internal/vm_snapshot_ready_to_use.go create mode 100644 images/virtualization-artifact/pkg/controller/vmrestore/internal/watcher/vmrestore_watcher.go rename images/virtualization-artifact/pkg/controller/{vm/internal/watchers => vmrestore/internal/watcher}/vmsnapshot_watcher.go (81%) create mode 100644 images/virtualization-artifact/pkg/controller/vmrestore/vmrestore_controller.go create mode 100644 images/virtualization-artifact/pkg/controller/vmrestore/vmrestore_reconciler.go create mode 100644 images/virtualization-artifact/pkg/controller/vmrestore/vmrestore_webhook.go delete mode 100644 images/virtualization-artifact/pkg/controller/vmsnapshot/internal/secret_builder.go diff --git a/api/client/generated/clientset/versioned/typed/core/v1alpha2/core_client.go b/api/client/generated/clientset/versioned/typed/core/v1alpha2/core_client.go index 9443fee15..38b2cdc05 100644 --- a/api/client/generated/clientset/versioned/typed/core/v1alpha2/core_client.go +++ b/api/client/generated/clientset/versioned/typed/core/v1alpha2/core_client.go @@ -38,6 +38,7 @@ type VirtualizationV1alpha2Interface interface { VirtualMachineIPAddressesGetter VirtualMachineIPAddressLeasesGetter VirtualMachineOperationsGetter + VirtualMachineRestoresGetter VirtualMachineSnapshotsGetter } @@ -86,6 +87,10 @@ func (c *VirtualizationV1alpha2Client) VirtualMachineOperations(namespace string return newVirtualMachineOperations(c, namespace) } +func (c *VirtualizationV1alpha2Client) VirtualMachineRestores(namespace string) VirtualMachineRestoreInterface { + return newVirtualMachineRestores(c, namespace) +} + func (c *VirtualizationV1alpha2Client) VirtualMachineSnapshots(namespace string) VirtualMachineSnapshotInterface { return newVirtualMachineSnapshots(c, namespace) } diff --git a/api/client/generated/clientset/versioned/typed/core/v1alpha2/fake/fake_core_client.go b/api/client/generated/clientset/versioned/typed/core/v1alpha2/fake/fake_core_client.go index 42c7892a5..203ed5377 100644 --- a/api/client/generated/clientset/versioned/typed/core/v1alpha2/fake/fake_core_client.go +++ b/api/client/generated/clientset/versioned/typed/core/v1alpha2/fake/fake_core_client.go @@ -68,6 +68,10 @@ func (c *FakeVirtualizationV1alpha2) VirtualMachineOperations(namespace string) return &FakeVirtualMachineOperations{c, namespace} } +func (c *FakeVirtualizationV1alpha2) VirtualMachineRestores(namespace string) v1alpha2.VirtualMachineRestoreInterface { + return &FakeVirtualMachineRestores{c, namespace} +} + func (c *FakeVirtualizationV1alpha2) VirtualMachineSnapshots(namespace string) v1alpha2.VirtualMachineSnapshotInterface { return &FakeVirtualMachineSnapshots{c, namespace} } diff --git a/api/client/generated/clientset/versioned/typed/core/v1alpha2/fake/fake_virtualmachinerestore.go b/api/client/generated/clientset/versioned/typed/core/v1alpha2/fake/fake_virtualmachinerestore.go new file mode 100644 index 000000000..7997b7dde --- /dev/null +++ b/api/client/generated/clientset/versioned/typed/core/v1alpha2/fake/fake_virtualmachinerestore.go @@ -0,0 +1,141 @@ +/* +Copyright Flant JSC + +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 client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeVirtualMachineRestores implements VirtualMachineRestoreInterface +type FakeVirtualMachineRestores struct { + Fake *FakeVirtualizationV1alpha2 + ns string +} + +var virtualmachinerestoresResource = v1alpha2.SchemeGroupVersion.WithResource("virtualmachinerestores") + +var virtualmachinerestoresKind = v1alpha2.SchemeGroupVersion.WithKind("VirtualMachineRestore") + +// Get takes name of the virtualMachineRestore, and returns the corresponding virtualMachineRestore object, and an error if there is any. +func (c *FakeVirtualMachineRestores) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha2.VirtualMachineRestore, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(virtualmachinerestoresResource, c.ns, name), &v1alpha2.VirtualMachineRestore{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.VirtualMachineRestore), err +} + +// List takes label and field selectors, and returns the list of VirtualMachineRestores that match those selectors. +func (c *FakeVirtualMachineRestores) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha2.VirtualMachineRestoreList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(virtualmachinerestoresResource, virtualmachinerestoresKind, c.ns, opts), &v1alpha2.VirtualMachineRestoreList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha2.VirtualMachineRestoreList{ListMeta: obj.(*v1alpha2.VirtualMachineRestoreList).ListMeta} + for _, item := range obj.(*v1alpha2.VirtualMachineRestoreList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested virtualMachineRestores. +func (c *FakeVirtualMachineRestores) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(virtualmachinerestoresResource, c.ns, opts)) + +} + +// Create takes the representation of a virtualMachineRestore and creates it. Returns the server's representation of the virtualMachineRestore, and an error, if there is any. +func (c *FakeVirtualMachineRestores) Create(ctx context.Context, virtualMachineRestore *v1alpha2.VirtualMachineRestore, opts v1.CreateOptions) (result *v1alpha2.VirtualMachineRestore, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(virtualmachinerestoresResource, c.ns, virtualMachineRestore), &v1alpha2.VirtualMachineRestore{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.VirtualMachineRestore), err +} + +// Update takes the representation of a virtualMachineRestore and updates it. Returns the server's representation of the virtualMachineRestore, and an error, if there is any. +func (c *FakeVirtualMachineRestores) Update(ctx context.Context, virtualMachineRestore *v1alpha2.VirtualMachineRestore, opts v1.UpdateOptions) (result *v1alpha2.VirtualMachineRestore, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(virtualmachinerestoresResource, c.ns, virtualMachineRestore), &v1alpha2.VirtualMachineRestore{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.VirtualMachineRestore), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeVirtualMachineRestores) UpdateStatus(ctx context.Context, virtualMachineRestore *v1alpha2.VirtualMachineRestore, opts v1.UpdateOptions) (*v1alpha2.VirtualMachineRestore, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(virtualmachinerestoresResource, "status", c.ns, virtualMachineRestore), &v1alpha2.VirtualMachineRestore{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.VirtualMachineRestore), err +} + +// Delete takes name of the virtualMachineRestore and deletes it. Returns an error if one occurs. +func (c *FakeVirtualMachineRestores) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(virtualmachinerestoresResource, c.ns, name, opts), &v1alpha2.VirtualMachineRestore{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeVirtualMachineRestores) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(virtualmachinerestoresResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha2.VirtualMachineRestoreList{}) + return err +} + +// Patch applies the patch and returns the patched virtualMachineRestore. +func (c *FakeVirtualMachineRestores) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.VirtualMachineRestore, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(virtualmachinerestoresResource, c.ns, name, pt, data, subresources...), &v1alpha2.VirtualMachineRestore{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.VirtualMachineRestore), err +} diff --git a/api/client/generated/clientset/versioned/typed/core/v1alpha2/fake/fake_virtualmachinesnapshot.go b/api/client/generated/clientset/versioned/typed/core/v1alpha2/fake/fake_virtualmachinesnapshot.go index 466f5c465..e83f30936 100644 --- a/api/client/generated/clientset/versioned/typed/core/v1alpha2/fake/fake_virtualmachinesnapshot.go +++ b/api/client/generated/clientset/versioned/typed/core/v1alpha2/fake/fake_virtualmachinesnapshot.go @@ -1,5 +1,5 @@ /* -Copyright 2022 Flant JSC +Copyright Flant JSC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13,6 +13,7 @@ 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 client-gen. DO NOT EDIT. package fake diff --git a/api/client/generated/clientset/versioned/typed/core/v1alpha2/generated_expansion.go b/api/client/generated/clientset/versioned/typed/core/v1alpha2/generated_expansion.go index fcf59a09b..a52a7ada9 100644 --- a/api/client/generated/clientset/versioned/typed/core/v1alpha2/generated_expansion.go +++ b/api/client/generated/clientset/versioned/typed/core/v1alpha2/generated_expansion.go @@ -38,4 +38,6 @@ type VirtualMachineIPAddressLeaseExpansion interface{} type VirtualMachineOperationExpansion interface{} +type VirtualMachineRestoreExpansion interface{} + type VirtualMachineSnapshotExpansion interface{} diff --git a/api/client/generated/clientset/versioned/typed/core/v1alpha2/virtualmachinerestore.go b/api/client/generated/clientset/versioned/typed/core/v1alpha2/virtualmachinerestore.go new file mode 100644 index 000000000..e945cd23e --- /dev/null +++ b/api/client/generated/clientset/versioned/typed/core/v1alpha2/virtualmachinerestore.go @@ -0,0 +1,195 @@ +/* +Copyright Flant JSC + +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 client-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + "context" + "time" + + scheme "github.com/deckhouse/virtualization/api/client/generated/clientset/versioned/scheme" + v1alpha2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// VirtualMachineRestoresGetter has a method to return a VirtualMachineRestoreInterface. +// A group's client should implement this interface. +type VirtualMachineRestoresGetter interface { + VirtualMachineRestores(namespace string) VirtualMachineRestoreInterface +} + +// VirtualMachineRestoreInterface has methods to work with VirtualMachineRestore resources. +type VirtualMachineRestoreInterface interface { + Create(ctx context.Context, virtualMachineRestore *v1alpha2.VirtualMachineRestore, opts v1.CreateOptions) (*v1alpha2.VirtualMachineRestore, error) + Update(ctx context.Context, virtualMachineRestore *v1alpha2.VirtualMachineRestore, opts v1.UpdateOptions) (*v1alpha2.VirtualMachineRestore, error) + UpdateStatus(ctx context.Context, virtualMachineRestore *v1alpha2.VirtualMachineRestore, opts v1.UpdateOptions) (*v1alpha2.VirtualMachineRestore, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha2.VirtualMachineRestore, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha2.VirtualMachineRestoreList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.VirtualMachineRestore, err error) + VirtualMachineRestoreExpansion +} + +// virtualMachineRestores implements VirtualMachineRestoreInterface +type virtualMachineRestores struct { + client rest.Interface + ns string +} + +// newVirtualMachineRestores returns a VirtualMachineRestores +func newVirtualMachineRestores(c *VirtualizationV1alpha2Client, namespace string) *virtualMachineRestores { + return &virtualMachineRestores{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the virtualMachineRestore, and returns the corresponding virtualMachineRestore object, and an error if there is any. +func (c *virtualMachineRestores) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha2.VirtualMachineRestore, err error) { + result = &v1alpha2.VirtualMachineRestore{} + err = c.client.Get(). + Namespace(c.ns). + Resource("virtualmachinerestores"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of VirtualMachineRestores that match those selectors. +func (c *virtualMachineRestores) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha2.VirtualMachineRestoreList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha2.VirtualMachineRestoreList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("virtualmachinerestores"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested virtualMachineRestores. +func (c *virtualMachineRestores) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("virtualmachinerestores"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a virtualMachineRestore and creates it. Returns the server's representation of the virtualMachineRestore, and an error, if there is any. +func (c *virtualMachineRestores) Create(ctx context.Context, virtualMachineRestore *v1alpha2.VirtualMachineRestore, opts v1.CreateOptions) (result *v1alpha2.VirtualMachineRestore, err error) { + result = &v1alpha2.VirtualMachineRestore{} + err = c.client.Post(). + Namespace(c.ns). + Resource("virtualmachinerestores"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(virtualMachineRestore). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a virtualMachineRestore and updates it. Returns the server's representation of the virtualMachineRestore, and an error, if there is any. +func (c *virtualMachineRestores) Update(ctx context.Context, virtualMachineRestore *v1alpha2.VirtualMachineRestore, opts v1.UpdateOptions) (result *v1alpha2.VirtualMachineRestore, err error) { + result = &v1alpha2.VirtualMachineRestore{} + err = c.client.Put(). + Namespace(c.ns). + Resource("virtualmachinerestores"). + Name(virtualMachineRestore.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(virtualMachineRestore). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *virtualMachineRestores) UpdateStatus(ctx context.Context, virtualMachineRestore *v1alpha2.VirtualMachineRestore, opts v1.UpdateOptions) (result *v1alpha2.VirtualMachineRestore, err error) { + result = &v1alpha2.VirtualMachineRestore{} + err = c.client.Put(). + Namespace(c.ns). + Resource("virtualmachinerestores"). + Name(virtualMachineRestore.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(virtualMachineRestore). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the virtualMachineRestore and deletes it. Returns an error if one occurs. +func (c *virtualMachineRestores) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("virtualmachinerestores"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *virtualMachineRestores) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("virtualmachinerestores"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched virtualMachineRestore. +func (c *virtualMachineRestores) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.VirtualMachineRestore, err error) { + result = &v1alpha2.VirtualMachineRestore{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("virtualmachinerestores"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/api/client/generated/clientset/versioned/typed/core/v1alpha2/virtualmachinesnapshot.go b/api/client/generated/clientset/versioned/typed/core/v1alpha2/virtualmachinesnapshot.go index f6e599cbc..fd41aa9f9 100644 --- a/api/client/generated/clientset/versioned/typed/core/v1alpha2/virtualmachinesnapshot.go +++ b/api/client/generated/clientset/versioned/typed/core/v1alpha2/virtualmachinesnapshot.go @@ -1,5 +1,5 @@ /* -Copyright 2022 Flant JSC +Copyright Flant JSC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13,6 +13,7 @@ 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 client-gen. DO NOT EDIT. package v1alpha2 diff --git a/api/client/generated/informers/externalversions/core/v1alpha2/interface.go b/api/client/generated/informers/externalversions/core/v1alpha2/interface.go index 9178544d2..867f35fd0 100644 --- a/api/client/generated/informers/externalversions/core/v1alpha2/interface.go +++ b/api/client/generated/informers/externalversions/core/v1alpha2/interface.go @@ -44,6 +44,8 @@ type Interface interface { VirtualMachineIPAddressLeases() VirtualMachineIPAddressLeaseInformer // VirtualMachineOperations returns a VirtualMachineOperationInformer. VirtualMachineOperations() VirtualMachineOperationInformer + // VirtualMachineRestores returns a VirtualMachineRestoreInformer. + VirtualMachineRestores() VirtualMachineRestoreInformer // VirtualMachineSnapshots returns a VirtualMachineSnapshotInformer. VirtualMachineSnapshots() VirtualMachineSnapshotInformer } @@ -109,6 +111,11 @@ func (v *version) VirtualMachineOperations() VirtualMachineOperationInformer { return &virtualMachineOperationInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } +// VirtualMachineRestores returns a VirtualMachineRestoreInformer. +func (v *version) VirtualMachineRestores() VirtualMachineRestoreInformer { + return &virtualMachineRestoreInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // VirtualMachineSnapshots returns a VirtualMachineSnapshotInformer. func (v *version) VirtualMachineSnapshots() VirtualMachineSnapshotInformer { return &virtualMachineSnapshotInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/api/client/generated/informers/externalversions/core/v1alpha2/virtualmachinerestore.go b/api/client/generated/informers/externalversions/core/v1alpha2/virtualmachinerestore.go new file mode 100644 index 000000000..64a8ed69c --- /dev/null +++ b/api/client/generated/informers/externalversions/core/v1alpha2/virtualmachinerestore.go @@ -0,0 +1,90 @@ +/* +Copyright Flant JSC + +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 informer-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + "context" + time "time" + + versioned "github.com/deckhouse/virtualization/api/client/generated/clientset/versioned" + internalinterfaces "github.com/deckhouse/virtualization/api/client/generated/informers/externalversions/internalinterfaces" + v1alpha2 "github.com/deckhouse/virtualization/api/client/generated/listers/core/v1alpha2" + corev1alpha2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// VirtualMachineRestoreInformer provides access to a shared informer and lister for +// VirtualMachineRestores. +type VirtualMachineRestoreInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha2.VirtualMachineRestoreLister +} + +type virtualMachineRestoreInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewVirtualMachineRestoreInformer constructs a new informer for VirtualMachineRestore type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewVirtualMachineRestoreInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredVirtualMachineRestoreInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredVirtualMachineRestoreInformer constructs a new informer for VirtualMachineRestore type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredVirtualMachineRestoreInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.VirtualizationV1alpha2().VirtualMachineRestores(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.VirtualizationV1alpha2().VirtualMachineRestores(namespace).Watch(context.TODO(), options) + }, + }, + &corev1alpha2.VirtualMachineRestore{}, + resyncPeriod, + indexers, + ) +} + +func (f *virtualMachineRestoreInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredVirtualMachineRestoreInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *virtualMachineRestoreInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&corev1alpha2.VirtualMachineRestore{}, f.defaultInformer) +} + +func (f *virtualMachineRestoreInformer) Lister() v1alpha2.VirtualMachineRestoreLister { + return v1alpha2.NewVirtualMachineRestoreLister(f.Informer().GetIndexer()) +} diff --git a/api/client/generated/informers/externalversions/core/v1alpha2/virtualmachinesnapshot.go b/api/client/generated/informers/externalversions/core/v1alpha2/virtualmachinesnapshot.go index 4e85ca84d..3d2fae580 100644 --- a/api/client/generated/informers/externalversions/core/v1alpha2/virtualmachinesnapshot.go +++ b/api/client/generated/informers/externalversions/core/v1alpha2/virtualmachinesnapshot.go @@ -1,5 +1,5 @@ /* -Copyright 2022 Flant JSC +Copyright Flant JSC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13,6 +13,7 @@ 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 informer-gen. DO NOT EDIT. package v1alpha2 diff --git a/api/client/generated/informers/externalversions/generic.go b/api/client/generated/informers/externalversions/generic.go index dd169446c..77f8f2188 100644 --- a/api/client/generated/informers/externalversions/generic.go +++ b/api/client/generated/informers/externalversions/generic.go @@ -73,6 +73,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Virtualization().V1alpha2().VirtualMachineIPAddressLeases().Informer()}, nil case v1alpha2.SchemeGroupVersion.WithResource("virtualmachineoperations"): return &genericInformer{resource: resource.GroupResource(), informer: f.Virtualization().V1alpha2().VirtualMachineOperations().Informer()}, nil + case v1alpha2.SchemeGroupVersion.WithResource("virtualmachinerestores"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Virtualization().V1alpha2().VirtualMachineRestores().Informer()}, nil case v1alpha2.SchemeGroupVersion.WithResource("virtualmachinesnapshots"): return &genericInformer{resource: resource.GroupResource(), informer: f.Virtualization().V1alpha2().VirtualMachineSnapshots().Informer()}, nil diff --git a/api/client/generated/listers/core/v1alpha2/expansion_generated.go b/api/client/generated/listers/core/v1alpha2/expansion_generated.go index b504fbe43..f9d606ce1 100644 --- a/api/client/generated/listers/core/v1alpha2/expansion_generated.go +++ b/api/client/generated/listers/core/v1alpha2/expansion_generated.go @@ -86,6 +86,14 @@ type VirtualMachineOperationListerExpansion interface{} // VirtualMachineOperationNamespaceLister. type VirtualMachineOperationNamespaceListerExpansion interface{} +// VirtualMachineRestoreListerExpansion allows custom methods to be added to +// VirtualMachineRestoreLister. +type VirtualMachineRestoreListerExpansion interface{} + +// VirtualMachineRestoreNamespaceListerExpansion allows custom methods to be added to +// VirtualMachineRestoreNamespaceLister. +type VirtualMachineRestoreNamespaceListerExpansion interface{} + // VirtualMachineSnapshotListerExpansion allows custom methods to be added to // VirtualMachineSnapshotLister. type VirtualMachineSnapshotListerExpansion interface{} diff --git a/api/client/generated/listers/core/v1alpha2/virtualmachinerestore.go b/api/client/generated/listers/core/v1alpha2/virtualmachinerestore.go new file mode 100644 index 000000000..620f644f3 --- /dev/null +++ b/api/client/generated/listers/core/v1alpha2/virtualmachinerestore.go @@ -0,0 +1,99 @@ +/* +Copyright Flant JSC + +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 lister-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + v1alpha2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// VirtualMachineRestoreLister helps list VirtualMachineRestores. +// All objects returned here must be treated as read-only. +type VirtualMachineRestoreLister interface { + // List lists all VirtualMachineRestores in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha2.VirtualMachineRestore, err error) + // VirtualMachineRestores returns an object that can list and get VirtualMachineRestores. + VirtualMachineRestores(namespace string) VirtualMachineRestoreNamespaceLister + VirtualMachineRestoreListerExpansion +} + +// virtualMachineRestoreLister implements the VirtualMachineRestoreLister interface. +type virtualMachineRestoreLister struct { + indexer cache.Indexer +} + +// NewVirtualMachineRestoreLister returns a new VirtualMachineRestoreLister. +func NewVirtualMachineRestoreLister(indexer cache.Indexer) VirtualMachineRestoreLister { + return &virtualMachineRestoreLister{indexer: indexer} +} + +// List lists all VirtualMachineRestores in the indexer. +func (s *virtualMachineRestoreLister) List(selector labels.Selector) (ret []*v1alpha2.VirtualMachineRestore, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha2.VirtualMachineRestore)) + }) + return ret, err +} + +// VirtualMachineRestores returns an object that can list and get VirtualMachineRestores. +func (s *virtualMachineRestoreLister) VirtualMachineRestores(namespace string) VirtualMachineRestoreNamespaceLister { + return virtualMachineRestoreNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// VirtualMachineRestoreNamespaceLister helps list and get VirtualMachineRestores. +// All objects returned here must be treated as read-only. +type VirtualMachineRestoreNamespaceLister interface { + // List lists all VirtualMachineRestores in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha2.VirtualMachineRestore, err error) + // Get retrieves the VirtualMachineRestore from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha2.VirtualMachineRestore, error) + VirtualMachineRestoreNamespaceListerExpansion +} + +// virtualMachineRestoreNamespaceLister implements the VirtualMachineRestoreNamespaceLister +// interface. +type virtualMachineRestoreNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all VirtualMachineRestores in the indexer for a given namespace. +func (s virtualMachineRestoreNamespaceLister) List(selector labels.Selector) (ret []*v1alpha2.VirtualMachineRestore, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha2.VirtualMachineRestore)) + }) + return ret, err +} + +// Get retrieves the VirtualMachineRestore from the indexer for a given namespace and name. +func (s virtualMachineRestoreNamespaceLister) Get(name string) (*v1alpha2.VirtualMachineRestore, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha2.Resource("virtualmachinerestore"), name) + } + return obj.(*v1alpha2.VirtualMachineRestore), nil +} diff --git a/api/client/generated/listers/core/v1alpha2/virtualmachinesnapshot.go b/api/client/generated/listers/core/v1alpha2/virtualmachinesnapshot.go index 074597a91..2c273b0b0 100644 --- a/api/client/generated/listers/core/v1alpha2/virtualmachinesnapshot.go +++ b/api/client/generated/listers/core/v1alpha2/virtualmachinesnapshot.go @@ -1,5 +1,5 @@ /* -Copyright 2022 Flant JSC +Copyright Flant JSC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13,6 +13,7 @@ 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 lister-gen. DO NOT EDIT. package v1alpha2 diff --git a/api/core/v1alpha2/finalizers.go b/api/core/v1alpha2/finalizers.go index ab7fcbbce..4c705d78d 100644 --- a/api/core/v1alpha2/finalizers.go +++ b/api/core/v1alpha2/finalizers.go @@ -34,6 +34,7 @@ const ( FinalizerIPAddressLeaseCleanup = "virtualization.deckhouse.io/vmipl-cleanup" FinalizerVDSnapshotCleanup = "virtualization.deckhouse.io/vdsnapshot-cleanup" FinalizerVMSnapshotCleanup = "virtualization.deckhouse.io/vmsnapshot-cleanup" + FinalizerVMRestoreCleanup = "virtualization.deckhouse.io/vmrestore-cleanup" FinalizerVMOPCleanup = "virtualization.deckhouse.io/vmop-cleanup" FinalizerVMClassCleanup = "virtualization.deckhouse.io/vmclass-cleanup" FinalizerVMBDACleanup = "virtualization.deckhouse.io/vmbda-cleanup" diff --git a/api/core/v1alpha2/register.go b/api/core/v1alpha2/register.go index 362ff5afc..62c73482b 100644 --- a/api/core/v1alpha2/register.go +++ b/api/core/v1alpha2/register.go @@ -84,6 +84,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { &VirtualDiskSnapshotList{}, &VirtualMachineSnapshot{}, &VirtualMachineSnapshotList{}, + &VirtualMachineRestore{}, + &VirtualMachineRestoreList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil diff --git a/api/core/v1alpha2/virtual_machine_block_disk_attachment.go b/api/core/v1alpha2/virtual_machine_block_disk_attachment.go index 67da465ad..a54bb2d8a 100644 --- a/api/core/v1alpha2/virtual_machine_block_disk_attachment.go +++ b/api/core/v1alpha2/virtual_machine_block_disk_attachment.go @@ -19,8 +19,8 @@ package v1alpha2 import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" const ( - VMBDAKind = "VirtualMachineBlockDeviceAttachment" - VMBDAResource = "virtualmachineblockdeviceattachments" + VirtualMachineBlockDeviceAttachmentKind = "VirtualMachineBlockDeviceAttachment" + VirtualMachineBlockDeviceAttachmentResource = "virtualmachineblockdeviceattachments" ) // VirtualMachineBlockDeviceAttachment provides a hot plug for connecting a disk to a virtual machine. diff --git a/api/core/v1alpha2/virtual_machine_ip_address.go b/api/core/v1alpha2/virtual_machine_ip_address.go index fcb97fbce..890c481b8 100644 --- a/api/core/v1alpha2/virtual_machine_ip_address.go +++ b/api/core/v1alpha2/virtual_machine_ip_address.go @@ -20,6 +20,11 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +const ( + VirtualMachineIPAddressKind = "VirtualMachineIPAddress" + VirtualMachineIPAddressResource = "virtualmachineipaddresses" +) + // VirtualMachineIPAddress defines IP address for virtual machine. // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/api/core/v1alpha2/virtual_machine_restore.go b/api/core/v1alpha2/virtual_machine_restore.go new file mode 100644 index 000000000..cec749de7 --- /dev/null +++ b/api/core/v1alpha2/virtual_machine_restore.go @@ -0,0 +1,105 @@ +/* +Copyright 2024 Flant JSC + +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. +*/ + +// +kubebuilder:object:generate=true +// +groupName=virtualization.deckhouse.io +package v1alpha2 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + VirtualMachineRestoreKind = "VirtualMachineRestore" + VirtualMachineRestoreResource = "virtualmachinerestores" +) + +// VirtualMachineRestore provides a resource that allows to restore a snapshot of the virtual machine and all its resources. +// +// +kubebuilder:object:root=true +// +kubebuilder:metadata:labels={heritage=deckhouse,module=virtualization} +// +kubebuilder:subresource:status +// +kubebuilder:resource:categories=virtualization,scope=Namespaced,shortName={vmrestore,vmrestores},singular=virtualmachinerestore +// +kubebuilder:printcolumn:name="Phase",type="string",JSONPath=".status.phase",description="VirtualMachineRestore phase." +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="VirtualMachineRestore age." +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type VirtualMachineRestore struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec VirtualMachineRestoreSpec `json:"spec"` + Status VirtualMachineRestoreStatus `json:"status,omitempty"` +} + +// VirtualMachineRestoreList contains a list of `VirtualMachineRestore` +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type VirtualMachineRestoreList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + Items []VirtualMachineRestore `json:"items"` +} + +type VirtualMachineRestoreSpec struct { + // The name of virtual machine snapshot to restore the virtual machine. + // + // +kubebuilder:validation:MinLength=1 + VirtualMachineSnapshotName string `json:"virtualMachineSnapshotName"` + // Redefining the virtual machine resource names. + NameReplacements []NameReplacement `json:"nameReplacements,omitempty"` +} + +type VirtualMachineRestoreStatus struct { + Phase VirtualMachineRestorePhase `json:"phase"` + // Contains details of the current state of this API Resource. + Conditions []metav1.Condition `json:"conditions,omitempty"` + // The generation last processed by the controller. + ObservedGeneration int64 `json:"observedGeneration,omitempty"` +} + +// NameReplacement represents rule to redefine the virtual machine resource names. +type NameReplacement struct { + // The selector to choose resources for name replacement. + From NameReplacementFrom `json:"from"` + // The new resource name. + To string `json:"to"` +} + +// NameReplacementFrom represents the selector to choose resources for name replacement. +type NameReplacementFrom struct { + // The kind of resource to rename. + Kind string `json:"kind,omitempty"` + // The current name of resource to rename. + Name string `json:"name"` +} + +// VirtualMachineRestorePhase defines current status of resource: +// * Pending - the resource has been created and is on a waiting queue. +// * InProgress - the process of creating the virtual machine from the snapshot is currently underway. +// * Ready - the virtual machine creation from the snapshot has successfully completed. +// * Failed - an error occurred during the virtual machine creation process. +// * Terminating - the resource is in the process of being deleted. +// +// +kubebuilder:validation:Enum={Pending,InProgress,Ready,Failed,Terminating} +type VirtualMachineRestorePhase string + +const ( + VirtualMachineRestorePhasePending VirtualMachineRestorePhase = "Pending" + VirtualMachineRestorePhaseInProgress VirtualMachineRestorePhase = "InProgress" + VirtualMachineRestorePhaseReady VirtualMachineRestorePhase = "Ready" + VirtualMachineRestorePhaseFailed VirtualMachineRestorePhase = "Failed" + VirtualMachineRestorePhaseTerminating VirtualMachineRestorePhase = "Terminating" +) diff --git a/api/core/v1alpha2/virtual_machine_snapshot.go b/api/core/v1alpha2/virtual_machine_snapshot.go index d0ad91ba6..bf6fa437e 100644 --- a/api/core/v1alpha2/virtual_machine_snapshot.go +++ b/api/core/v1alpha2/virtual_machine_snapshot.go @@ -57,7 +57,7 @@ type VirtualMachineSnapshotList struct { type VirtualMachineSnapshotSpec struct { // The name of virtual machine to take snapshot. // - // +required + // +kubebuilder:validation:MinLength=1 VirtualMachineName string `json:"virtualMachineName"` // Create a snapshot of a virtual machine only if it is possible to freeze the machine through the agent. // @@ -70,7 +70,7 @@ type VirtualMachineSnapshotSpec struct { // +kubebuilder:default:="Always" KeepIPAddress KeepIPAddress `json:"keepIPAddress"` // +optional - VolumeSnapshotClassNames []VolumeSnapshotClassName `json:"volumeSnapshotClassNames"` + VolumeSnapshotClasses []VolumeSnapshotClassName `json:"volumeSnapshotClasses"` } type VirtualMachineSnapshotStatus struct { @@ -79,7 +79,7 @@ type VirtualMachineSnapshotStatus struct { Consistent *bool `json:"consistent,omitempty"` // The name of underlying `Secret`, created for virtual machine snapshotting. VirtualMachineSnapshotSecretName string `json:"virtualMachineSnapshotSecretName,omitempty"` - // The list of `VirtualDiskSnapshot` names that associated with the virtual machine and taken for snapshotting. + // The list of `VirtualDiskSnapshot` names for the snapshots taken from the virtual disks of the associated virtual machine. VirtualDiskSnapshotNames []string `json:"virtualDiskSnapshotNames,omitempty"` Conditions []metav1.Condition `json:"conditions,omitempty"` // The generation last processed by the controller. @@ -89,12 +89,8 @@ type VirtualMachineSnapshotStatus struct { // VolumeSnapshotClassName defines `StorageClass` and `VolumeSnapshotClass` binding. type VolumeSnapshotClassName struct { // The `StorageClass` name associated with `VolumeSnapshotClass`. - // - // +required StorageClassName string `json:"storageClassName"` // The name of `VolumeSnapshotClass` to use for virtual disk snapshotting. - // - // +required VolumeSnapshotClassName string `json:"volumeSnapshotClassName"` } @@ -107,8 +103,8 @@ type VolumeSnapshotClassName struct { type KeepIPAddress string const ( - KeepIPAddressAlways VirtualMachineSnapshotPhase = "Always" - KeepIPAddressNever VirtualMachineSnapshotPhase = "Never" + KeepIPAddressAlways KeepIPAddress = "Always" + KeepIPAddressNever KeepIPAddress = "Never" ) // VirtualMachineSnapshotPhase defines current status of resource: diff --git a/api/core/v1alpha2/vm-restore-condition/condition.go b/api/core/v1alpha2/vm-restore-condition/condition.go new file mode 100644 index 000000000..c6f070677 --- /dev/null +++ b/api/core/v1alpha2/vm-restore-condition/condition.go @@ -0,0 +1,64 @@ +/* +Copyright 2024 Flant JSC + +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. +*/ + +package vmrestorecondition + +// Type represents the various condition types for the `VirtualMachineSnapshot`. +type Type string + +const ( + // VirtualMachineSnapshotReadyToUseType indicates that the `VirtualMachine` is ready for snapshotting. + VirtualMachineSnapshotReadyToUseType Type = "VirtualMachineSnapshotReadyToUse" + // VirtualMachineRestoreReadyType indicates that the virtual machine snapshot has been successfully taken and is ready for restore. + VirtualMachineRestoreReadyType Type = "VirtualMachineRestoreReady" +) + +type ( + // VirtualMachineSnapshotReadyToUseReason represents the various reasons for the `VirtualMachineReady` condition type. + VirtualMachineSnapshotReadyToUseReason string + // VirtualMachineRestoreReadyReason represents the various reasons for the `VirtualMachineSnapshotReady` condition type. + VirtualMachineRestoreReadyReason string +) + +const ( + // VirtualMachineSnapshotUnknown represents unknown condition state. + VirtualMachineSnapshotUnknown VirtualMachineSnapshotReadyToUseReason = "Unknown" + // VirtualMachineSnapshotNotFound indicates that the specified virtual machine snapshot is absent. + VirtualMachineSnapshotNotFound VirtualMachineSnapshotReadyToUseReason = "VirtualMachineSnapshotNotFound" + VirtualMachineSnapshotNotReady VirtualMachineSnapshotReadyToUseReason = "VirtualMachineSnapshotNotReady" + VirtualMachineSnapshotReadyToUse VirtualMachineSnapshotReadyToUseReason = "VirtualMachineSnapshotReadyToUse" + + // VirtualMachineRestoreUnknown represents unknown condition state. + VirtualMachineRestoreUnknown VirtualMachineRestoreReadyReason = "Unknown" + // VirtualMachineRestoreConflict signifies that the snapshot process has failed. + VirtualMachineRestoreConflict VirtualMachineRestoreReadyReason = "VirtualMachineRestoreConflict" + // VirtualMachineRestoreFailed signifies that the snapshot process has failed. + VirtualMachineRestoreFailed VirtualMachineRestoreReadyReason = "VirtualMachineRestoreFailed" + // VirtualMachineRestoreReady signifies that the snapshot process has failed. + VirtualMachineRestoreReady VirtualMachineRestoreReadyReason = "VirtualMachineRestoreReady" +) + +func (t Type) String() string { + return string(t) +} + +func (r VirtualMachineSnapshotReadyToUseReason) String() string { + return string(r) +} + +func (r VirtualMachineRestoreReadyReason) String() string { + return string(r) +} diff --git a/api/core/v1alpha2/vmcondition/condition.go b/api/core/v1alpha2/vmcondition/condition.go index f9922d351..a5e496244 100644 --- a/api/core/v1alpha2/vmcondition/condition.go +++ b/api/core/v1alpha2/vmcondition/condition.go @@ -89,7 +89,7 @@ const ( ReasonFilesystemFrozen Reason = "Frozen" ReasonFilesystemNotReady Reason = "NotReady" - ReasonChosenForSnapshotting Reason = "WaitingForTheSnapshotting" + WaitingForTheSnapshotToStart Reason = "WaitingForTheSnapshotToStart" ReasonSnapshottingInProgress Reason = "SnapshottingInProgress" ReasonSizingPolicyMatched Reason = "SizingPolicyMatched" diff --git a/api/core/v1alpha2/vmscondition/condition.go b/api/core/v1alpha2/vmscondition/condition.go index 1d257672e..47ae72bf3 100644 --- a/api/core/v1alpha2/vmscondition/condition.go +++ b/api/core/v1alpha2/vmscondition/condition.go @@ -17,7 +17,7 @@ limitations under the License. package vmscondition // Type represents the various condition types for the `VirtualMachineSnapshot`. -type Type = string +type Type string const ( // VirtualMachineReadyType indicates that the `VirtualMachine` is ready for snapshotting. @@ -28,9 +28,9 @@ const ( type ( // VirtualMachineReadyReason represents the various reasons for the `VirtualMachineReady` condition type. - VirtualMachineReadyReason = string + VirtualMachineReadyReason string // VirtualMachineSnapshotReadyReason represents the various reasons for the `VirtualMachineSnapshotReady` condition type. - VirtualMachineSnapshotReadyReason = string + VirtualMachineSnapshotReadyReason string ) const ( @@ -64,3 +64,15 @@ const ( // VirtualMachineSnapshotFailed signifies that the snapshot process has failed. VirtualMachineSnapshotFailed VirtualMachineSnapshotReadyReason = "VirtualMachineSnapshotFailed" ) + +func (t Type) String() string { + return string(t) +} + +func (r VirtualMachineReadyReason) String() string { + return string(r) +} + +func (r VirtualMachineSnapshotReadyReason) String() string { + return string(r) +} diff --git a/api/core/v1alpha2/zz_generated.deepcopy.go b/api/core/v1alpha2/zz_generated.deepcopy.go index a96a93fbc..b458df748 100644 --- a/api/core/v1alpha2/zz_generated.deepcopy.go +++ b/api/core/v1alpha2/zz_generated.deepcopy.go @@ -550,6 +550,39 @@ func (in *MemoryStatus) DeepCopy() *MemoryStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NameReplacement) DeepCopyInto(out *NameReplacement) { + *out = *in + out.From = in.From + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NameReplacement. +func (in *NameReplacement) DeepCopy() *NameReplacement { + if in == nil { + return nil + } + out := new(NameReplacement) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NameReplacementFrom) DeepCopyInto(out *NameReplacementFrom) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NameReplacementFrom. +func (in *NameReplacementFrom) DeepCopy() *NameReplacementFrom { + if in == nil { + return nil + } + out := new(NameReplacementFrom) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NodeSelector) DeepCopyInto(out *NodeSelector) { *out = *in @@ -2132,6 +2165,111 @@ func (in *VirtualMachinePod) DeepCopy() *VirtualMachinePod { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VirtualMachineRestore) DeepCopyInto(out *VirtualMachineRestore) { + *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 VirtualMachineRestore. +func (in *VirtualMachineRestore) DeepCopy() *VirtualMachineRestore { + if in == nil { + return nil + } + out := new(VirtualMachineRestore) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VirtualMachineRestore) 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 *VirtualMachineRestoreList) DeepCopyInto(out *VirtualMachineRestoreList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]VirtualMachineRestore, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualMachineRestoreList. +func (in *VirtualMachineRestoreList) DeepCopy() *VirtualMachineRestoreList { + if in == nil { + return nil + } + out := new(VirtualMachineRestoreList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VirtualMachineRestoreList) 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 *VirtualMachineRestoreSpec) DeepCopyInto(out *VirtualMachineRestoreSpec) { + *out = *in + if in.NameReplacements != nil { + in, out := &in.NameReplacements, &out.NameReplacements + *out = make([]NameReplacement, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualMachineRestoreSpec. +func (in *VirtualMachineRestoreSpec) DeepCopy() *VirtualMachineRestoreSpec { + if in == nil { + return nil + } + out := new(VirtualMachineRestoreSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VirtualMachineRestoreStatus) DeepCopyInto(out *VirtualMachineRestoreStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.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 VirtualMachineRestoreStatus. +func (in *VirtualMachineRestoreStatus) DeepCopy() *VirtualMachineRestoreStatus { + if in == nil { + return nil + } + out := new(VirtualMachineRestoreStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VirtualMachineSnapshot) DeepCopyInto(out *VirtualMachineSnapshot) { *out = *in @@ -2196,8 +2334,8 @@ func (in *VirtualMachineSnapshotList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VirtualMachineSnapshotSpec) DeepCopyInto(out *VirtualMachineSnapshotSpec) { *out = *in - if in.VolumeSnapshotClassNames != nil { - in, out := &in.VolumeSnapshotClassNames, &out.VolumeSnapshotClassNames + if in.VolumeSnapshotClasses != nil { + in, out := &in.VolumeSnapshotClasses, &out.VolumeSnapshotClasses *out = make([]VolumeSnapshotClassName, len(*in)) copy(*out, *in) } diff --git a/api/pkg/apiserver/api/generated/openapi/zz_generated.openapi.go b/api/pkg/apiserver/api/generated/openapi/zz_generated.openapi.go index f0217065e..819e93874 100644 --- a/api/pkg/apiserver/api/generated/openapi/zz_generated.openapi.go +++ b/api/pkg/apiserver/api/generated/openapi/zz_generated.openapi.go @@ -59,6 +59,8 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/deckhouse/virtualization/api/core/v1alpha2.MemoryMinMax": schema_virtualization_api_core_v1alpha2_MemoryMinMax(ref), "github.com/deckhouse/virtualization/api/core/v1alpha2.MemorySpec": schema_virtualization_api_core_v1alpha2_MemorySpec(ref), "github.com/deckhouse/virtualization/api/core/v1alpha2.MemoryStatus": schema_virtualization_api_core_v1alpha2_MemoryStatus(ref), + "github.com/deckhouse/virtualization/api/core/v1alpha2.NameReplacement": schema_virtualization_api_core_v1alpha2_NameReplacement(ref), + "github.com/deckhouse/virtualization/api/core/v1alpha2.NameReplacementFrom": schema_virtualization_api_core_v1alpha2_NameReplacementFrom(ref), "github.com/deckhouse/virtualization/api/core/v1alpha2.NodeSelector": schema_virtualization_api_core_v1alpha2_NodeSelector(ref), "github.com/deckhouse/virtualization/api/core/v1alpha2.Provisioning": schema_virtualization_api_core_v1alpha2_Provisioning(ref), "github.com/deckhouse/virtualization/api/core/v1alpha2.ResourcesStatus": schema_virtualization_api_core_v1alpha2_ResourcesStatus(ref), @@ -122,6 +124,10 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualMachineOperationStatus": schema_virtualization_api_core_v1alpha2_VirtualMachineOperationStatus(ref), "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualMachinePhaseTransitionTimestamp": schema_virtualization_api_core_v1alpha2_VirtualMachinePhaseTransitionTimestamp(ref), "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualMachinePod": schema_virtualization_api_core_v1alpha2_VirtualMachinePod(ref), + "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualMachineRestore": schema_virtualization_api_core_v1alpha2_VirtualMachineRestore(ref), + "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualMachineRestoreList": schema_virtualization_api_core_v1alpha2_VirtualMachineRestoreList(ref), + "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualMachineRestoreSpec": schema_virtualization_api_core_v1alpha2_VirtualMachineRestoreSpec(ref), + "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualMachineRestoreStatus": schema_virtualization_api_core_v1alpha2_VirtualMachineRestoreStatus(ref), "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualMachineSnapshot": schema_virtualization_api_core_v1alpha2_VirtualMachineSnapshot(ref), "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualMachineSnapshotList": schema_virtualization_api_core_v1alpha2_VirtualMachineSnapshotList(ref), "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualMachineSnapshotSpec": schema_virtualization_api_core_v1alpha2_VirtualMachineSnapshotSpec(ref), @@ -1592,6 +1598,66 @@ func schema_virtualization_api_core_v1alpha2_MemoryStatus(ref common.ReferenceCa } } +func schema_virtualization_api_core_v1alpha2_NameReplacement(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "NameReplacement represents rule to redefine the virtual machine resource names.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "from": { + SchemaProps: spec.SchemaProps{ + Description: "The selector to choose resources for name replacement.", + Default: map[string]interface{}{}, + Ref: ref("github.com/deckhouse/virtualization/api/core/v1alpha2.NameReplacementFrom"), + }, + }, + "to": { + SchemaProps: spec.SchemaProps{ + Description: "The new resource name.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"from", "to"}, + }, + }, + Dependencies: []string{ + "github.com/deckhouse/virtualization/api/core/v1alpha2.NameReplacementFrom"}, + } +} + +func schema_virtualization_api_core_v1alpha2_NameReplacementFrom(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "NameReplacementFrom represents the selector to choose resources for name replacement.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "The kind of resource to rename.", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "The current name of resource to rename.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name"}, + }, + }, + } +} + func schema_virtualization_api_core_v1alpha2_NodeSelector(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -4168,6 +4234,183 @@ func schema_virtualization_api_core_v1alpha2_VirtualMachinePod(ref common.Refere } } +func schema_virtualization_api_core_v1alpha2_VirtualMachineRestore(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "VirtualMachineRestore provides a resource that allows to restore a snapshot of the virtual machine and all its resources.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + 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{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + 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{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualMachineRestoreSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualMachineRestoreStatus"), + }, + }, + }, + Required: []string{"spec"}, + }, + }, + Dependencies: []string{ + "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualMachineRestoreSpec", "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualMachineRestoreStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_virtualization_api_core_v1alpha2_VirtualMachineRestoreList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "VirtualMachineRestoreList contains a list of `VirtualMachineRestore`", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + 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{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + 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{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualMachineRestore"), + }, + }, + }, + }, + }, + }, + Required: []string{"metadata", "items"}, + }, + }, + Dependencies: []string{ + "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualMachineRestore", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_virtualization_api_core_v1alpha2_VirtualMachineRestoreSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "virtualMachineSnapshotName": { + SchemaProps: spec.SchemaProps{ + Description: "The name of virtual machine snapshot to restore the virtual machine.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "nameReplacements": { + SchemaProps: spec.SchemaProps{ + Description: "Redefining the virtual machine resource names.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/deckhouse/virtualization/api/core/v1alpha2.NameReplacement"), + }, + }, + }, + }, + }, + }, + Required: []string{"virtualMachineSnapshotName"}, + }, + }, + Dependencies: []string{ + "github.com/deckhouse/virtualization/api/core/v1alpha2.NameReplacement"}, + } +} + +func schema_virtualization_api_core_v1alpha2_VirtualMachineRestoreStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "phase": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "conditions": { + SchemaProps: spec.SchemaProps{ + Description: "Contains details of the current state of this API Resource.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Condition"), + }, + }, + }, + }, + }, + "observedGeneration": { + SchemaProps: spec.SchemaProps{ + Description: "The generation last processed by the controller.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + }, + Required: []string{"phase"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + } +} + func schema_virtualization_api_core_v1alpha2_VirtualMachineSnapshot(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -4294,7 +4537,7 @@ func schema_virtualization_api_core_v1alpha2_VirtualMachineSnapshotSpec(ref comm Format: "", }, }, - "volumeSnapshotClassNames": { + "volumeSnapshotClasses": { SchemaProps: spec.SchemaProps{ Type: []string{"array"}, Items: &spec.SchemaOrArray{ @@ -4345,7 +4588,7 @@ func schema_virtualization_api_core_v1alpha2_VirtualMachineSnapshotStatus(ref co }, "virtualDiskSnapshotNames": { SchemaProps: spec.SchemaProps{ - Description: "The list of `VirtualDiskSnapshot` names that associated with the virtual machine and taken for snapshotting.", + Description: "The list of `VirtualDiskSnapshot` names for the snapshots taken from the virtual disks of the associated virtual machine.", Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ diff --git a/api/scripts/update-codegen.sh b/api/scripts/update-codegen.sh index a2b67a2dd..dabe03936 100755 --- a/api/scripts/update-codegen.sh +++ b/api/scripts/update-codegen.sh @@ -32,7 +32,7 @@ function source::settings { MODULE="github.com/deckhouse/virtualization/api" PREFIX_GROUP="virtualization.deckhouse.io_" # TODO: Temporary filter until all CRDs become auto-generated. - ALLOWED_RESOURCE_GEN_CRD=("VirtualMachineClass" "VirtualMachineBlockDeviceAttachment" "ExampleKind1" "ExampleKind2") + ALLOWED_RESOURCE_GEN_CRD=("VirtualMachineClass" "VirtualMachineBlockDeviceAttachment" "VirtualMachineSnapshot" "VirtualMachineRestore" "ExampleKind1" "ExampleKind2") source "${CODEGEN_PKG}/kube_codegen.sh" } diff --git a/crds/doc-ru-virtualmachinerestores.yaml b/crds/doc-ru-virtualmachinerestores.yaml new file mode 100644 index 000000000..516e99d05 --- /dev/null +++ b/crds/doc-ru-virtualmachinerestores.yaml @@ -0,0 +1,70 @@ +spec: + versions: + - name: v1alpha2 + schema: + openAPIV3Schema: + description: |- + `VirtualMachineSnapshot` предоставляет ресурс для создания снимков виртуальных машин. + properties: + spec: + properties: + keepIPAddress: + description: |- + Сохранить ip адрес виртуальной машины или нет: + + * Always - при создании снимка адреса виртуальный машины будет конвертирован из `Auto` в `Static` и сохранен. + * Never - при создании снимка адрес виртуальной машины не будет конвертирован. + requiredConsistency: + description: |- + Создавать снимок виртуальной машины только в том случае, если возможно заморозить её через агента. + + Если значение установлено в true, снимок виртуальной машины будет создан только в следующих случаях: + - виртуальная машина выключена; + - виртуальная машина с агентом, операция заморозки прошла успешно. + virtualMachineName: + description: Имя виртуальной машины для создания снимка. + volumeSnapshotClasses: + items: + description: Устанавливает соответствие между `StorageClass` и `VolumeSnapshotClass`. + properties: + storageClassName: + description: Имя ресурса `StorageClass`, соответствующее указанному ресурсу `VolumeSnapshotClass`. + volumeSnapshotClassName: + description: Имя ресурса `VolumeSnapshotClass`, который будет использован для создания снимков виртуальных дисков с соответствующим классом хранения. + status: + properties: + conditions: + description: | + Последнее подтвержденное состояние данного ресурса. + items: + properties: + lastProbeTime: + description: Время проверки условия. + lastTransitionTime: + description: Время перехода условия из одного состояния в другое. + message: + description: Удобочитаемое сообщение с подробной информацией о последнем переходе. + reason: + description: Краткая причина последнего перехода состояния. + status: + description: | + Статус условия. Возможные значения: `True`, `False`, `Unknown`. + type: + description: Тип условия. + consistent: + description: Снимок виртуальной машины консистентен. + observedGeneration: + description: Поколение ресурса, которое в последний раз обрабатывалось контроллером. + phase: + description: |- + Текущее состояние ресурса `VirtualMachineSnapshot`: + + * `Pending` — ресурс был создан и находится в очереди ожидания. + * `InProgress` — идет процесс создания снимка виртуальной машины. + * `Ready` — создание снимка успешно завершено, и снимок виртуальной машины доступен для использования. + * `Failed` — произошла ошибка во время процесса создания снимка виртуальной машины. + * `Terminating` — ресурс находится в процессе удаления. + virtualDiskSnapshotNames: + description: Имена созданных снимков виртуальных дисков. + virtualMachineSnapshotSecretName: + description: Имя созданного секрета с информацией о снимке виртуальной машины. diff --git a/crds/doc-ru-virtualmachinesnapshots.yaml b/crds/doc-ru-virtualmachinesnapshots.yaml index e2e925437..516e99d05 100644 --- a/crds/doc-ru-virtualmachinesnapshots.yaml +++ b/crds/doc-ru-virtualmachinesnapshots.yaml @@ -23,7 +23,7 @@ spec: - виртуальная машина с агентом, операция заморозки прошла успешно. virtualMachineName: description: Имя виртуальной машины для создания снимка. - volumeSnapshotClassNames: + volumeSnapshotClasses: items: description: Устанавливает соответствие между `StorageClass` и `VolumeSnapshotClass`. properties: @@ -65,6 +65,6 @@ spec: * `Failed` — произошла ошибка во время процесса создания снимка виртуальной машины. * `Terminating` — ресурс находится в процессе удаления. virtualDiskSnapshotNames: - description: Имена созданных снимков виртаульных дисков. + description: Имена созданных снимков виртуальных дисков. virtualMachineSnapshotSecretName: description: Имя созданного секрета с информацией о снимке виртуальной машины. diff --git a/crds/virtualmachinerestores.yaml b/crds/virtualmachinerestores.yaml new file mode 100644 index 000000000..af09ad520 --- /dev/null +++ b/crds/virtualmachinerestores.yaml @@ -0,0 +1,197 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + labels: + heritage: deckhouse + module: virtualization + name: virtualmachinerestores.virtualization.deckhouse.io +spec: + group: virtualization.deckhouse.io + names: + categories: + - virtualization + kind: VirtualMachineRestore + listKind: VirtualMachineRestoreList + plural: virtualmachinerestores + shortNames: + - vmrestore + - vmrestores + singular: virtualmachinerestore + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: VirtualMachineRestore phase. + jsonPath: .status.phase + name: Phase + type: string + - description: VirtualMachineRestore age. + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: + VirtualMachineRestore provides a resource that allows to restore + a snapshot of the virtual machine and all its resources. + 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: + properties: + nameReplacements: + description: Redefining the virtual machine resource names. + items: + description: + NameReplacement represents rule to redefine the virtual + machine resource names. + properties: + from: + description: The selector to choose resources for name replacement. + properties: + kind: + description: The kind of resource to rename. + type: string + name: + description: The current name of resource to rename. + type: string + required: + - name + type: object + to: + description: The new resource name. + type: string + required: + - from + - to + type: object + type: array + virtualMachineSnapshotName: + description: + The name of virtual machine snapshot to restore the virtual + machine. + minLength: 1 + type: string + required: + - virtualMachineSnapshotName + type: object + status: + properties: + conditions: + description: Contains details of the current state of this API Resource. + items: + description: + "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the 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: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "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. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + observedGeneration: + description: The generation last processed by the controller. + format: int64 + type: integer + phase: + description: |- + VirtualMachineRestorePhase defines current status of resource: + * Pending - the resource has been created and is on a waiting queue. + * InProgress - the process of creating the virtual machine from the snapshot is currently underway. + * Ready - the virtual machine creation from the snapshot has successfully completed. + * Failed - an error occurred during the virtual machine creation process. + * Terminating - the resource is in the process of being deleted. + enum: + - Pending + - InProgress + - Ready + - Failed + - Terminating + type: string + required: + - phase + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/crds/virtualmachinesnapshots.yaml b/crds/virtualmachinesnapshots.yaml index 469dbcb06..9ce05c12e 100644 --- a/crds/virtualmachinesnapshots.yaml +++ b/crds/virtualmachinesnapshots.yaml @@ -85,8 +85,9 @@ spec: type: boolean virtualMachineName: description: The name of virtual machine to take snapshot. + minLength: 1 type: string - volumeSnapshotClassNames: + volumeSnapshotClasses: items: description: VolumeSnapshotClassName defines `StorageClass` and @@ -208,8 +209,8 @@ spec: type: string virtualDiskSnapshotNames: description: - The list of `VirtualDiskSnapshot` names that associated - with the virtual machine and taken for snapshotting. + The list of `VirtualDiskSnapshot` names for the snapshots + taken from the virtual disks of the associated virtual machine. items: type: string type: array diff --git a/images/virtualization-artifact/cmd/virtualization-controller/main.go b/images/virtualization-artifact/cmd/virtualization-controller/main.go index d8a6be21e..c119350c7 100644 --- a/images/virtualization-artifact/cmd/virtualization-controller/main.go +++ b/images/virtualization-artifact/cmd/virtualization-controller/main.go @@ -24,11 +24,13 @@ import ( "runtime" "strconv" "strings" + "time" vsv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1" extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiruntime "k8s.io/apimachinery/pkg/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/utils/ptr" virtv1 "kubevirt.io/api/core/v1" cdiv1beta1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1" "sigs.k8s.io/controller-runtime/pkg/client/config" @@ -48,6 +50,7 @@ import ( "github.com/deckhouse/virtualization-controller/pkg/controller/vmip" "github.com/deckhouse/virtualization-controller/pkg/controller/vmiplease" "github.com/deckhouse/virtualization-controller/pkg/controller/vmop" + "github.com/deckhouse/virtualization-controller/pkg/controller/vmrestore" "github.com/deckhouse/virtualization-controller/pkg/controller/vmsnapshot" "github.com/deckhouse/virtualization-controller/pkg/logger" "github.com/deckhouse/virtualization/api/client/kubeclient" @@ -167,6 +170,7 @@ func main() { LeaderElectionID: "d8-virt-operator-leader-election-helper", LeaderElectionResourceLock: "leases", Scheme: scheme, + GracefulShutdownTimeout: ptr.To(10 * time.Minute), } if pprofBindAddr != "" { managerOpts.PprofBindAddress = pprofBindAddr @@ -267,6 +271,11 @@ func main() { os.Exit(1) } + if _, err = vmrestore.NewController(ctx, mgr, log); err != nil { + log.Error(err.Error()) + os.Exit(1) + } + if err = vmop.SetupController(ctx, mgr, log); err != nil { log.Error(err.Error()) os.Exit(1) diff --git a/images/virtualization-artifact/pkg/controller/conditions/builder.go b/images/virtualization-artifact/pkg/controller/conditions/builder.go index 141a289fd..266a224a3 100644 --- a/images/virtualization-artifact/pkg/controller/conditions/builder.go +++ b/images/virtualization-artifact/pkg/controller/conditions/builder.go @@ -54,8 +54,8 @@ func NewConditionBuilder(conditionType Stringer) *ConditionBuilder { } type ConditionBuilder struct { - status metav1.ConditionStatus conditionType Stringer + status metav1.ConditionStatus reason string message string generation int64 @@ -72,6 +72,14 @@ func (c *ConditionBuilder) Condition() metav1.Condition { } } +func (c *ConditionBuilder) Parse(condition metav1.Condition) *ConditionBuilder { + c.status = condition.Status + c.reason = condition.Reason + c.message = condition.Message + c.generation = condition.ObservedGeneration + return c +} + func (c *ConditionBuilder) Status(status metav1.ConditionStatus) *ConditionBuilder { c.status = status return c diff --git a/images/virtualization-artifact/pkg/controller/cvi/cvi_controller.go b/images/virtualization-artifact/pkg/controller/cvi/cvi_controller.go index 2db06b974..c28a605ab 100644 --- a/images/virtualization-artifact/pkg/controller/cvi/cvi_controller.go +++ b/images/virtualization-artifact/pkg/controller/cvi/cvi_controller.go @@ -19,6 +19,7 @@ package cvi import ( "context" "log/slog" + "time" corev1 "k8s.io/api/core/v1" "k8s.io/utils/ptr" @@ -77,9 +78,10 @@ func NewController( ) cviController, err := controller.New(ControllerName, mgr, controller.Options{ - Reconciler: reconciler, - RecoverPanic: ptr.To(true), - LogConstructor: logger.NewConstructor(log), + Reconciler: reconciler, + RecoverPanic: ptr.To(true), + LogConstructor: logger.NewConstructor(log), + CacheSyncTimeout: 10 * time.Minute, }) if err != nil { return nil, err diff --git a/images/virtualization-artifact/pkg/controller/gc/gc_reconciler.go b/images/virtualization-artifact/pkg/controller/gc/gc_reconciler.go index c89cdfc00..845777855 100644 --- a/images/virtualization-artifact/pkg/controller/gc/gc_reconciler.go +++ b/images/virtualization-artifact/pkg/controller/gc/gc_reconciler.go @@ -19,6 +19,7 @@ package gc import ( "context" "log/slog" + "time" "k8s.io/client-go/tools/record" "k8s.io/utils/ptr" @@ -91,8 +92,9 @@ func (r *Reconciler) SetupWithManager(controllerName string, mgr ctrl.Manager, l }, })). WithOptions(controller.Options{ - RecoverPanic: ptr.To(true), - LogConstructor: logger.NewConstructor(log), + RecoverPanic: ptr.To(true), + LogConstructor: logger.NewConstructor(log), + CacheSyncTimeout: 10 * time.Minute, }). WatchesRawSource(r.watchSource, nil). Complete(r) diff --git a/images/virtualization-artifact/pkg/controller/indexer/indexer.go b/images/virtualization-artifact/pkg/controller/indexer/indexer.go index 95e1999e9..df3fab723 100644 --- a/images/virtualization-artifact/pkg/controller/indexer/indexer.go +++ b/images/virtualization-artifact/pkg/controller/indexer/indexer.go @@ -35,6 +35,11 @@ const ( IndexFieldVDByVDSnapshot = "spec.DataSource.ObjectRef.Name,.Kind=VirtualDiskSnapshot" + IndexFieldVMSnapshotByVM = "spec.virtualMachineName" + IndexFieldVMSnapshotByVDSnapshot = "status.virtualDiskSnapshotNames" + + IndexFieldVMRestoreByVMSnapshot = "spec.virtualMachineSnapshotName" + IndexFieldVMIPByVM = "status.virtualMachine" ) @@ -48,6 +53,9 @@ func IndexALL(ctx context.Context, mgr manager.Manager) error { IndexVMByCVI, IndexVMIPLeaseByVMIP, IndexVDByVDSnapshot, + IndexVMSnapshotByVM, + IndexVMSnapshotByVDSnapshot, + IndexVMRestoreByVMSnapshot, IndexVMIPByVM, } { if err := fn(ctx, mgr); err != nil { diff --git a/images/virtualization-artifact/pkg/controller/indexer/vm_restore_indexer.go b/images/virtualization-artifact/pkg/controller/indexer/vm_restore_indexer.go new file mode 100644 index 000000000..552b59bd6 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/indexer/vm_restore_indexer.go @@ -0,0 +1,37 @@ +/* +Copyright 2024 Flant JSC + +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. +*/ + +package indexer + +import ( + "context" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" + + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +func IndexVMRestoreByVMSnapshot(ctx context.Context, mgr manager.Manager) error { + return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.VirtualMachineRestore{}, IndexFieldVMRestoreByVMSnapshot, func(object client.Object) []string { + vmSnapshot, ok := object.(*virtv2.VirtualMachineRestore) + if !ok || vmSnapshot == nil { + return nil + } + + return []string{vmSnapshot.Spec.VirtualMachineSnapshotName} + }) +} diff --git a/images/virtualization-artifact/pkg/controller/indexer/vm_snapshot_indexer.go b/images/virtualization-artifact/pkg/controller/indexer/vm_snapshot_indexer.go new file mode 100644 index 000000000..cafcbde3d --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/indexer/vm_snapshot_indexer.go @@ -0,0 +1,48 @@ +/* +Copyright 2024 Flant JSC + +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. +*/ + +package indexer + +import ( + "context" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" + + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +func IndexVMSnapshotByVM(ctx context.Context, mgr manager.Manager) error { + return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.VirtualMachineSnapshot{}, IndexFieldVMSnapshotByVM, func(object client.Object) []string { + vmSnapshot, ok := object.(*virtv2.VirtualMachineSnapshot) + if !ok || vmSnapshot == nil { + return nil + } + + return []string{vmSnapshot.Spec.VirtualMachineName} + }) +} + +func IndexVMSnapshotByVDSnapshot(ctx context.Context, mgr manager.Manager) error { + return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.VirtualMachineSnapshot{}, IndexFieldVMSnapshotByVDSnapshot, func(object client.Object) []string { + vmSnapshot, ok := object.(*virtv2.VirtualMachineSnapshot) + if !ok || vmSnapshot == nil { + return nil + } + + return vmSnapshot.Status.VirtualDiskSnapshotNames + }) +} diff --git a/images/virtualization-artifact/pkg/controller/service/restore_service.go b/images/virtualization-artifact/pkg/controller/service/restore_service.go new file mode 100644 index 000000000..b00ba23fb --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/service/restore_service.go @@ -0,0 +1,68 @@ +/* +Copyright 2024 Flant JSC + +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. +*/ + +package service + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + + "github.com/deckhouse/virtualization-controller/pkg/sdk/framework/helper" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +type RestoreService struct { + client Client +} + +func NewRestoreService(client Client) *RestoreService { + return &RestoreService{ + client: client, + } +} + +func (s *RestoreService) CreateVirtualMachine(ctx context.Context, vm *virtv2.VirtualMachine) (*virtv2.VirtualMachine, error) { + err := s.client.Create(ctx, vm) + if err != nil && !k8serrors.IsAlreadyExists(err) { + return nil, err + } + + return vm, nil +} + +func (s *RestoreService) CreateVirtualDisk(ctx context.Context, vd *virtv2.VirtualDisk) (*virtv2.VirtualDisk, error) { + err := s.client.Create(ctx, vd) + if err != nil && !k8serrors.IsAlreadyExists(err) { + return nil, err + } + + return vd, nil +} + +func (s *RestoreService) GetVirtualMachineSnapshot(ctx context.Context, name, namespace string) (*virtv2.VirtualMachineSnapshot, error) { + return helper.FetchObject(ctx, types.NamespacedName{Namespace: namespace, Name: name}, s.client, &virtv2.VirtualMachineSnapshot{}) +} + +func (s *RestoreService) GetVirtualDiskSnapshot(ctx context.Context, name, namespace string) (*virtv2.VirtualDiskSnapshot, error) { + return helper.FetchObject(ctx, types.NamespacedName{Namespace: namespace, Name: name}, s.client, &virtv2.VirtualDiskSnapshot{}) +} + +func (s *RestoreService) GetSecret(ctx context.Context, name, namespace string) (*corev1.Secret, error) { + return helper.FetchObject(ctx, types.NamespacedName{Namespace: namespace, Name: name}, s.client, &corev1.Secret{}) +} diff --git a/images/virtualization-artifact/pkg/controller/service/restorer/keys.go b/images/virtualization-artifact/pkg/controller/service/restorer/keys.go new file mode 100644 index 000000000..ff6b94f9a --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/service/restorer/keys.go @@ -0,0 +1,40 @@ +/* +Copyright 2024 Flant JSC + +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. +*/ + +package restorer + +const ( + virtualMachineKey = "vm" + virtualMachineBlockDeviceAttachmentKey = "vmbdas" + virtualMachineIPAddressKey = "vmip" + provisionerKey = "provisioner" +) + +func getVirtualMachineKey() string { + return virtualMachineKey +} + +func getVirtualMachineBlockDeviceAttachmentKey() string { + return virtualMachineBlockDeviceAttachmentKey +} + +func getVirtualMachineIPAddressKey() string { + return virtualMachineIPAddressKey +} + +func getProvisionerKey() string { + return provisionerKey +} diff --git a/images/virtualization-artifact/pkg/controller/service/restorer/restorer.go b/images/virtualization-artifact/pkg/controller/service/restorer/restorer.go new file mode 100644 index 000000000..878f42249 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/service/restorer/restorer.go @@ -0,0 +1,291 @@ +/* +Copyright 2024 Flant JSC + +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. +*/ + +package restorer + +import ( + "context" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + + 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" + + "github.com/deckhouse/virtualization-controller/pkg/controller/service" + "github.com/deckhouse/virtualization-controller/pkg/sdk/framework/helper" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +type SecretRestorer struct { + client client.Client +} + +func NewSecretRestorer(client client.Client) *SecretRestorer { + return &SecretRestorer{ + client: client, + } +} + +func (r SecretRestorer) Store(ctx context.Context, vm *virtv2.VirtualMachine, vmSnapshot *virtv2.VirtualMachineSnapshot) (*corev1.Secret, error) { + secret := corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: vmSnapshot.Status.VirtualMachineSnapshotSecretName, + Namespace: vmSnapshot.Namespace, + OwnerReferences: []metav1.OwnerReference{ + service.MakeOwnerReference(vmSnapshot), + }, + }, + Data: make(map[string][]byte), + Type: "virtualmachine.virtualization.deckhouse.io/snapshot", + } + + err := r.setVirtualMachine(&secret, vm) + if err != nil { + return nil, err + } + + err = r.setVirtualMachineIPAddress(ctx, &secret, vm, vmSnapshot.Spec.KeepIPAddress) + if err != nil { + return nil, err + } + + err = r.setProvisioning(ctx, &secret, vm) + if err != nil { + return nil, err + } + + err = r.setVirtualMachineBlockDeviceAttachments(ctx, &secret, vm) + if err != nil { + return nil, err + } + + return &secret, nil +} + +func (r SecretRestorer) RestoreVirtualMachine(_ context.Context, secret *corev1.Secret) (*virtv2.VirtualMachine, error) { + return get[*virtv2.VirtualMachine](secret, getVirtualMachineKey()) +} + +func (r SecretRestorer) RestoreProvisioner(_ context.Context, secret *corev1.Secret) (*corev1.Secret, error) { + return get[*corev1.Secret](secret, getProvisionerKey()) +} + +func (r SecretRestorer) RestoreVirtualMachineIPAddress(_ context.Context, secret *corev1.Secret) (*virtv2.VirtualMachineIPAddress, error) { + return get[*virtv2.VirtualMachineIPAddress](secret, getVirtualMachineIPAddressKey()) +} + +func (r SecretRestorer) RestoreVirtualMachineBlockDeviceAttachments(_ context.Context, secret *corev1.Secret) ([]*virtv2.VirtualMachineBlockDeviceAttachment, error) { + return get[[]*virtv2.VirtualMachineBlockDeviceAttachment](secret, getVirtualMachineBlockDeviceAttachmentKey()) +} + +func (r SecretRestorer) setVirtualMachine(secret *corev1.Secret, vm *virtv2.VirtualMachine) error { + JSON, err := json.Marshal(vm) + if err != nil { + return err + } + + secret.Data[getVirtualMachineKey()] = []byte(base64.StdEncoding.EncodeToString(JSON)) + + return nil +} + +func (r SecretRestorer) setVirtualMachineBlockDeviceAttachments(ctx context.Context, secret *corev1.Secret, vm *virtv2.VirtualMachine) error { + var vmbdas []*virtv2.VirtualMachineBlockDeviceAttachment + + for _, bdr := range vm.Status.BlockDeviceRefs { + if bdr.Kind != virtv2.DiskDevice || !bdr.Hotplugged { + continue + } + + vmbda, err := helper.FetchObject(ctx, types.NamespacedName{ + Name: bdr.VirtualMachineBlockDeviceAttachmentName, + Namespace: vm.Namespace, + }, r.client, &virtv2.VirtualMachineBlockDeviceAttachment{}) + if err != nil { + return err + } + + if vmbda == nil { + return fmt.Errorf("the virtual machine block device attachment %q not found", bdr.VirtualMachineBlockDeviceAttachmentName) + } + + vmbdas = append(vmbdas, vmbda) + } + + if len(vmbdas) == 0 { + return nil + } + + JSON, err := json.Marshal(vmbdas) + if err != nil { + return err + } + + secret.Data[getVirtualMachineBlockDeviceAttachmentKey()] = []byte(base64.StdEncoding.EncodeToString(JSON)) + + return nil +} + +func (r SecretRestorer) setVirtualMachineIPAddress(ctx context.Context, secret *corev1.Secret, vm *virtv2.VirtualMachine, keepIPAddress virtv2.KeepIPAddress) error { + vmip, err := helper.FetchObject(ctx, types.NamespacedName{ + Namespace: vm.Namespace, + Name: vm.Status.VirtualMachineIPAddress, + }, r.client, &virtv2.VirtualMachineIPAddress{}) + if err != nil { + return err + } + + if vmip == nil { + return fmt.Errorf("the virtual machine ip address %q not found", vm.Status.VirtualMachineIPAddress) + } + + /* + 1. Never/Always (Keep/Convert) + 2. Static/Auto + 3. Empty/Set + + Always == convert Auto to Static + Static == keep old IP address + Set == with old name + ----------------------------------------------------------------------------- + | KEEP | IP-TYPE | VM-IP | BEHAVIOUR | + | Never | Static | Empty | not possible | + | Never | Static | Set | keep old IP address with old name | + | Never | Auto | Empty | allocate new random IP address with any name | + | Never | Auto | Set | allocate new random IP address with old name | + | Always | Static | Empty | not possible | + | Always | Static | Set | keep old IP address with old name | + | Always | Auto | Empty | convert and keep old IP address with any name | + | Always | Auto | Set | convert and keep old IP address with old name | + ----------------------------------------------------------------------------- + */ + + switch keepIPAddress { + case virtv2.KeepIPAddressNever: + switch vmip.Spec.Type { + case virtv2.VirtualMachineIPAddressTypeStatic: + if vm.Spec.VirtualMachineIPAddress == "" { + return errors.New("TODO: not possible") + } + case virtv2.VirtualMachineIPAddressTypeAuto: + } + + // Put to secret. + case virtv2.KeepIPAddressAlways: + switch vmip.Spec.Type { + case virtv2.VirtualMachineIPAddressTypeStatic: + if vm.Spec.VirtualMachineIPAddress == "" { + return errors.New("TODO: not possible") + } + + // Put to secret. + case virtv2.VirtualMachineIPAddressTypeAuto: + vmip.Spec.Type = virtv2.VirtualMachineIPAddressTypeStatic + vmip.Spec.StaticIP = vmip.Status.Address + // Put to secret. + } + } + + JSON, err := json.Marshal(vmip) + if err != nil { + return err + } + + secret.Data[getVirtualMachineIPAddressKey()] = []byte(base64.StdEncoding.EncodeToString(JSON)) + + return nil +} + +func (r SecretRestorer) setProvisioning(ctx context.Context, secret *corev1.Secret, vm *virtv2.VirtualMachine) error { + var secretName string + + switch vm.Spec.Provisioning.Type { + case virtv2.ProvisioningTypeSysprepRef: + if vm.Spec.Provisioning.SysprepRef == nil { + return errors.New("the virtual machine sysprep ref provisioning is nil") + } + + switch vm.Spec.Provisioning.SysprepRef.Kind { + case virtv2.SysprepRefKindSecret: + secretName = vm.Spec.Provisioning.SysprepRef.Name + default: + return fmt.Errorf("unknown sysprep ref kind %s", vm.Spec.Provisioning.SysprepRef.Kind) + } + case virtv2.ProvisioningTypeUserDataRef: + if vm.Spec.Provisioning.UserDataRef == nil { + return errors.New("the virtual machine user data ref provisioning is nil") + } + + switch vm.Spec.Provisioning.UserDataRef.Kind { + case virtv2.UserDataRefKindSecret: + secretName = vm.Spec.Provisioning.UserDataRef.Name + default: + return fmt.Errorf("unknown user data ref kind %s", vm.Spec.Provisioning.UserDataRef.Kind) + } + default: + return nil + } + + provisioner, err := helper.FetchObject(ctx, types.NamespacedName{ + Name: secretName, + Namespace: vm.Namespace, + }, r.client, &corev1.Secret{}) + if err != nil { + return err + } + + if provisioner == nil { + return fmt.Errorf("the virtual machine provisioning secret %q not found", secretName) + } + + JSON, err := json.Marshal(provisioner) + if err != nil { + return err + } + + secret.Data[getProvisionerKey()] = []byte(base64.StdEncoding.EncodeToString(JSON)) + + return nil +} + +func get[T any](secret *corev1.Secret, key string) (T, error) { + var t T + + data, ok := secret.Data[key] + if !ok { + return t, nil + } + + JSON, err := base64.StdEncoding.DecodeString(string(data)) + if err != nil { + return t, err + } + + err = json.Unmarshal(JSON, &t) + if err != nil { + return t, err + } + + return t, nil +} diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/source/object_ref_vdsnapshot.go b/images/virtualization-artifact/pkg/controller/vd/internal/source/object_ref_vdsnapshot.go index 8a6094601..3a88d61ed 100644 --- a/images/virtualization-artifact/pkg/controller/vd/internal/source/object_ref_vdsnapshot.go +++ b/images/virtualization-artifact/pkg/controller/vd/internal/source/object_ref_vdsnapshot.go @@ -80,6 +80,7 @@ func (ds ObjectRefVirtualDiskSnapshot) Sync(ctx context.Context, vd *virtv2.Virt namespacedName := supplements.NewGenerator(common.VDShortName, vd.Name, vd.Namespace, vd.UID).PersistentVolumeClaim() storageClassName := vs.Annotations["storageClass"] + volumeMode := vs.Annotations["volumeMode"] accessModesStr := strings.Split(vs.Annotations["accessModes"], ",") accessModes := make([]corev1.PersistentVolumeAccessMode, 0, len(accessModesStr)) for _, accessModeStr := range accessModesStr { @@ -89,7 +90,7 @@ func (ds ObjectRefVirtualDiskSnapshot) Sync(ctx context.Context, vd *virtv2.Virt spec := corev1.PersistentVolumeClaimSpec{ AccessModes: accessModes, DataSource: &corev1.TypedLocalObjectReference{ - APIGroup: ptr.To(vs.GroupVersionKind().GroupVersion().String()), + APIGroup: ptr.To(vs.GroupVersionKind().Group), Kind: vs.Kind, Name: vd.Spec.DataSource.ObjectRef.Name, }, @@ -99,6 +100,10 @@ func (ds ObjectRefVirtualDiskSnapshot) Sync(ctx context.Context, vd *virtv2.Virt spec.StorageClassName = &storageClassName } + if volumeMode != "" { + spec.VolumeMode = ptr.To(corev1.PersistentVolumeMode(volumeMode)) + } + if vs.Status != nil && vs.Status.RestoreSize != nil { spec.Resources = corev1.VolumeResourceRequirements{ Requests: corev1.ResourceList{ diff --git a/images/virtualization-artifact/pkg/controller/vd/vd_controller.go b/images/virtualization-artifact/pkg/controller/vd/vd_controller.go index 9ac6e6c35..0737e7192 100644 --- a/images/virtualization-artifact/pkg/controller/vd/vd_controller.go +++ b/images/virtualization-artifact/pkg/controller/vd/vd_controller.go @@ -19,6 +19,7 @@ package vd import ( "context" "log/slog" + "time" corev1 "k8s.io/api/core/v1" "k8s.io/utils/ptr" @@ -84,9 +85,10 @@ func NewController( ) vdController, err := controller.New(ControllerName, mgr, controller.Options{ - Reconciler: reconciler, - RecoverPanic: ptr.To(true), - LogConstructor: logger.NewConstructor(log), + Reconciler: reconciler, + RecoverPanic: ptr.To(true), + LogConstructor: logger.NewConstructor(log), + CacheSyncTimeout: 10 * time.Minute, }) if err != nil { return nil, err diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go index 83e5eec58..99c121d68 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go @@ -102,8 +102,6 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *virtv2.Virtual return reconcile.Result{}, nil } - log.Debug("Process the virtual disk snapshot") - virtualDiskReadyCondition, _ := service.GetCondition(vdscondition.VirtualDiskReadyType, vdSnapshot.Status.Conditions) if vd == nil || virtualDiskReadyCondition.Status != metav1.ConditionTrue { vdSnapshot.Status.Phase = virtv2.VirtualDiskSnapshotPhasePending @@ -171,14 +169,19 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *virtv2.Virtual anno := make(map[string]string) if pvc.Spec.StorageClassName != nil && *pvc.Spec.StorageClassName != "" { anno["storageClass"] = *pvc.Spec.StorageClassName - accessModes := make([]string, 0, len(pvc.Status.AccessModes)) - for _, accessMode := range pvc.Status.AccessModes { - accessModes = append(accessModes, string(accessMode)) - } + } - anno["accessModes"] = strings.Join(accessModes, ",") + if pvc.Spec.VolumeMode != nil && *pvc.Spec.VolumeMode != "" { + anno["volumeMode"] = string(*pvc.Spec.VolumeMode) } + accessModes := make([]string, 0, len(pvc.Status.AccessModes)) + for _, accessMode := range pvc.Status.AccessModes { + accessModes = append(accessModes, string(accessMode)) + } + + anno["accessModes"] = strings.Join(accessModes, ",") + vs = &vsv1.VolumeSnapshot{ ObjectMeta: metav1.ObjectMeta{ Annotations: anno, diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/vdsnapshot_controller.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/vdsnapshot_controller.go index a5a46558f..c0699958c 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/vdsnapshot_controller.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/vdsnapshot_controller.go @@ -19,6 +19,7 @@ package vdsnapshot import ( "context" "log/slog" + "time" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/builder" @@ -53,9 +54,10 @@ func NewController( ) vdSnapshotController, err := controller.New(ControllerName, mgr, controller.Options{ - Reconciler: reconciler, - RecoverPanic: ptr.To(true), - LogConstructor: logger.NewConstructor(log), + Reconciler: reconciler, + RecoverPanic: ptr.To(true), + LogConstructor: logger.NewConstructor(log), + CacheSyncTimeout: 10 * time.Minute, }) if err != nil { return nil, err diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/vdsnapshot_webhook.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/vdsnapshot_webhook.go index 419275723..a51af1e4a 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/vdsnapshot_webhook.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/vdsnapshot_webhook.go @@ -38,19 +38,8 @@ func NewValidator(logger *slog.Logger) *Validator { } func (v *Validator) ValidateCreate(_ context.Context, obj runtime.Object) (admission.Warnings, error) { - vds, ok := obj.(*virtv2.VirtualDiskSnapshot) - if !ok { - return nil, fmt.Errorf("expected a VirtualDiskSnapshot but got a %T", obj) - } - - if vds.Spec.VirtualDiskName == "" { - return nil, fmt.Errorf("virtual disk name cannot be empty") - } - - if vds.Spec.VolumeSnapshotClassName == "" { - return nil, fmt.Errorf("volume snapshot class name cannot be empty") - } - + err := fmt.Errorf("misconfigured webhook rules: delete operation not implemented") + v.logger.Error("Ensure the correctness of ValidatingWebhookConfiguration", "err", err) return nil, nil } diff --git a/images/virtualization-artifact/pkg/controller/vi/vi_controller.go b/images/virtualization-artifact/pkg/controller/vi/vi_controller.go index 3f8d207f0..7f37697b4 100644 --- a/images/virtualization-artifact/pkg/controller/vi/vi_controller.go +++ b/images/virtualization-artifact/pkg/controller/vi/vi_controller.go @@ -19,6 +19,7 @@ package vi import ( "context" "log/slog" + "time" corev1 "k8s.io/api/core/v1" "k8s.io/utils/ptr" @@ -78,9 +79,10 @@ func NewController( ) viController, err := controller.New(ControllerName, mgr, controller.Options{ - Reconciler: reconciler, - RecoverPanic: ptr.To(true), - LogConstructor: logger.NewConstructor(log), + Reconciler: reconciler, + RecoverPanic: ptr.To(true), + LogConstructor: logger.NewConstructor(log), + CacheSyncTimeout: 10 * time.Minute, }) if err != nil { return nil, err diff --git a/images/virtualization-artifact/pkg/controller/vm/internal/snapshotting.go b/images/virtualization-artifact/pkg/controller/vm/internal/snapshotting.go index 705bb1b7a..ffe8de797 100644 --- a/images/virtualization-artifact/pkg/controller/vm/internal/snapshotting.go +++ b/images/virtualization-artifact/pkg/controller/vm/internal/snapshotting.go @@ -82,7 +82,7 @@ func (h *SnapshottingHandler) Handle(ctx context.Context, s state.VirtualMachine default: cb.Status(metav1.ConditionTrue). Message("The virtual machine is selected for taking a snapshot."). - Reason(vmcondition.ReasonChosenForSnapshotting) + Reason(vmcondition.WaitingForTheSnapshotToStart) continue } } diff --git a/images/virtualization-artifact/pkg/controller/vm/internal/watcher/vmsnapshot_watcher.go b/images/virtualization-artifact/pkg/controller/vm/internal/watcher/vmsnapshot_watcher.go new file mode 100644 index 000000000..be20acc49 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vm/internal/watcher/vmsnapshot_watcher.go @@ -0,0 +1,90 @@ +/* +Copyright 2024 Flant JSC + +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. +*/ + +package watcher + +import ( + "context" + "fmt" + "log/slog" + + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" + + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +type VirtualMachineSnapshotWatcher struct { + client client.Client +} + +func NewVirtualMachineSnapshotWatcher(client client.Client) *VirtualMachineSnapshotWatcher { + return &VirtualMachineSnapshotWatcher{ + client: client, + } +} + +func (w VirtualMachineSnapshotWatcher) Watch(mgr manager.Manager, ctr controller.Controller) error { + return ctr.Watch( + source.Kind(mgr.GetCache(), &virtv2.VirtualMachineSnapshot{}), + handler.EnqueueRequestsFromMapFunc(w.enqueueRequests), + predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { return true }, + DeleteFunc: func(e event.DeleteEvent) bool { return true }, + UpdateFunc: w.filterUpdateEvents, + }, + ) +} + +func (w VirtualMachineSnapshotWatcher) enqueueRequests(_ context.Context, obj client.Object) (requests []reconcile.Request) { + vmSnapshot, ok := obj.(*virtv2.VirtualMachineSnapshot) + if !ok { + slog.Default().Error(fmt.Sprintf("expected a VirtualMachineSnapshot but got a %T", obj)) + return nil + } + + return []reconcile.Request{ + { + NamespacedName: types.NamespacedName{ + Name: vmSnapshot.Spec.VirtualMachineName, + Namespace: vmSnapshot.Namespace, + }, + }, + } +} + +func (w VirtualMachineSnapshotWatcher) filterUpdateEvents(e event.UpdateEvent) bool { + oldVMSnapshot, ok := e.ObjectOld.(*virtv2.VirtualMachineSnapshot) + if !ok { + slog.Default().Error(fmt.Sprintf("expected an old VirtualMachineSnapshot but got a %T", e.ObjectOld)) + return false + } + + newVMSnapshot, ok := e.ObjectNew.(*virtv2.VirtualMachineSnapshot) + if !ok { + slog.Default().Error(fmt.Sprintf("expected a new VirtualMachineSnapshot but got a %T", e.ObjectNew)) + return false + } + + return oldVMSnapshot.Status.Phase != newVMSnapshot.Status.Phase +} diff --git a/images/virtualization-artifact/pkg/controller/vm/vm_controller.go b/images/virtualization-artifact/pkg/controller/vm/vm_controller.go index 507cbe9e1..2b464d113 100644 --- a/images/virtualization-artifact/pkg/controller/vm/vm_controller.go +++ b/images/virtualization-artifact/pkg/controller/vm/vm_controller.go @@ -19,6 +19,7 @@ package vm import ( "context" "log/slog" + "time" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/builder" @@ -67,9 +68,10 @@ func SetupController( r := NewReconciler(client, handlers...) c, err := controller.New(controllerName, mgr, controller.Options{ - Reconciler: r, - RecoverPanic: ptr.To(true), - LogConstructor: logger.NewConstructor(log), + Reconciler: r, + RecoverPanic: ptr.To(true), + LogConstructor: logger.NewConstructor(log), + CacheSyncTimeout: 10 * time.Minute, }) if err != nil { return err diff --git a/images/virtualization-artifact/pkg/controller/vm/vm_reconciler.go b/images/virtualization-artifact/pkg/controller/vm/vm_reconciler.go index 2c7795e28..5654dce2f 100644 --- a/images/virtualization-artifact/pkg/controller/vm/vm_reconciler.go +++ b/images/virtualization-artifact/pkg/controller/vm/vm_reconciler.go @@ -38,7 +38,6 @@ import ( "github.com/deckhouse/virtualization-controller/pkg/controller/service" "github.com/deckhouse/virtualization-controller/pkg/controller/vm/internal/state" "github.com/deckhouse/virtualization-controller/pkg/controller/vm/internal/watcher" - watcher "github.com/deckhouse/virtualization-controller/pkg/controller/vm/internal/watchers" "github.com/deckhouse/virtualization-controller/pkg/logger" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" ) @@ -48,6 +47,10 @@ type Handler interface { Name() string } +type Watcher interface { + Watch(mgr manager.Manager, ctr controller.Controller) error +} + func NewReconciler(client client.Client, handlers ...Handler) *Reconciler { return &Reconciler{ client: client, @@ -240,16 +243,14 @@ func (r *Reconciler) SetupController(_ context.Context, mgr manager.Manager, ctr return fmt.Errorf("error setting watch on ClusterVirtualImage: %w", err) } - // Subscribe on VirtualMachineClass size policy change. - vmClassWatcher := watcher.NewVirtualMachineClassWatcher() - if err := vmClassWatcher.Watch(mgr, ctr); err != nil { - return fmt.Errorf("error setting watch on VirtualMachineClass SizePolicy: %w", err) - } - - w := watcher.NewVirtualMachineSnapshotWatcher(mgr.GetClient()) - err := w.Watch(mgr, ctr) - if err != nil { - return fmt.Errorf("faield to run watcher %s: %w", reflect.TypeOf(w).Elem().Name(), err) + for _, w := range []Watcher{ + watcher.NewVirtualMachineClassWatcher(), + watcher.NewVirtualMachineSnapshotWatcher(mgr.GetClient()), + } { + err := w.Watch(mgr, ctr) + if err != nil { + return fmt.Errorf("faield to run watcher %s: %w", reflect.TypeOf(w).Elem().Name(), err) + } } return nil diff --git a/images/virtualization-artifact/pkg/controller/vmbda/vmbda_controller.go b/images/virtualization-artifact/pkg/controller/vmbda/vmbda_controller.go index 538804567..bc4ab57f9 100644 --- a/images/virtualization-artifact/pkg/controller/vmbda/vmbda_controller.go +++ b/images/virtualization-artifact/pkg/controller/vmbda/vmbda_controller.go @@ -19,6 +19,7 @@ package vmbda import ( "context" "log/slog" + "time" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/builder" @@ -54,9 +55,10 @@ func NewController( ) vmbdaController, err := controller.New(ControllerName, mgr, controller.Options{ - Reconciler: reconciler, - RecoverPanic: ptr.To(true), - LogConstructor: logger.NewConstructor(log), + Reconciler: reconciler, + RecoverPanic: ptr.To(true), + LogConstructor: logger.NewConstructor(log), + CacheSyncTimeout: 10 * time.Minute, }) if err != nil { return nil, err diff --git a/images/virtualization-artifact/pkg/controller/vmclass/vmclass_controller.go b/images/virtualization-artifact/pkg/controller/vmclass/vmclass_controller.go index 4375a7aaa..5031c4990 100644 --- a/images/virtualization-artifact/pkg/controller/vmclass/vmclass_controller.go +++ b/images/virtualization-artifact/pkg/controller/vmclass/vmclass_controller.go @@ -19,6 +19,7 @@ package vmclass import ( "context" "log/slog" + "time" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/builder" @@ -51,9 +52,10 @@ func NewController( r := NewReconciler(client, handlers...) c, err := controller.New(controllerName, mgr, controller.Options{ - Reconciler: r, - RecoverPanic: ptr.To(true), - LogConstructor: logger.NewConstructor(log), + Reconciler: r, + RecoverPanic: ptr.To(true), + LogConstructor: logger.NewConstructor(log), + CacheSyncTimeout: 10 * time.Minute, }) if err != nil { return nil, err diff --git a/images/virtualization-artifact/pkg/controller/vmip/vmip_controller.go b/images/virtualization-artifact/pkg/controller/vmip/vmip_controller.go index f0e8f409f..869b6fc2c 100644 --- a/images/virtualization-artifact/pkg/controller/vmip/vmip_controller.go +++ b/images/virtualization-artifact/pkg/controller/vmip/vmip_controller.go @@ -19,6 +19,7 @@ package vmip import ( "context" "log/slog" + "time" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/builder" @@ -58,9 +59,10 @@ func NewController( } c, err := controller.New(controllerName, mgr, controller.Options{ - Reconciler: r, - RecoverPanic: ptr.To(true), - LogConstructor: logger.NewConstructor(log), + Reconciler: r, + RecoverPanic: ptr.To(true), + LogConstructor: logger.NewConstructor(log), + CacheSyncTimeout: 10 * time.Minute, }) if err != nil { return nil, err diff --git a/images/virtualization-artifact/pkg/controller/vmiplease/vmiplease_controller.go b/images/virtualization-artifact/pkg/controller/vmiplease/vmiplease_controller.go index 7fb3a526e..42597f340 100644 --- a/images/virtualization-artifact/pkg/controller/vmiplease/vmiplease_controller.go +++ b/images/virtualization-artifact/pkg/controller/vmiplease/vmiplease_controller.go @@ -57,9 +57,10 @@ func NewController( r := NewReconciler(mgr.GetClient(), handlers...) c, err := controller.New(controllerName, mgr, controller.Options{ - Reconciler: r, - RecoverPanic: ptr.To(true), - LogConstructor: logger.NewConstructor(log), + Reconciler: r, + RecoverPanic: ptr.To(true), + LogConstructor: logger.NewConstructor(log), + CacheSyncTimeout: 10 * time.Minute, }) if err != nil { return nil, err diff --git a/images/virtualization-artifact/pkg/controller/vmop/vmop_controller.go b/images/virtualization-artifact/pkg/controller/vmop/vmop_controller.go index 5da7956a2..ebd9cab33 100644 --- a/images/virtualization-artifact/pkg/controller/vmop/vmop_controller.go +++ b/images/virtualization-artifact/pkg/controller/vmop/vmop_controller.go @@ -59,10 +59,11 @@ func SetupController( reconciler := NewReconciler(client, handlers...) vmopController, err := controller.New(controllerName, mgr, controller.Options{ - Reconciler: reconciler, - RateLimiter: workqueue.NewItemExponentialFailureRateLimiter(time.Second, 32*time.Second), - RecoverPanic: ptr.To(true), - LogConstructor: logger.NewConstructor(log), + Reconciler: reconciler, + RateLimiter: workqueue.NewItemExponentialFailureRateLimiter(time.Second, 32*time.Second), + RecoverPanic: ptr.To(true), + LogConstructor: logger.NewConstructor(log), + CacheSyncTimeout: 10 * time.Minute, }) if err != nil { return err diff --git a/images/virtualization-artifact/pkg/controller/vmrestore/internal/deletion.go b/images/virtualization-artifact/pkg/controller/vmrestore/internal/deletion.go new file mode 100644 index 000000000..fc9fe39d5 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmrestore/internal/deletion.go @@ -0,0 +1,42 @@ +/* +Copyright 2024 Flant JSC + +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. +*/ + +package internal + +import ( + "context" + + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +type DeletionHandler struct{} + +func NewDeletionHandler() *DeletionHandler { + return &DeletionHandler{} +} + +func (h DeletionHandler) Handle(_ context.Context, vmRestore *virtv2.VirtualMachineRestore) (reconcile.Result, error) { + if vmRestore.DeletionTimestamp == nil { + controllerutil.AddFinalizer(vmRestore, virtv2.FinalizerVMRestoreCleanup) + return reconcile.Result{}, nil + } + + controllerutil.RemoveFinalizer(vmRestore, virtv2.FinalizerVMRestoreCleanup) + return reconcile.Result{}, nil +} diff --git a/images/virtualization-artifact/pkg/controller/vmrestore/internal/interfaces.go b/images/virtualization-artifact/pkg/controller/vmrestore/internal/interfaces.go new file mode 100644 index 000000000..0a983f30e --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmrestore/internal/interfaces.go @@ -0,0 +1,34 @@ +/* +Copyright 2024 Flant JSC + +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. +*/ + +package internal + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +//go:generate moq -rm -out mock.go . Restorer + +type Restorer interface { + RestoreVirtualMachine(ctx context.Context, secret *corev1.Secret) (*virtv2.VirtualMachine, error) + RestoreProvisioner(ctx context.Context, secret *corev1.Secret) (*corev1.Secret, error) + RestoreVirtualMachineIPAddress(ctx context.Context, secret *corev1.Secret) (*virtv2.VirtualMachineIPAddress, error) + RestoreVirtualMachineBlockDeviceAttachments(ctx context.Context, secret *corev1.Secret) ([]*virtv2.VirtualMachineBlockDeviceAttachment, error) +} diff --git a/images/virtualization-artifact/pkg/controller/vmrestore/internal/life_cycle.go b/images/virtualization-artifact/pkg/controller/vmrestore/internal/life_cycle.go new file mode 100644 index 000000000..2a84c93a0 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmrestore/internal/life_cycle.go @@ -0,0 +1,282 @@ +/* +Copyright 2024 Flant JSC + +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. +*/ + +package internal + +import ( + "context" + "errors" + "fmt" + + 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" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" + "github.com/deckhouse/virtualization-controller/pkg/controller/service" + "github.com/deckhouse/virtualization-controller/pkg/controller/vmrestore/internal/restorer" + "github.com/deckhouse/virtualization-controller/pkg/logger" + "github.com/deckhouse/virtualization-controller/pkg/sdk/framework/helper" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + vmrestorecondition "github.com/deckhouse/virtualization/api/core/v1alpha2/vm-restore-condition" +) + +type LifeCycleHandler struct { + client client.Client + restorer Restorer +} + +func NewLifeCycleHandler(client client.Client, restorer Restorer) *LifeCycleHandler { + return &LifeCycleHandler{ + client: client, + restorer: restorer, + } +} + +func (h LifeCycleHandler) Handle(ctx context.Context, vmRestore *virtv2.VirtualMachineRestore) (reconcile.Result, error) { + log := logger.FromContext(ctx).With(logger.SlogHandler("lifecycle")) + + cb := conditions.NewConditionBuilder(vmrestorecondition.VirtualMachineRestoreReadyType) + defer func() { conditions.SetCondition(cb.Generation(vmRestore.Generation), &vmRestore.Status.Conditions) }() + + if !conditions.HasCondition(cb.GetType(), vmRestore.Status.Conditions) { + cb.Status(metav1.ConditionUnknown).Reason(vmrestorecondition.VirtualMachineRestoreUnknown) + } else { + ready, _ := conditions.GetCondition(cb.GetType(), vmRestore.Status.Conditions) + cb = cb.Parse(ready) + } + + if vmRestore.Status.Phase == "" { + vmRestore.Status.Phase = virtv2.VirtualMachineRestorePhasePending + } + + if vmRestore.DeletionTimestamp != nil { + vmRestore.Status.Phase = virtv2.VirtualMachineRestorePhaseTerminating + cb.Status(metav1.ConditionUnknown).Reason(vmrestorecondition.VirtualMachineRestoreUnknown) + return reconcile.Result{}, nil + } + + switch vmRestore.Status.Phase { + case + virtv2.VirtualMachineRestorePhaseReady, + virtv2.VirtualMachineRestorePhaseFailed, + virtv2.VirtualMachineRestorePhaseTerminating: + log.Warn("DONE") + return reconcile.Result{}, nil + case virtv2.VirtualMachineRestorePhaseInProgress: + // TODO: check resources are ready. + + log.Warn("INPROGRESS") + + vmRestore.Status.Phase = virtv2.VirtualMachineRestorePhaseReady + cb.Status(metav1.ConditionTrue).Reason(vmrestorecondition.VirtualMachineRestoreReady) + return reconcile.Result{}, nil + } + + log.Warn("RESTORE") + + vmSnapshotKey := types.NamespacedName{Namespace: vmRestore.Namespace, Name: vmRestore.Spec.VirtualMachineSnapshotName} + vmSnapshot, err := helper.FetchObject(ctx, vmSnapshotKey, h.client, &virtv2.VirtualMachineSnapshot{}) + if err != nil { + setPhaseConditionToFailed(cb, &vmRestore.Status.Phase, err) + return reconcile.Result{}, err + } + + if vmSnapshot == nil { + err = errors.New("TODO: adsadsad") + setPhaseConditionToFailed(cb, &vmRestore.Status.Phase, err) + return reconcile.Result{}, err + } + + vmSnapshotReadyToUseCondition, _ := conditions.GetCondition(vmrestorecondition.VirtualMachineSnapshotReadyToUseType, vmRestore.Status.Conditions) + if vmSnapshotReadyToUseCondition.Status != metav1.ConditionTrue { + vmRestore.Status.Phase = virtv2.VirtualMachineRestorePhasePending + cb. + Status(metav1.ConditionFalse). + Reason(vmrestorecondition.VirtualMachineSnapshotNotReady). + Message(fmt.Sprintf("Waiting for the virtual machine %q to be ready for snapshotting.", vmSnapshot.Spec.VirtualMachineName)) + return reconcile.Result{}, nil + } + + restorerSecretKey := types.NamespacedName{Namespace: vmSnapshot.Namespace, Name: vmSnapshot.Status.VirtualMachineSnapshotSecretName} + restorerSecret, err := helper.FetchObject(ctx, restorerSecretKey, h.client, &corev1.Secret{}) + if err != nil { + setPhaseConditionToFailed(cb, &vmRestore.Status.Phase, err) + return reconcile.Result{}, err + } + + var overrideValidators []OverrideValidator + + vm, err := h.restorer.RestoreVirtualMachine(ctx, restorerSecret) + if err != nil { + setPhaseConditionToFailed(cb, &vmRestore.Status.Phase, err) + return reconcile.Result{}, err + } + + vmip, err := h.restorer.RestoreVirtualMachineIPAddress(ctx, restorerSecret) + if err != nil { + setPhaseConditionToFailed(cb, &vmRestore.Status.Phase, err) + return reconcile.Result{}, err + } + + if vmip != nil { + vm.Spec.VirtualMachineIPAddress = vmip.Name + overrideValidators = append(overrideValidators, restorer.NewVirtualMachineIPAddressOverrideValidator(vmip, h.client)) + } + + overrideValidators = append(overrideValidators, restorer.NewVirtualMachineOverrideValidator(vm, h.client)) + + vds, err := h.getVirtualDisks(ctx, vmSnapshot) + if err != nil { + setPhaseConditionToFailed(cb, &vmRestore.Status.Phase, err) + return reconcile.Result{}, err + } + + vmbdas, err := h.restorer.RestoreVirtualMachineBlockDeviceAttachments(ctx, restorerSecret) + if err != nil { + setPhaseConditionToFailed(cb, &vmRestore.Status.Phase, err) + return reconcile.Result{}, err + } + + provisioner, err := h.restorer.RestoreProvisioner(ctx, restorerSecret) + if err != nil { + setPhaseConditionToFailed(cb, &vmRestore.Status.Phase, err) + return reconcile.Result{}, err + } + + // TODO: vmClass validator. + + for _, vd := range vds { + overrideValidators = append(overrideValidators, restorer.NewVirtualDiskOverrideValidator(vd, h.client)) + } + + for _, vmbda := range vmbdas { + overrideValidators = append(overrideValidators, restorer.NewVirtualMachineBlockDeviceAttachmentsOverrideValidator(vmbda, h.client)) + } + + if provisioner != nil { + overrideValidators = append(overrideValidators, restorer.NewProvisionerOverrideValidator(provisioner, h.client)) + } + + log.Warn("VALIDATE") + + var toCreate []client.Object + + for _, ov := range overrideValidators { + ov.Override(vmRestore.Spec.NameReplacements) + + err = ov.Validate(ctx) + switch { + case err == nil: + case errors.Is(err, restorer.ErrAlreadyExists): + vmRestore.Status.Phase = virtv2.VirtualMachineRestorePhaseFailed + cb. + Status(metav1.ConditionFalse). + Reason(vmrestorecondition.VirtualMachineRestoreConflict). + Message(service.CapitalizeFirstLetter(err.Error()) + ".") + return reconcile.Result{}, nil + default: + setPhaseConditionToFailed(cb, &vmRestore.Status.Phase, err) + return reconcile.Result{}, err + } + + toCreate = append(toCreate, ov.Object()) + } + + log.Warn("CREATE") + + err = h.createBatch(ctx, toCreate...) + if err != nil { + setPhaseConditionToFailed(cb, &vmRestore.Status.Phase, err) + return reconcile.Result{}, err + } + + vmRestore.Status.Phase = virtv2.VirtualMachineRestorePhaseInProgress + cb. + Status(metav1.ConditionFalse). + Reason(vmrestorecondition.VirtualMachineSnapshotNotReady). + Message(fmt.Sprintf("The virtual machine %q is in the process of restore: all reasources created.", vmSnapshot.Spec.VirtualMachineName)) + return reconcile.Result{}, nil +} + +type OverrideValidator interface { + Object() client.Object + Override(rules []virtv2.NameReplacement) + Validate(ctx context.Context) error +} + +func (h LifeCycleHandler) getVirtualDisks(ctx context.Context, vmSnapshot *virtv2.VirtualMachineSnapshot) ([]*virtv2.VirtualDisk, error) { + vds := make([]*virtv2.VirtualDisk, 0, len(vmSnapshot.Status.VirtualDiskSnapshotNames)) + + for _, vdSnapshotName := range vmSnapshot.Status.VirtualDiskSnapshotNames { + vdSnapshotKey := types.NamespacedName{Namespace: vmSnapshot.Namespace, Name: vdSnapshotName} + vdSnapshot, err := helper.FetchObject(ctx, vdSnapshotKey, h.client, &virtv2.VirtualDiskSnapshot{}) + if err != nil { + return nil, err + } + + if vdSnapshot == nil { + return nil, errors.New("TODO: asdasgsdbdfbvmwie") + } + + vd := virtv2.VirtualDisk{ + TypeMeta: metav1.TypeMeta{ + Kind: virtv2.VirtualDiskKind, + APIVersion: virtv2.Version, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: vdSnapshot.Spec.VirtualDiskName, + Namespace: vdSnapshot.Namespace, + }, + Spec: virtv2.VirtualDiskSpec{ + DataSource: &virtv2.VirtualDiskDataSource{ + Type: virtv2.DataSourceTypeObjectRef, + ObjectRef: &virtv2.VirtualDiskObjectRef{ + Kind: virtv2.VirtualDiskObjectRefKindVirtualDiskSnapshot, + Name: vdSnapshot.Name, + }, + }, + }, + } + + vds = append(vds, &vd) + } + + return vds, nil +} + +func (h LifeCycleHandler) createBatch(ctx context.Context, objs ...client.Object) error { + for _, obj := range objs { + logger.FromContext(ctx).With(logger.SlogHandler("lifecycle")).Warn("CREATE " + obj.GetObjectKind().GroupVersionKind().Kind + " " + obj.GetName()) + + err := h.client.Create(ctx, obj) + if err != nil { + return err + } + } + + return nil +} + +func setPhaseConditionToFailed(cb *conditions.ConditionBuilder, phase *virtv2.VirtualMachineRestorePhase, err error) { + *phase = virtv2.VirtualMachineRestorePhaseFailed + cb. + Status(metav1.ConditionFalse). + Reason(vmrestorecondition.VirtualMachineRestoreFailed). + Message(service.CapitalizeFirstLetter(err.Error()) + ".") +} diff --git a/images/virtualization-artifact/pkg/controller/vmrestore/internal/restorer/errors.go b/images/virtualization-artifact/pkg/controller/vmrestore/internal/restorer/errors.go new file mode 100644 index 000000000..4cca1aa2c --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmrestore/internal/restorer/errors.go @@ -0,0 +1,21 @@ +/* +Copyright 2024 Flant JSC + +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. +*/ + +package restorer + +import "errors" + +var ErrAlreadyExists = errors.New("already exists") diff --git a/images/virtualization-artifact/pkg/controller/vmrestore/internal/restorer/overrider.go b/images/virtualization-artifact/pkg/controller/vmrestore/internal/restorer/overrider.go new file mode 100644 index 000000000..f1de740a8 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmrestore/internal/restorer/overrider.go @@ -0,0 +1,37 @@ +/* +Copyright 2024 Flant JSC + +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. +*/ + +package restorer + +import virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + +func overrideName(kind, name string, rules []virtv2.NameReplacement) string { + if name == "" { + return "" + } + + for _, rule := range rules { + if rule.From.Kind != "" && rule.From.Kind != kind { + continue + } + + if rule.From.Name == name { + return rule.To + } + } + + return name +} diff --git a/images/virtualization-artifact/pkg/controller/vmrestore/internal/restorer/provisioner_restorer.go b/images/virtualization-artifact/pkg/controller/vmrestore/internal/restorer/provisioner_restorer.go new file mode 100644 index 000000000..2bf028e48 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmrestore/internal/restorer/provisioner_restorer.go @@ -0,0 +1,85 @@ +/* +Copyright 2024 Flant JSC + +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. +*/ + +package restorer + +import ( + "bytes" + "context" + "fmt" + "maps" + + 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" + + "github.com/deckhouse/virtualization-controller/pkg/sdk/framework/helper" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +type ProvisionerOverrideValidator struct { + secret *corev1.Secret + client client.Client +} + +func NewProvisionerOverrideValidator(secretTmpl *corev1.Secret, client client.Client) *ProvisionerOverrideValidator { + return &ProvisionerOverrideValidator{ + secret: &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: secretTmpl.Kind, + APIVersion: secretTmpl.APIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: secretTmpl.Name, + Namespace: secretTmpl.Namespace, + Annotations: secretTmpl.Annotations, + Labels: secretTmpl.Labels, + }, + Immutable: secretTmpl.Immutable, + Data: secretTmpl.Data, + StringData: secretTmpl.StringData, + Type: secretTmpl.Type, + }, + client: client, + } +} + +func (v *ProvisionerOverrideValidator) Override(rules []virtv2.NameReplacement) { + v.secret.Name = overrideName(v.secret.Kind, v.secret.Name, rules) +} + +func (v *ProvisionerOverrideValidator) Validate(ctx context.Context) error { + secretKey := types.NamespacedName{Namespace: v.secret.Namespace, Name: v.secret.Name} + existed, err := helper.FetchObject(ctx, secretKey, v.client, &corev1.Secret{}) + if err != nil { + return err + } + + if existed == nil { + return nil + } + + if !maps.EqualFunc(existed.Data, v.secret.Data, bytes.Equal) { + return fmt.Errorf("TODO: %w", ErrAlreadyExists) + } + + return nil +} + +func (v *ProvisionerOverrideValidator) Object() client.Object { + return v.secret +} diff --git a/images/virtualization-artifact/pkg/controller/vmrestore/internal/restorer/vd_restorer.go b/images/virtualization-artifact/pkg/controller/vmrestore/internal/restorer/vd_restorer.go new file mode 100644 index 000000000..2b64d94e1 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmrestore/internal/restorer/vd_restorer.go @@ -0,0 +1,74 @@ +/* +Copyright 2024 Flant JSC + +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. +*/ + +package restorer + +import ( + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/deckhouse/virtualization-controller/pkg/sdk/framework/helper" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +type VirtualDiskOverrideValidator struct { + vd *virtv2.VirtualDisk + client client.Client +} + +func NewVirtualDiskOverrideValidator(vdTmpl *virtv2.VirtualDisk, client client.Client) *VirtualDiskOverrideValidator { + return &VirtualDiskOverrideValidator{ + vd: &virtv2.VirtualDisk{ + TypeMeta: metav1.TypeMeta{ + Kind: vdTmpl.Kind, + APIVersion: vdTmpl.APIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: vdTmpl.Name, + Namespace: vdTmpl.Namespace, + Annotations: vdTmpl.Annotations, + Labels: vdTmpl.Labels, + }, + Spec: vdTmpl.Spec, + }, + client: client, + } +} + +func (v VirtualDiskOverrideValidator) Override(rules []virtv2.NameReplacement) { + v.vd.Name = overrideName(v.vd.Kind, v.vd.Name, rules) +} + +func (v VirtualDiskOverrideValidator) Validate(ctx context.Context) error { + vmKey := types.NamespacedName{Namespace: v.vd.Namespace, Name: v.vd.Name} + existed, err := helper.FetchObject(ctx, vmKey, v.client, &virtv2.VirtualMachine{}) + if err != nil { + return err + } + + if existed != nil { + return ErrAlreadyExists + } + + return nil +} + +func (v VirtualDiskOverrideValidator) Object() client.Object { + return v.vd +} diff --git a/images/virtualization-artifact/pkg/controller/vmrestore/internal/restorer/vm_restorer.go b/images/virtualization-artifact/pkg/controller/vmrestore/internal/restorer/vm_restorer.go new file mode 100644 index 000000000..d73a51428 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmrestore/internal/restorer/vm_restorer.go @@ -0,0 +1,83 @@ +/* +Copyright 2024 Flant JSC + +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. +*/ + +package restorer + +import ( + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/deckhouse/virtualization-controller/pkg/sdk/framework/helper" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +type VirtualMachineOverrideValidator struct { + vm *virtv2.VirtualMachine + client client.Client +} + +func NewVirtualMachineOverrideValidator(vmTmpl *virtv2.VirtualMachine, client client.Client) *VirtualMachineOverrideValidator { + return &VirtualMachineOverrideValidator{ + vm: &virtv2.VirtualMachine{ + TypeMeta: metav1.TypeMeta{ + Kind: vmTmpl.Kind, + APIVersion: vmTmpl.APIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: vmTmpl.Name, + Namespace: vmTmpl.Namespace, + Annotations: vmTmpl.Annotations, + Labels: vmTmpl.Labels, + }, + Spec: vmTmpl.Spec, + }, + client: client, + } +} + +func (v *VirtualMachineOverrideValidator) Override(rules []virtv2.NameReplacement) { + v.vm.Name = overrideName(v.vm.Kind, v.vm.Name, rules) + v.vm.Spec.VirtualMachineIPAddress = overrideName(virtv2.VirtualMachineIPAddressKind, v.vm.Spec.VirtualMachineIPAddress, rules) + + for i := range v.vm.Spec.BlockDeviceRefs { + if v.vm.Spec.BlockDeviceRefs[i].Kind != virtv2.DiskDevice { + continue + } + + v.vm.Spec.BlockDeviceRefs[i].Name = overrideName(virtv2.VirtualDiskKind, v.vm.Spec.BlockDeviceRefs[i].Name, rules) + } +} + +func (v *VirtualMachineOverrideValidator) Validate(ctx context.Context) error { + vmKey := types.NamespacedName{Namespace: v.vm.Namespace, Name: v.vm.Name} + existed, err := helper.FetchObject(ctx, vmKey, v.client, &virtv2.VirtualMachine{}) + if err != nil { + return err + } + + if existed != nil { + return ErrAlreadyExists + } + + return nil +} + +func (v *VirtualMachineOverrideValidator) Object() client.Object { + return v.vm +} diff --git a/images/virtualization-artifact/pkg/controller/vmrestore/internal/restorer/vmbda_restorer.go b/images/virtualization-artifact/pkg/controller/vmrestore/internal/restorer/vmbda_restorer.go new file mode 100644 index 000000000..117e73220 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmrestore/internal/restorer/vmbda_restorer.go @@ -0,0 +1,79 @@ +/* +Copyright 2024 Flant JSC + +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. +*/ + +package restorer + +import ( + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/deckhouse/virtualization-controller/pkg/sdk/framework/helper" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +type VirtualMachineBlockDeviceAttachmentsOverrideValidator struct { + vmbda *virtv2.VirtualMachineBlockDeviceAttachment + client client.Client +} + +func NewVirtualMachineBlockDeviceAttachmentsOverrideValidator(vmbdaTmpl *virtv2.VirtualMachineBlockDeviceAttachment, client client.Client) *VirtualMachineBlockDeviceAttachmentsOverrideValidator { + return &VirtualMachineBlockDeviceAttachmentsOverrideValidator{ + vmbda: &virtv2.VirtualMachineBlockDeviceAttachment{ + TypeMeta: metav1.TypeMeta{ + Kind: vmbdaTmpl.Kind, + APIVersion: vmbdaTmpl.APIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: vmbdaTmpl.Name, + Namespace: vmbdaTmpl.Namespace, + Annotations: vmbdaTmpl.Annotations, + Labels: vmbdaTmpl.Labels, + }, + Spec: vmbdaTmpl.Spec, + }, + client: client, + } +} + +func (v *VirtualMachineBlockDeviceAttachmentsOverrideValidator) Override(rules []virtv2.NameReplacement) { + v.vmbda.Name = overrideName(v.vmbda.Kind, v.vmbda.Name, rules) + v.vmbda.Spec.VirtualMachineName = overrideName(virtv2.VirtualMachineKind, v.vmbda.Spec.VirtualMachineName, rules) + + if v.vmbda.Spec.BlockDeviceRef.Kind == virtv2.VMBDAObjectRefKindVirtualDisk { + v.vmbda.Spec.BlockDeviceRef.Name = overrideName(virtv2.VirtualDiskKind, v.vmbda.Spec.BlockDeviceRef.Name, rules) + } +} + +func (v *VirtualMachineBlockDeviceAttachmentsOverrideValidator) Validate(ctx context.Context) error { + vmKey := types.NamespacedName{Namespace: v.vmbda.Namespace, Name: v.vmbda.Name} + existed, err := helper.FetchObject(ctx, vmKey, v.client, &virtv2.VirtualMachineBlockDeviceAttachment{}) + if err != nil { + return err + } + + if existed != nil { + return ErrAlreadyExists + } + + return nil +} + +func (v *VirtualMachineBlockDeviceAttachmentsOverrideValidator) Object() client.Object { + return v.vmbda +} diff --git a/images/virtualization-artifact/pkg/controller/vmrestore/internal/restorer/vmip_restorer.go b/images/virtualization-artifact/pkg/controller/vmrestore/internal/restorer/vmip_restorer.go new file mode 100644 index 000000000..5c8d65837 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmrestore/internal/restorer/vmip_restorer.go @@ -0,0 +1,88 @@ +/* +Copyright 2024 Flant JSC + +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. +*/ + +package restorer + +import ( + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/deckhouse/virtualization-controller/pkg/sdk/framework/helper" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +type VirtualMachineIPAddressOverrideValidator struct { + vmip *virtv2.VirtualMachineIPAddress + client client.Client +} + +func NewVirtualMachineIPAddressOverrideValidator(vmipTmpl *virtv2.VirtualMachineIPAddress, client client.Client) *VirtualMachineIPAddressOverrideValidator { + return &VirtualMachineIPAddressOverrideValidator{ + vmip: &virtv2.VirtualMachineIPAddress{ + TypeMeta: metav1.TypeMeta{ + Kind: vmipTmpl.Kind, + APIVersion: vmipTmpl.APIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: vmipTmpl.Name, + Namespace: vmipTmpl.Namespace, + Annotations: vmipTmpl.Annotations, + Labels: vmipTmpl.Labels, + }, + Spec: vmipTmpl.Spec, + }, + client: client, + } +} + +func (v *VirtualMachineIPAddressOverrideValidator) Override(rules []virtv2.NameReplacement) { + v.vmip.Name = overrideName(v.vmip.Kind, v.vmip.Name, rules) +} + +func (v *VirtualMachineIPAddressOverrideValidator) Validate(ctx context.Context) error { + vmipKey := types.NamespacedName{Namespace: v.vmip.Namespace, Name: v.vmip.Name} + existed, err := helper.FetchObject(ctx, vmipKey, v.client, &virtv2.VirtualMachineIPAddress{}) + if err != nil { + return err + } + + if existed != nil { + return ErrAlreadyExists + } + + // TODO ensure no such ip. + return nil + + // attached, _ := conditions.GetCondition(vmipcondition.AttachedType, existed.Status.Conditions) + // if attached.Status != metav1.ConditionFalse || attached.Reason != vmipcondition.VirtualMachineNotFound.String() { + // return fmt.Errorf("TODO: hhhhhhhhhhhhhh %w", ErrAlreadyExists) + // } + // + // if existed.Spec.Type != v.vmip.Spec.Type { + // return fmt.Errorf("TODO: sadasdsad %w", ErrAlreadyExists) + // } + // + // if v.vmip.Spec.Type == virtv2.VirtualMachineIPAddressTypeStatic && existed.Status.Address != v.vmip.Spec.StaticIP { + // return fmt.Errorf("TODO: jdjdjjdjd %w", ErrAlreadyExists) + // } +} + +func (v *VirtualMachineIPAddressOverrideValidator) Object() client.Object { + return v.vmip +} diff --git a/images/virtualization-artifact/pkg/controller/vmrestore/internal/vm_snapshot_ready_to_use.go b/images/virtualization-artifact/pkg/controller/vmrestore/internal/vm_snapshot_ready_to_use.go new file mode 100644 index 000000000..9d9112abe --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmrestore/internal/vm_snapshot_ready_to_use.go @@ -0,0 +1,102 @@ +/* +Copyright 2024 Flant JSC + +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. +*/ + +package internal + +import ( + "context" + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" + "github.com/deckhouse/virtualization-controller/pkg/sdk/framework/helper" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + vmrestorecondition "github.com/deckhouse/virtualization/api/core/v1alpha2/vm-restore-condition" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vmscondition" +) + +type VirtualMachineSnapshotReadyToUseHandler struct { + client client.Client +} + +func NewVirtualMachineSnapshotReadyToUseHandler(client client.Client) *VirtualMachineSnapshotReadyToUseHandler { + return &VirtualMachineSnapshotReadyToUseHandler{ + client: client, + } +} + +func (h VirtualMachineSnapshotReadyToUseHandler) Handle(ctx context.Context, vmRestore *virtv2.VirtualMachineRestore) (reconcile.Result, error) { + cb := conditions.NewConditionBuilder(vmrestorecondition.VirtualMachineSnapshotReadyToUseType) + defer func() { conditions.SetCondition(cb.Generation(vmRestore.Generation), &vmRestore.Status.Conditions) }() + + if !conditions.HasCondition(cb.GetType(), vmRestore.Status.Conditions) { + cb.Status(metav1.ConditionUnknown).Reason(vmrestorecondition.VirtualMachineSnapshotUnknown) + } + + if vmRestore.DeletionTimestamp != nil { + cb.Status(metav1.ConditionUnknown).Reason(vmrestorecondition.VirtualMachineSnapshotUnknown) + return reconcile.Result{}, nil + } + + vmSnapshot, err := helper.FetchObject(ctx, types.NamespacedName{ + Name: vmRestore.Spec.VirtualMachineSnapshotName, + Namespace: vmRestore.Namespace, + }, h.client, &virtv2.VirtualMachineSnapshot{}) + if err != nil { + return reconcile.Result{}, err + } + + if vmSnapshot == nil { + cb. + Status(metav1.ConditionFalse). + Reason(vmrestorecondition.VirtualMachineSnapshotNotFound). + Message(fmt.Sprintf("The virtual machine snapshot %q not found.", vmRestore.Spec.VirtualMachineSnapshotName)) + return reconcile.Result{}, nil + } + + if vmSnapshot.GetDeletionTimestamp() != nil { + cb. + Status(metav1.ConditionFalse). + Reason(vmrestorecondition.VirtualMachineSnapshotNotReady). + Message(fmt.Sprintf("The virtual machine snapshot %q is in the process if deleting.", vmSnapshot.Name)) + return reconcile.Result{}, nil + } + + vmSnapshotReady, _ := conditions.GetCondition(vmscondition.VirtualMachineSnapshotReadyType, vmSnapshot.Status.Conditions) + if vmSnapshotReady.Status != metav1.ConditionTrue || vmSnapshot.Status.Phase != virtv2.VirtualMachineSnapshotPhaseReady { + cb. + Status(metav1.ConditionFalse). + Reason(vmrestorecondition.VirtualMachineSnapshotNotReady). + Message(fmt.Sprintf("Waiting for the virtual machine snapshot %q to be ready for use.", vmSnapshot.Name)) + return reconcile.Result{}, nil + } + + if vmSnapshot.Generation != vmSnapshot.Status.ObservedGeneration { + cb. + Status(metav1.ConditionFalse). + Reason(vmrestorecondition.VirtualMachineSnapshotNotReady). + Message(fmt.Sprintf("Waiting for the virtual machine snapshot %q to be observed in its latest state generation.", vmSnapshot.Name)) + return reconcile.Result{}, nil + } + + cb.Status(metav1.ConditionTrue).Reason(vmrestorecondition.VirtualMachineSnapshotReadyToUse) + + return reconcile.Result{}, nil +} diff --git a/images/virtualization-artifact/pkg/controller/vmrestore/internal/watcher/vmrestore_watcher.go b/images/virtualization-artifact/pkg/controller/vmrestore/internal/watcher/vmrestore_watcher.go new file mode 100644 index 000000000..e7662dbea --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmrestore/internal/watcher/vmrestore_watcher.go @@ -0,0 +1,53 @@ +/* +Copyright 2024 Flant JSC + +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. +*/ + +package watcher + +import ( + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/source" + + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +type VirtualMachineRestoreWatcher struct { + client client.Client +} + +func NewVirtualMachineRestoreWatcher(client client.Client) *VirtualMachineRestoreWatcher { + return &VirtualMachineRestoreWatcher{ + client: client, + } +} + +func (w VirtualMachineRestoreWatcher) Watch(mgr manager.Manager, ctr controller.Controller) error { + return ctr.Watch( + source.Kind(mgr.GetCache(), &virtv2.VirtualMachineRestore{}), + &handler.EnqueueRequestForObject{}, + predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { return true }, + DeleteFunc: func(e event.DeleteEvent) bool { return true }, + UpdateFunc: func(e event.UpdateEvent) bool { + return e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration() + }, + }, + ) +} diff --git a/images/virtualization-artifact/pkg/controller/vm/internal/watchers/vmsnapshot_watcher.go b/images/virtualization-artifact/pkg/controller/vmrestore/internal/watcher/vmsnapshot_watcher.go similarity index 81% rename from images/virtualization-artifact/pkg/controller/vm/internal/watchers/vmsnapshot_watcher.go rename to images/virtualization-artifact/pkg/controller/vmrestore/internal/watcher/vmsnapshot_watcher.go index b71fbb880..e89c21ae5 100644 --- a/images/virtualization-artifact/pkg/controller/vm/internal/watchers/vmsnapshot_watcher.go +++ b/images/virtualization-artifact/pkg/controller/vmrestore/internal/watcher/vmsnapshot_watcher.go @@ -21,6 +21,7 @@ import ( "fmt" "log/slog" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -31,6 +32,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" + "github.com/deckhouse/virtualization-controller/pkg/controller/indexer" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" ) @@ -57,31 +59,23 @@ func (w VirtualMachineSnapshotWatcher) Watch(mgr manager.Manager, ctr controller } func (w VirtualMachineSnapshotWatcher) enqueueRequests(ctx context.Context, obj client.Object) (requests []reconcile.Request) { - vmSnapshot, ok := obj.(*virtv2.VirtualMachineSnapshot) - if !ok { - slog.Default().Error(fmt.Sprintf("expected a VirtualMachineSnapshot but got a %T", obj)) - return nil - } - - var vms virtv2.VirtualMachineList - // TODO use index. - err := w.client.List(ctx, &vms, &client.ListOptions{ - Namespace: vmSnapshot.Namespace, + var vmRestores virtv2.VirtualMachineRestoreList + err := w.client.List(ctx, &vmRestores, &client.ListOptions{ + Namespace: obj.GetNamespace(), + FieldSelector: fields.OneTermEqualSelector(indexer.IndexFieldVMRestoreByVMSnapshot, obj.GetName()), }) if err != nil { - slog.Default().Error(fmt.Sprintf("failed to list virtual machines: %s", err)) + slog.Default().Error(fmt.Sprintf("failed to list virtual machine restores: %s", err)) return } - for _, vm := range vms.Items { - if vmSnapshot.Spec.VirtualMachineName == vm.Name { - requests = append(requests, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: vm.Name, - Namespace: vm.Namespace, - }, - }) - } + for _, vmRestore := range vmRestores.Items { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: vmRestore.Name, + Namespace: vmRestore.Namespace, + }, + }) } return diff --git a/images/virtualization-artifact/pkg/controller/vmrestore/vmrestore_controller.go b/images/virtualization-artifact/pkg/controller/vmrestore/vmrestore_controller.go new file mode 100644 index 000000000..15bb953ed --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmrestore/vmrestore_controller.go @@ -0,0 +1,76 @@ +/* +Copyright 2024 Flant JSC + +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. +*/ + +package vmrestore + +import ( + "context" + "log/slog" + "time" + + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/manager" + + "github.com/deckhouse/virtualization-controller/pkg/controller/service/restorer" + "github.com/deckhouse/virtualization-controller/pkg/controller/vmrestore/internal" + "github.com/deckhouse/virtualization-controller/pkg/logger" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +const ControllerName = "vmrestore-controller" + +func NewController( + ctx context.Context, + mgr manager.Manager, + log *slog.Logger, +) (controller.Controller, error) { + log = log.With(logger.SlogController(ControllerName)) + + reconciler := NewReconciler( + mgr.GetClient(), + internal.NewVirtualMachineSnapshotReadyToUseHandler(mgr.GetClient()), + internal.NewLifeCycleHandler(mgr.GetClient(), restorer.NewSecretRestorer(mgr.GetClient())), + internal.NewDeletionHandler(), + ) + + vmRestoreController, err := controller.New(ControllerName, mgr, controller.Options{ + Reconciler: reconciler, + RecoverPanic: ptr.To(true), + LogConstructor: logger.NewConstructor(log), + CacheSyncTimeout: 10 * time.Minute, + }) + if err != nil { + return nil, err + } + + err = reconciler.SetupController(ctx, mgr, vmRestoreController) + if err != nil { + return nil, err + } + + if err = builder.WebhookManagedBy(mgr). + For(&virtv2.VirtualMachineRestore{}). + WithValidator(NewValidator(log)). + Complete(); err != nil { + return nil, err + } + + log.Info("Initialized VirtualMachineRestore controller") + + return vmRestoreController, nil +} diff --git a/images/virtualization-artifact/pkg/controller/vmrestore/vmrestore_reconciler.go b/images/virtualization-artifact/pkg/controller/vmrestore/vmrestore_reconciler.go new file mode 100644 index 000000000..013d6d75f --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmrestore/vmrestore_reconciler.go @@ -0,0 +1,119 @@ +/* +Copyright 2024 Flant JSC + +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. +*/ + +package vmrestore + +import ( + "context" + "errors" + "fmt" + "reflect" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/deckhouse/virtualization-controller/pkg/controller/service" + "github.com/deckhouse/virtualization-controller/pkg/controller/vmrestore/internal/watcher" + "github.com/deckhouse/virtualization-controller/pkg/logger" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +type Handler interface { + Handle(ctx context.Context, vmRestore *virtv2.VirtualMachineRestore) (reconcile.Result, error) +} + +type Watcher interface { + Watch(mgr manager.Manager, ctr controller.Controller) error +} + +type Reconciler struct { + handlers []Handler + client client.Client +} + +func NewReconciler(client client.Client, handlers ...Handler) *Reconciler { + return &Reconciler{ + client: client, + handlers: handlers, + } +} + +func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { + log := logger.FromContext(ctx) + + vmRestore := service.NewResource(req.NamespacedName, r.client, r.factory, r.statusGetter) + + err := vmRestore.Fetch(ctx) + if err != nil { + return reconcile.Result{}, err + } + + if vmRestore.IsEmpty() { + return reconcile.Result{}, nil + } + + var result reconcile.Result + var handlerErrs []error + + for _, h := range r.handlers { + var res reconcile.Result + res, err = h.Handle(ctx, vmRestore.Changed()) + if err != nil { + log.Error("Failed to handle vmRestore", logger.SlogErr(err), logger.SlogHandler(reflect.TypeOf(h).Elem().Name())) + handlerErrs = append(handlerErrs, err) + } + + result = service.MergeResults(result, res) + } + + vmRestore.Changed().Status.ObservedGeneration = vmRestore.Changed().Generation + + err = vmRestore.Update(ctx) + if err != nil { + return reconcile.Result{}, err + } + + err = errors.Join(handlerErrs...) + if err != nil { + return reconcile.Result{}, err + } + + return result, nil +} + +func (r *Reconciler) SetupController(_ context.Context, mgr manager.Manager, ctr controller.Controller) error { + for _, w := range []Watcher{ + watcher.NewVirtualMachineRestoreWatcher(mgr.GetClient()), + watcher.NewVirtualMachineSnapshotWatcher(mgr.GetClient()), + } { + err := w.Watch(mgr, ctr) + if err != nil { + return fmt.Errorf("faield to run watcher %s: %w", reflect.TypeOf(w).Elem().Name(), err) + } + } + + return nil +} + +func (r *Reconciler) factory() *virtv2.VirtualMachineRestore { + return &virtv2.VirtualMachineRestore{} +} + +func (r *Reconciler) statusGetter(obj *virtv2.VirtualMachineRestore) virtv2.VirtualMachineRestoreStatus { + return obj.Status +} diff --git a/images/virtualization-artifact/pkg/controller/vmrestore/vmrestore_webhook.go b/images/virtualization-artifact/pkg/controller/vmrestore/vmrestore_webhook.go new file mode 100644 index 000000000..f031e4670 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmrestore/vmrestore_webhook.go @@ -0,0 +1,70 @@ +/* +Copyright 2024 Flant JSC + +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. +*/ + +package vmrestore + +import ( + "context" + "fmt" + "log/slog" + + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +type Validator struct { + logger *slog.Logger +} + +func NewValidator(logger *slog.Logger) *Validator { + return &Validator{ + logger: logger.With("webhook", "validator"), + } +} + +func (v *Validator) ValidateCreate(_ context.Context, _ runtime.Object) (admission.Warnings, error) { + err := fmt.Errorf("misconfigured webhook rules: delete operation not implemented") + v.logger.Error("Ensure the correctness of ValidatingWebhookConfiguration", "err", err) + return nil, nil +} + +func (v *Validator) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + oldVMRestore, ok := oldObj.(*virtv2.VirtualMachineRestore) + if !ok { + return nil, fmt.Errorf("expected an old VirtualMachineRestore but got a %T", newObj) + } + + newVMRestore, ok := newObj.(*virtv2.VirtualMachineRestore) + if !ok { + return nil, fmt.Errorf("expected a new VirtualMachineRestore but got a %T", newObj) + } + + v.logger.Info("Validating VirtualMachineRestore") + + if oldVMRestore.Generation != newVMRestore.Generation { + return nil, fmt.Errorf("VirtualMachineRestore is an idempotent resource: specification changes are not available") + } + + return nil, nil +} + +func (v *Validator) ValidateDelete(_ context.Context, _ runtime.Object) (admission.Warnings, error) { + err := fmt.Errorf("misconfigured webhook rules: delete operation not implemented") + v.logger.Error("Ensure the correctness of ValidatingWebhookConfiguration", "err", err) + return nil, nil +} diff --git a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/interfaces.go b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/interfaces.go index 885ab7515..4c0847e49 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/interfaces.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/interfaces.go @@ -24,13 +24,13 @@ import ( virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" ) -//go:generate moq -rm -out mock.go . SecretBuilderSnapshotter LifeCycleSnapshotter +//go:generate moq -rm -out mock.go . Storer Snapshotter -type SecretBuilderSnapshotter interface { - Build(ctx context.Context, vm *virtv2.VirtualMachine, vmSnapshot *virtv2.VirtualMachineSnapshot) (*corev1.Secret, error) +type Storer interface { + Store(ctx context.Context, vm *virtv2.VirtualMachine, vmSnapshot *virtv2.VirtualMachineSnapshot) (*corev1.Secret, error) } -type LifeCycleSnapshotter interface { +type Snapshotter interface { GetSecret(ctx context.Context, name, namespace string) (*corev1.Secret, error) CreateSecret(ctx context.Context, secret *corev1.Secret) (*corev1.Secret, error) GetVirtualMachine(ctx context.Context, name, namespace string) (*virtv2.VirtualMachine, error) diff --git a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle.go b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle.go index 6b30745e8..d09c344e7 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle.go @@ -27,6 +27,7 @@ import ( "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" "github.com/deckhouse/virtualization-controller/pkg/controller/service" "github.com/deckhouse/virtualization-controller/pkg/logger" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" @@ -36,36 +37,33 @@ import ( ) type LifeCycleHandler struct { - snapshotter LifeCycleSnapshotter - builder SecretBuilderSnapshotter + snapshotter Snapshotter + storer Storer } -func NewLifeCycleHandler(snapshotter LifeCycleSnapshotter, builder SecretBuilderSnapshotter) *LifeCycleHandler { +func NewLifeCycleHandler(snapshotter Snapshotter, storer Storer) *LifeCycleHandler { return &LifeCycleHandler{ snapshotter: snapshotter, - builder: builder, + storer: storer, } } func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *virtv2.VirtualMachineSnapshot) (reconcile.Result, error) { log := logger.FromContext(ctx).With(logger.SlogHandler("lifecycle")) - condition, ok := service.GetCondition(vmscondition.VirtualMachineSnapshotReadyType, vmSnapshot.Status.Conditions) - if !ok { - condition = metav1.Condition{ - Type: vmscondition.VirtualMachineSnapshotReadyType, - Status: metav1.ConditionUnknown, - Reason: vmscondition.VirtualMachineSnapshotUnknown, - } - } + cb := conditions.NewConditionBuilder(vmscondition.VirtualMachineSnapshotReadyType) + defer func() { conditions.SetCondition(cb.Generation(vmSnapshot.Generation), &vmSnapshot.Status.Conditions) }() - defer func() { service.SetCondition(condition, &vmSnapshot.Status.Conditions) }() + if !conditions.HasCondition(cb.GetType(), vmSnapshot.Status.Conditions) { + cb.Status(metav1.ConditionUnknown).Reason(vmscondition.VirtualMachineSnapshotUnknown) + } if vmSnapshot.DeletionTimestamp != nil { vmSnapshot.Status.Phase = virtv2.VirtualMachineSnapshotPhaseTerminating - condition.Status = metav1.ConditionUnknown - condition.Reason = vmscondition.VirtualMachineSnapshotUnknown - condition.Message = "" + cb. + Status(metav1.ConditionUnknown). + Reason(vmscondition.VirtualMachineSnapshotUnknown). + Message("") return reconcile.Result{}, nil } @@ -79,7 +77,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *virtv2.Virtual for _, vdSnapshotName := range vmSnapshot.Status.VirtualDiskSnapshotNames { vdSnapshot, err := h.snapshotter.GetVirtualDiskSnapshot(ctx, vdSnapshotName, vmSnapshot.Namespace) if err != nil { - setPhaseConditionToFailed(&condition, &vmSnapshot.Status.Phase, err) + setPhaseConditionToFailed(cb, &vmSnapshot.Status.Phase, err) return reconcile.Result{}, err } @@ -93,37 +91,36 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *virtv2.Virtual if len(lostVDSnapshots) > 0 { vmSnapshot.Status.Phase = virtv2.VirtualMachineSnapshotPhaseFailed - condition.Status = metav1.ConditionFalse - condition.Reason = vmscondition.VirtualDiskSnapshotLost + cb.Status(metav1.ConditionFalse).Reason(vmscondition.VirtualDiskSnapshotLost) if len(lostVDSnapshots) == 1 { - condition.Message = fmt.Sprintf("The underlieng virtual disk snapshot (%s) is lost.", lostVDSnapshots[0]) + cb.Message(fmt.Sprintf("The underlieng virtual disk snapshot (%s) is lost.", lostVDSnapshots[0])) } else { - condition.Message = fmt.Sprintf("The underlieng virtual disk snapshots (%s) are lost.", strings.Join(lostVDSnapshots, ", ")) + cb.Message(fmt.Sprintf("The underlieng virtual disk snapshots (%s) are lost.", strings.Join(lostVDSnapshots, ", "))) } return reconcile.Result{}, nil } vmSnapshot.Status.Phase = virtv2.VirtualMachineSnapshotPhaseReady - condition.Status = metav1.ConditionTrue - condition.Reason = vmscondition.VirtualMachineSnapshotReady - condition.Message = "" + cb. + Status(metav1.ConditionTrue). + Reason(vmscondition.VirtualMachineSnapshotReady). + Message("") return reconcile.Result{}, nil } - log.Debug("Process the virtual machine snapshot") - vm, err := h.snapshotter.GetVirtualMachine(ctx, vmSnapshot.Spec.VirtualMachineName, vmSnapshot.Namespace) if err != nil { - setPhaseConditionToFailed(&condition, &vmSnapshot.Status.Phase, err) + setPhaseConditionToFailed(cb, &vmSnapshot.Status.Phase, err) return reconcile.Result{}, err } - virtualMachineReadyCondition, _ := service.GetCondition(vmscondition.VirtualMachineReadyType, vmSnapshot.Status.Conditions) + virtualMachineReadyCondition, _ := conditions.GetCondition(vmscondition.VirtualMachineReadyType, vmSnapshot.Status.Conditions) if vm == nil || virtualMachineReadyCondition.Status != metav1.ConditionTrue { vmSnapshot.Status.Phase = virtv2.VirtualMachineSnapshotPhasePending - condition.Status = metav1.ConditionFalse - condition.Reason = vmscondition.WaitingForTheVirtualMachine - condition.Message = fmt.Sprintf("Waiting for the virtual machine %q to be ready for snapshotting.", vmSnapshot.Spec.VirtualMachineName) + cb. + Status(metav1.ConditionFalse). + Reason(vmscondition.WaitingForTheVirtualMachine). + Message(fmt.Sprintf("Waiting for the virtual machine %q to be ready for snapshotting.", vmSnapshot.Spec.VirtualMachineName)) return reconcile.Result{}, nil } @@ -133,24 +130,26 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *virtv2.Virtual case err == nil: case errors.Is(err, ErrBlockDevicesNotReady), errors.Is(err, ErrVirtualDiskNotReady), errors.Is(err, ErrVirtualDiskResizing): vmSnapshot.Status.Phase = virtv2.VirtualMachineSnapshotPhasePending - condition.Status = metav1.ConditionFalse - condition.Reason = vmscondition.BlockDevicesNotReady - condition.Message = service.CapitalizeFirstLetter(err.Error()) + "." + cb. + Status(metav1.ConditionFalse). + Reason(vmscondition.BlockDevicesNotReady). + Message(service.CapitalizeFirstLetter(err.Error() + ".")) return reconcile.Result{}, nil default: - setPhaseConditionToFailed(&condition, &vmSnapshot.Status.Phase, err) + setPhaseConditionToFailed(cb, &vmSnapshot.Status.Phase, err) return reconcile.Result{}, err } // 2. Ensure there are no RestartAwaitingChanges. if len(vm.Status.RestartAwaitingChanges) > 0 { vmSnapshot.Status.Phase = virtv2.VirtualMachineSnapshotPhasePending - condition.Status = metav1.ConditionFalse - condition.Reason = vmscondition.RestartAwaitingChanges - condition.Message = fmt.Sprintf( - "Waiting for the restart and approval of changes to virtual machine %q before taking the snapshot.", - vm.Name, - ) + cb. + Status(metav1.ConditionFalse). + Reason(vmscondition.RestartAwaitingChanges).Message( + fmt.Sprintf( + "Waiting for the restart and approval of changes to virtual machine %q before taking the snapshot.", + vm.Name, + )) return reconcile.Result{}, nil } @@ -161,32 +160,34 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *virtv2.Virtual case errors.Is(err, ErrPotentiallyInconsistent): if vmSnapshot.Spec.RequiredConsistency { vmSnapshot.Status.Phase = virtv2.VirtualMachineSnapshotPhasePending - condition.Status = metav1.ConditionFalse - condition.Reason = vmscondition.PotentiallyInconsistent - condition.Message = fmt.Sprintf( - "The snapshotting of virtual machine %q might result in an inconsistent snapshot: "+ - "waiting for the virtual machine to be %s", - vm.Name, virtv2.MachineStopped, - ) + cb. + Status(metav1.ConditionFalse). + Reason(vmscondition.PotentiallyInconsistent). + Message(fmt.Sprintf( + "The snapshotting of virtual machine %q might result in an inconsistent snapshot: "+ + "waiting for the virtual machine to be %s", + vm.Name, virtv2.MachineStopped, + )) return reconcile.Result{}, nil } default: - setPhaseConditionToFailed(&condition, &vmSnapshot.Status.Phase, err) + setPhaseConditionToFailed(cb, &vmSnapshot.Status.Phase, err) return reconcile.Result{}, err } if hasFrozen { vmSnapshot.Status.Phase = virtv2.VirtualMachineSnapshotPhaseInProgress - condition.Status = metav1.ConditionFalse - condition.Reason = vmscondition.FileSystemFreezing - condition.Message = fmt.Sprintf("The virtual machine %q is in the process of being frozen for taking a snapshot.", vm.Name) + cb. + Status(metav1.ConditionFalse). + Reason(vmscondition.FileSystemFreezing). + Message(fmt.Sprintf("The virtual machine %q is in the process of being frozen for taking a snapshot.", vm.Name)) return reconcile.Result{}, nil } // 4. Create secret. err = h.ensureSecret(ctx, vm, vmSnapshot) if err != nil { - setPhaseConditionToFailed(&condition, &vmSnapshot.Status.Phase, err) + setPhaseConditionToFailed(cb, &vmSnapshot.Status.Phase, err) return reconcile.Result{}, err } @@ -199,12 +200,13 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *virtv2.Virtual case err == nil: case errors.Is(err, ErrVolumeSnapshotClassNotFound): vmSnapshot.Status.Phase = virtv2.VirtualMachineSnapshotPhasePending - condition.Status = metav1.ConditionFalse - condition.Reason = vmscondition.BlockDevicesNotReady - condition.Message = service.CapitalizeFirstLetter(err.Error()) + "." + cb. + Status(metav1.ConditionFalse). + Reason(vmscondition.BlockDevicesNotReady). + Message(service.CapitalizeFirstLetter(err.Error()) + ".") return reconcile.Result{}, nil default: - setPhaseConditionToFailed(&condition, &vmSnapshot.Status.Phase, err) + setPhaseConditionToFailed(cb, &vmSnapshot.Status.Phase, err) return reconcile.Result{}, err } @@ -215,12 +217,14 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *virtv2.Virtual log.Debug("Waiting for the virtual disk snapshots to be taken for the block devices of the virtual machine") vmSnapshot.Status.Phase = virtv2.VirtualMachineSnapshotPhaseInProgress - condition.Status = metav1.ConditionFalse - condition.Reason = vmscondition.Snapshotting - condition.Message = fmt.Sprintf( - "Waiting for the virtual disk snapshots to be taken for the block devices of the virtual machine %q (%d/%d).", - vm.Name, readyCount, len(vdSnapshots), - ) + cb. + Status(metav1.ConditionFalse). + Reason(vmscondition.Snapshotting). + Message(fmt.Sprintf( + "Waiting for the virtual disk snapshots to be taken for "+ + "the block devices of the virtual machine %q (%d/%d).", + vm.Name, readyCount, len(vdSnapshots), + )) return reconcile.Result{}, nil } @@ -231,7 +235,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *virtv2.Virtual // 8. Unfreeze VirtualMachine if can. unfrozen, err := h.unfreezeVirtualMachineIfCan(ctx, vm) if err != nil { - setPhaseConditionToFailed(&condition, &vmSnapshot.Status.Phase, err) + setPhaseConditionToFailed(cb, &vmSnapshot.Status.Phase, err) return reconcile.Result{}, err } @@ -239,18 +243,20 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *virtv2.Virtual log.Debug("The virtual disk snapshots are taken: the virtual machine snapshot is Ready now", "unfrozen", unfrozen) vmSnapshot.Status.Phase = virtv2.VirtualMachineSnapshotPhaseReady - condition.Status = metav1.ConditionTrue - condition.Reason = vmscondition.VirtualMachineReady - condition.Message = "" + cb. + Status(metav1.ConditionTrue). + Reason(vmscondition.VirtualMachineReady). + Message("") return reconcile.Result{}, nil } -func setPhaseConditionToFailed(cond *metav1.Condition, phase *virtv2.VirtualMachineSnapshotPhase, err error) { +func setPhaseConditionToFailed(cb *conditions.ConditionBuilder, phase *virtv2.VirtualMachineSnapshotPhase, err error) { *phase = virtv2.VirtualMachineSnapshotPhaseFailed - cond.Status = metav1.ConditionFalse - cond.Reason = vmscondition.VirtualMachineSnapshotFailed - cond.Message = service.CapitalizeFirstLetter(err.Error()) + cb. + Status(metav1.ConditionFalse). + Reason(vmscondition.VirtualMachineSnapshotFailed). + Message(service.CapitalizeFirstLetter(err.Error()) + ".") } func (h LifeCycleHandler) fillStatusVirtualDiskSnapshotNames(vmSnapshot *virtv2.VirtualMachineSnapshot, vm *virtv2.VirtualMachine) { @@ -308,7 +314,7 @@ func (h LifeCycleHandler) ensureVirtualDiskSnapshots(ctx context.Context, vmSnap } var vsClass string - vsClass, err = h.getVolumeSnapshotClassByStorageClass(*pvc.Spec.StorageClassName, vmSnapshot.Spec.VolumeSnapshotClassNames) + vsClass, err = h.getVolumeSnapshotClassByStorageClass(*pvc.Spec.StorageClassName, vmSnapshot.Spec.VolumeSnapshotClasses) if err != nil { return nil, err } @@ -411,7 +417,7 @@ var ( ) func (h LifeCycleHandler) ensureBlockDeviceConsistency(ctx context.Context, vm *virtv2.VirtualMachine) error { - bdReady, _ := service.GetCondition(vmcondition.TypeBlockDevicesReady.String(), vm.Status.Conditions) + bdReady, _ := conditions.GetCondition(vmcondition.TypeBlockDevicesReady, vm.Status.Conditions) if bdReady.Status != metav1.ConditionTrue { return fmt.Errorf("%w: waiting for the block devices of the virtual machine %q to be ready", ErrBlockDevicesNotReady, vm.Name) } @@ -430,12 +436,14 @@ func (h LifeCycleHandler) ensureBlockDeviceConsistency(ctx context.Context, vm * return fmt.Errorf("%w: waiting for the virtual disk %q to be %s", ErrVirtualDiskNotReady, vd.Name, virtv2.DiskReady) } - ready, _ := service.GetCondition(vdcondition.ReadyType, vd.Status.Conditions) + //nolint:staticcheck + ready, _ := conditions.GetCondition(conditions.DeprecatedWrappedString(vdcondition.ReadyType), vd.Status.Conditions) if ready.Status != metav1.ConditionTrue { return fmt.Errorf("%w: waiting for the Ready condition of the virtual disk %q to be True", ErrVirtualDiskResizing, vd.Name) } - resizingReady, _ := service.GetCondition(vdcondition.ResizedType, vd.Status.Conditions) + //nolint:staticcheck + resizingReady, _ := conditions.GetCondition(conditions.DeprecatedWrappedString(vdcondition.ResizedType), vd.Status.Conditions) if resizingReady.Reason == vdcondition.InProgress { return fmt.Errorf("%w: waiting for the virtual disk %q to be resized", ErrVirtualDiskResizing, vd.Name) } @@ -455,7 +463,7 @@ func (h LifeCycleHandler) ensureSecret(ctx context.Context, vm *virtv2.VirtualMa } if secret == nil { - secret, err = h.builder.Build(ctx, vm, vmSnapshot) + secret, err = h.storer.Store(ctx, vm, vmSnapshot) if err != nil { return err } diff --git a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle_test.go b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle_test.go index 6e475fa07..aece2bcdc 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle_test.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle_test.go @@ -26,6 +26,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/ptr" + "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" "github.com/deckhouse/virtualization-controller/pkg/controller/service" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/api/core/v1alpha2/vdcondition" @@ -34,8 +35,8 @@ import ( ) var _ = Describe("LifeCycle handler", func() { - var snapshotter *LifeCycleSnapshotterMock - var secretBuilder *SecretBuilderSnapshotterMock + var snapshotter *SnapshotterMock + var storer *StorerMock var vd *virtv2.VirtualDisk var vm *virtv2.VirtualMachine var secret *corev1.Secret @@ -101,7 +102,7 @@ var _ = Describe("LifeCycle handler", func() { Status: virtv2.VirtualMachineSnapshotStatus{ Conditions: []metav1.Condition{ { - Type: vmscondition.VirtualMachineReadyType, + Type: vmscondition.VirtualMachineReadyType.String(), Status: metav1.ConditionTrue, }, }, @@ -115,7 +116,7 @@ var _ = Describe("LifeCycle handler", func() { }, } - snapshotter = &LifeCycleSnapshotterMock{ + snapshotter = &SnapshotterMock{ GetVirtualDiskFunc: func(_ context.Context, name, namespace string) (*virtv2.VirtualDisk, error) { return vd, nil }, @@ -149,14 +150,14 @@ var _ = Describe("LifeCycle handler", func() { }, &vm.Status.Conditions) return vm, nil } - h := NewLifeCycleHandler(snapshotter, secretBuilder) + h := NewLifeCycleHandler(snapshotter, storer) _, err := h.Handle(testContext(), vmSnapshot) Expect(err).To(BeNil()) Expect(vmSnapshot.Status.Phase).To(Equal(virtv2.VirtualMachineSnapshotPhasePending)) - ready, _ := service.GetCondition(vmscondition.VirtualMachineSnapshotReadyType, vmSnapshot.Status.Conditions) + ready, _ := conditions.GetCondition(vmscondition.VirtualMachineSnapshotReadyType, vmSnapshot.Status.Conditions) Expect(ready.Status).To(Equal(metav1.ConditionFalse)) - Expect(ready.Reason).To(Equal(vmscondition.BlockDevicesNotReady)) + Expect(ready.Reason).To(Equal(vmscondition.BlockDevicesNotReady.String())) Expect(ready.Message).ToNot(BeEmpty()) }) @@ -165,14 +166,14 @@ var _ = Describe("LifeCycle handler", func() { vd.Status.Phase = virtv2.DiskPending return vd, nil } - h := NewLifeCycleHandler(snapshotter, secretBuilder) + h := NewLifeCycleHandler(snapshotter, storer) _, err := h.Handle(testContext(), vmSnapshot) Expect(err).To(BeNil()) Expect(vmSnapshot.Status.Phase).To(Equal(virtv2.VirtualMachineSnapshotPhasePending)) - ready, _ := service.GetCondition(vmscondition.VirtualMachineSnapshotReadyType, vmSnapshot.Status.Conditions) + ready, _ := conditions.GetCondition(vmscondition.VirtualMachineSnapshotReadyType, vmSnapshot.Status.Conditions) Expect(ready.Status).To(Equal(metav1.ConditionFalse)) - Expect(ready.Reason).To(Equal(vmscondition.BlockDevicesNotReady)) + Expect(ready.Reason).To(Equal(vmscondition.BlockDevicesNotReady.String())) Expect(ready.Message).ToNot(BeEmpty()) }) @@ -184,14 +185,14 @@ var _ = Describe("LifeCycle handler", func() { }, &vd.Status.Conditions) return vd, nil } - h := NewLifeCycleHandler(snapshotter, secretBuilder) + h := NewLifeCycleHandler(snapshotter, storer) _, err := h.Handle(testContext(), vmSnapshot) Expect(err).To(BeNil()) Expect(vmSnapshot.Status.Phase).To(Equal(virtv2.VirtualMachineSnapshotPhasePending)) - ready, _ := service.GetCondition(vmscondition.VirtualMachineSnapshotReadyType, vmSnapshot.Status.Conditions) + ready, _ := conditions.GetCondition(vmscondition.VirtualMachineSnapshotReadyType, vmSnapshot.Status.Conditions) Expect(ready.Status).To(Equal(metav1.ConditionFalse)) - Expect(ready.Reason).To(Equal(vmscondition.BlockDevicesNotReady)) + Expect(ready.Reason).To(Equal(vmscondition.BlockDevicesNotReady.String())) Expect(ready.Message).ToNot(BeEmpty()) }) @@ -204,14 +205,14 @@ var _ = Describe("LifeCycle handler", func() { }, &vd.Status.Conditions) return vd, nil } - h := NewLifeCycleHandler(snapshotter, secretBuilder) + h := NewLifeCycleHandler(snapshotter, storer) _, err := h.Handle(testContext(), vmSnapshot) Expect(err).To(BeNil()) Expect(vmSnapshot.Status.Phase).To(Equal(virtv2.VirtualMachineSnapshotPhasePending)) - ready, _ := service.GetCondition(vmscondition.VirtualMachineSnapshotReadyType, vmSnapshot.Status.Conditions) + ready, _ := conditions.GetCondition(vmscondition.VirtualMachineSnapshotReadyType, vmSnapshot.Status.Conditions) Expect(ready.Status).To(Equal(metav1.ConditionFalse)) - Expect(ready.Reason).To(Equal(vmscondition.BlockDevicesNotReady)) + Expect(ready.Reason).To(Equal(vmscondition.BlockDevicesNotReady.String())) Expect(ready.Message).ToNot(BeEmpty()) }) }) @@ -223,14 +224,14 @@ var _ = Describe("LifeCycle handler", func() { return vm, nil } - h := NewLifeCycleHandler(snapshotter, secretBuilder) + h := NewLifeCycleHandler(snapshotter, storer) _, err := h.Handle(testContext(), vmSnapshot) Expect(err).To(BeNil()) Expect(vmSnapshot.Status.Phase).To(Equal(virtv2.VirtualMachineSnapshotPhasePending)) - ready, _ := service.GetCondition(vmscondition.VirtualMachineSnapshotReadyType, vmSnapshot.Status.Conditions) + ready, _ := conditions.GetCondition(vmscondition.VirtualMachineSnapshotReadyType, vmSnapshot.Status.Conditions) Expect(ready.Status).To(Equal(metav1.ConditionFalse)) - Expect(ready.Reason).To(Equal(vmscondition.RestartAwaitingChanges)) + Expect(ready.Reason).To(Equal(vmscondition.RestartAwaitingChanges.String())) Expect(ready.Message).ToNot(BeEmpty()) }) @@ -242,14 +243,14 @@ var _ = Describe("LifeCycle handler", func() { return false } - h := NewLifeCycleHandler(snapshotter, secretBuilder) + h := NewLifeCycleHandler(snapshotter, storer) _, err := h.Handle(testContext(), vmSnapshot) Expect(err).To(BeNil()) Expect(vmSnapshot.Status.Phase).To(Equal(virtv2.VirtualMachineSnapshotPhasePending)) - ready, _ := service.GetCondition(vmscondition.VirtualMachineSnapshotReadyType, vmSnapshot.Status.Conditions) + ready, _ := conditions.GetCondition(vmscondition.VirtualMachineSnapshotReadyType, vmSnapshot.Status.Conditions) Expect(ready.Status).To(Equal(metav1.ConditionFalse)) - Expect(ready.Reason).To(Equal(vmscondition.PotentiallyInconsistent)) + Expect(ready.Reason).To(Equal(vmscondition.PotentiallyInconsistent.String())) Expect(ready.Message).ToNot(BeEmpty()) }) @@ -264,35 +265,35 @@ var _ = Describe("LifeCycle handler", func() { return nil } - h := NewLifeCycleHandler(snapshotter, secretBuilder) + h := NewLifeCycleHandler(snapshotter, storer) _, err := h.Handle(testContext(), vmSnapshot) Expect(err).To(BeNil()) Expect(vmSnapshot.Status.Phase).To(Equal(virtv2.VirtualMachineSnapshotPhaseInProgress)) - ready, _ := service.GetCondition(vmscondition.VirtualMachineSnapshotReadyType, vmSnapshot.Status.Conditions) + ready, _ := conditions.GetCondition(vmscondition.VirtualMachineSnapshotReadyType, vmSnapshot.Status.Conditions) Expect(ready.Status).To(Equal(metav1.ConditionFalse)) - Expect(ready.Reason).To(Equal(vmscondition.FileSystemFreezing)) + Expect(ready.Reason).To(Equal(vmscondition.FileSystemFreezing.String())) Expect(ready.Message).ToNot(BeEmpty()) }) }) Context("The virtual machine snapshot is Ready", func() { It("The snapshot of virtual machine is Ready", func() { - h := NewLifeCycleHandler(snapshotter, secretBuilder) + h := NewLifeCycleHandler(snapshotter, storer) _, err := h.Handle(testContext(), vmSnapshot) Expect(err).To(BeNil()) Expect(vmSnapshot.Status.Phase).To(Equal(virtv2.VirtualMachineSnapshotPhaseReady)) - ready, _ := service.GetCondition(vmscondition.VirtualMachineSnapshotReadyType, vmSnapshot.Status.Conditions) + ready, _ := conditions.GetCondition(vmscondition.VirtualMachineSnapshotReadyType, vmSnapshot.Status.Conditions) Expect(ready.Status).To(Equal(metav1.ConditionTrue)) - Expect(ready.Reason).To(Equal(vmscondition.VirtualMachineReady)) + Expect(ready.Reason).To(Equal(vmscondition.VirtualMachineReady.String())) Expect(ready.Message).To(BeEmpty()) Expect(vmSnapshot.Status.VirtualDiskSnapshotNames[0]).To(Equal(vdSnapshot.Name)) }) It("The snapshot of running virtual machine is consistent", func() { - h := NewLifeCycleHandler(snapshotter, secretBuilder) + h := NewLifeCycleHandler(snapshotter, storer) _, err := h.Handle(testContext(), vmSnapshot) Expect(err).To(BeNil()) @@ -304,7 +305,7 @@ var _ = Describe("LifeCycle handler", func() { vm.Status.Phase = virtv2.MachineStopped return vm, nil } - h := NewLifeCycleHandler(snapshotter, secretBuilder) + h := NewLifeCycleHandler(snapshotter, storer) _, err := h.Handle(testContext(), vmSnapshot) Expect(err).To(BeNil()) @@ -319,7 +320,7 @@ var _ = Describe("LifeCycle handler", func() { snapshotter.CanFreezeFunc = func(_ *virtv2.VirtualMachine) bool { return false } - h := NewLifeCycleHandler(snapshotter, secretBuilder) + h := NewLifeCycleHandler(snapshotter, storer) _, err := h.Handle(testContext(), vmSnapshot) Expect(err).To(BeNil()) diff --git a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/mock.go b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/mock.go index f9028760c..79dd257b0 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/mock.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/mock.go @@ -10,33 +10,33 @@ import ( "sync" ) -// Ensure, that SecretBuilderSnapshotterMock does implement SecretBuilderSnapshotter. +// Ensure, that StorerMock does implement Storer. // If this is not the case, regenerate this file with moq. -var _ SecretBuilderSnapshotter = &SecretBuilderSnapshotterMock{} +var _ Storer = &StorerMock{} -// SecretBuilderSnapshotterMock is a mock implementation of SecretBuilderSnapshotter. +// StorerMock is a mock implementation of Storer. // -// func TestSomethingThatUsesSecretBuilderSnapshotter(t *testing.T) { +// func TestSomethingThatUsesStorer(t *testing.T) { // -// // make and configure a mocked SecretBuilderSnapshotter -// mockedSecretBuilderSnapshotter := &SecretBuilderSnapshotterMock{ -// BuildFunc: func(ctx context.Context, vm *virtv2.VirtualMachine, vmSnapshot *virtv2.VirtualMachineSnapshot) (*corev1.Secret, error) { -// panic("mock out the Build method") +// // make and configure a mocked Storer +// mockedStorer := &StorerMock{ +// StoreFunc: func(ctx context.Context, vm *virtv2.VirtualMachine, vmSnapshot *virtv2.VirtualMachineSnapshot) (*corev1.Secret, error) { +// panic("mock out the Store method") // }, // } // -// // use mockedSecretBuilderSnapshotter in code that requires SecretBuilderSnapshotter +// // use mockedStorer in code that requires Storer // // and then make assertions. // // } -type SecretBuilderSnapshotterMock struct { - // BuildFunc mocks the Build method. - BuildFunc func(ctx context.Context, vm *virtv2.VirtualMachine, vmSnapshot *virtv2.VirtualMachineSnapshot) (*corev1.Secret, error) +type StorerMock struct { + // StoreFunc mocks the Store method. + StoreFunc func(ctx context.Context, vm *virtv2.VirtualMachine, vmSnapshot *virtv2.VirtualMachineSnapshot) (*corev1.Secret, error) // calls tracks calls to the methods. calls struct { - // Build holds details about calls to the Build method. - Build []struct { + // Store holds details about calls to the Store method. + Store []struct { // Ctx is the ctx argument value. Ctx context.Context // VM is the vm argument value. @@ -45,13 +45,13 @@ type SecretBuilderSnapshotterMock struct { VmSnapshot *virtv2.VirtualMachineSnapshot } } - lockBuild sync.RWMutex + lockStore sync.RWMutex } -// Build calls BuildFunc. -func (mock *SecretBuilderSnapshotterMock) Build(ctx context.Context, vm *virtv2.VirtualMachine, vmSnapshot *virtv2.VirtualMachineSnapshot) (*corev1.Secret, error) { - if mock.BuildFunc == nil { - panic("SecretBuilderSnapshotterMock.BuildFunc: method is nil but SecretBuilderSnapshotter.Build was just called") +// Store calls StoreFunc. +func (mock *StorerMock) Store(ctx context.Context, vm *virtv2.VirtualMachine, vmSnapshot *virtv2.VirtualMachineSnapshot) (*corev1.Secret, error) { + if mock.StoreFunc == nil { + panic("StorerMock.StoreFunc: method is nil but Storer.Store was just called") } callInfo := struct { Ctx context.Context @@ -62,17 +62,17 @@ func (mock *SecretBuilderSnapshotterMock) Build(ctx context.Context, vm *virtv2. VM: vm, VmSnapshot: vmSnapshot, } - mock.lockBuild.Lock() - mock.calls.Build = append(mock.calls.Build, callInfo) - mock.lockBuild.Unlock() - return mock.BuildFunc(ctx, vm, vmSnapshot) + mock.lockStore.Lock() + mock.calls.Store = append(mock.calls.Store, callInfo) + mock.lockStore.Unlock() + return mock.StoreFunc(ctx, vm, vmSnapshot) } -// BuildCalls gets all the calls that were made to Build. +// StoreCalls gets all the calls that were made to Store. // Check the length with: // -// len(mockedSecretBuilderSnapshotter.BuildCalls()) -func (mock *SecretBuilderSnapshotterMock) BuildCalls() []struct { +// len(mockedStorer.StoreCalls()) +func (mock *StorerMock) StoreCalls() []struct { Ctx context.Context VM *virtv2.VirtualMachine VmSnapshot *virtv2.VirtualMachineSnapshot @@ -82,22 +82,22 @@ func (mock *SecretBuilderSnapshotterMock) BuildCalls() []struct { VM *virtv2.VirtualMachine VmSnapshot *virtv2.VirtualMachineSnapshot } - mock.lockBuild.RLock() - calls = mock.calls.Build - mock.lockBuild.RUnlock() + mock.lockStore.RLock() + calls = mock.calls.Store + mock.lockStore.RUnlock() return calls } -// Ensure, that LifeCycleSnapshotterMock does implement LifeCycleSnapshotter. +// Ensure, that SnapshotterMock does implement Snapshotter. // If this is not the case, regenerate this file with moq. -var _ LifeCycleSnapshotter = &LifeCycleSnapshotterMock{} +var _ Snapshotter = &SnapshotterMock{} -// LifeCycleSnapshotterMock is a mock implementation of LifeCycleSnapshotter. +// SnapshotterMock is a mock implementation of Snapshotter. // -// func TestSomethingThatUsesLifeCycleSnapshotter(t *testing.T) { +// func TestSomethingThatUsesSnapshotter(t *testing.T) { // -// // make and configure a mocked LifeCycleSnapshotter -// mockedLifeCycleSnapshotter := &LifeCycleSnapshotterMock{ +// // make and configure a mocked Snapshotter +// mockedSnapshotter := &SnapshotterMock{ // CanFreezeFunc: func(vm *virtv2.VirtualMachine) bool { // panic("mock out the CanFreeze method") // }, @@ -136,11 +136,11 @@ var _ LifeCycleSnapshotter = &LifeCycleSnapshotterMock{} // }, // } // -// // use mockedLifeCycleSnapshotter in code that requires LifeCycleSnapshotter +// // use mockedSnapshotter in code that requires Snapshotter // // and then make assertions. // // } -type LifeCycleSnapshotterMock struct { +type SnapshotterMock struct { // CanFreezeFunc mocks the CanFreeze method. CanFreezeFunc func(vm *virtv2.VirtualMachine) bool @@ -291,9 +291,9 @@ type LifeCycleSnapshotterMock struct { } // CanFreeze calls CanFreezeFunc. -func (mock *LifeCycleSnapshotterMock) CanFreeze(vm *virtv2.VirtualMachine) bool { +func (mock *SnapshotterMock) CanFreeze(vm *virtv2.VirtualMachine) bool { if mock.CanFreezeFunc == nil { - panic("LifeCycleSnapshotterMock.CanFreezeFunc: method is nil but LifeCycleSnapshotter.CanFreeze was just called") + panic("SnapshotterMock.CanFreezeFunc: method is nil but Snapshotter.CanFreeze was just called") } callInfo := struct { VM *virtv2.VirtualMachine @@ -309,8 +309,8 @@ func (mock *LifeCycleSnapshotterMock) CanFreeze(vm *virtv2.VirtualMachine) bool // CanFreezeCalls gets all the calls that were made to CanFreeze. // Check the length with: // -// len(mockedLifeCycleSnapshotter.CanFreezeCalls()) -func (mock *LifeCycleSnapshotterMock) CanFreezeCalls() []struct { +// len(mockedSnapshotter.CanFreezeCalls()) +func (mock *SnapshotterMock) CanFreezeCalls() []struct { VM *virtv2.VirtualMachine } { var calls []struct { @@ -323,9 +323,9 @@ func (mock *LifeCycleSnapshotterMock) CanFreezeCalls() []struct { } // CanUnfreeze calls CanUnfreezeFunc. -func (mock *LifeCycleSnapshotterMock) CanUnfreeze(ctx context.Context, vdSnapshotName string, vm *virtv2.VirtualMachine) (bool, error) { +func (mock *SnapshotterMock) CanUnfreeze(ctx context.Context, vdSnapshotName string, vm *virtv2.VirtualMachine) (bool, error) { if mock.CanUnfreezeFunc == nil { - panic("LifeCycleSnapshotterMock.CanUnfreezeFunc: method is nil but LifeCycleSnapshotter.CanUnfreeze was just called") + panic("SnapshotterMock.CanUnfreezeFunc: method is nil but Snapshotter.CanUnfreeze was just called") } callInfo := struct { Ctx context.Context @@ -345,8 +345,8 @@ func (mock *LifeCycleSnapshotterMock) CanUnfreeze(ctx context.Context, vdSnapsho // CanUnfreezeCalls gets all the calls that were made to CanUnfreeze. // Check the length with: // -// len(mockedLifeCycleSnapshotter.CanUnfreezeCalls()) -func (mock *LifeCycleSnapshotterMock) CanUnfreezeCalls() []struct { +// len(mockedSnapshotter.CanUnfreezeCalls()) +func (mock *SnapshotterMock) CanUnfreezeCalls() []struct { Ctx context.Context VdSnapshotName string VM *virtv2.VirtualMachine @@ -363,9 +363,9 @@ func (mock *LifeCycleSnapshotterMock) CanUnfreezeCalls() []struct { } // CreateSecret calls CreateSecretFunc. -func (mock *LifeCycleSnapshotterMock) CreateSecret(ctx context.Context, secret *corev1.Secret) (*corev1.Secret, error) { +func (mock *SnapshotterMock) CreateSecret(ctx context.Context, secret *corev1.Secret) (*corev1.Secret, error) { if mock.CreateSecretFunc == nil { - panic("LifeCycleSnapshotterMock.CreateSecretFunc: method is nil but LifeCycleSnapshotter.CreateSecret was just called") + panic("SnapshotterMock.CreateSecretFunc: method is nil but Snapshotter.CreateSecret was just called") } callInfo := struct { Ctx context.Context @@ -383,8 +383,8 @@ func (mock *LifeCycleSnapshotterMock) CreateSecret(ctx context.Context, secret * // CreateSecretCalls gets all the calls that were made to CreateSecret. // Check the length with: // -// len(mockedLifeCycleSnapshotter.CreateSecretCalls()) -func (mock *LifeCycleSnapshotterMock) CreateSecretCalls() []struct { +// len(mockedSnapshotter.CreateSecretCalls()) +func (mock *SnapshotterMock) CreateSecretCalls() []struct { Ctx context.Context Secret *corev1.Secret } { @@ -399,9 +399,9 @@ func (mock *LifeCycleSnapshotterMock) CreateSecretCalls() []struct { } // CreateVirtualDiskSnapshot calls CreateVirtualDiskSnapshotFunc. -func (mock *LifeCycleSnapshotterMock) CreateVirtualDiskSnapshot(ctx context.Context, vdSnapshot *virtv2.VirtualDiskSnapshot) (*virtv2.VirtualDiskSnapshot, error) { +func (mock *SnapshotterMock) CreateVirtualDiskSnapshot(ctx context.Context, vdSnapshot *virtv2.VirtualDiskSnapshot) (*virtv2.VirtualDiskSnapshot, error) { if mock.CreateVirtualDiskSnapshotFunc == nil { - panic("LifeCycleSnapshotterMock.CreateVirtualDiskSnapshotFunc: method is nil but LifeCycleSnapshotter.CreateVirtualDiskSnapshot was just called") + panic("SnapshotterMock.CreateVirtualDiskSnapshotFunc: method is nil but Snapshotter.CreateVirtualDiskSnapshot was just called") } callInfo := struct { Ctx context.Context @@ -419,8 +419,8 @@ func (mock *LifeCycleSnapshotterMock) CreateVirtualDiskSnapshot(ctx context.Cont // CreateVirtualDiskSnapshotCalls gets all the calls that were made to CreateVirtualDiskSnapshot. // Check the length with: // -// len(mockedLifeCycleSnapshotter.CreateVirtualDiskSnapshotCalls()) -func (mock *LifeCycleSnapshotterMock) CreateVirtualDiskSnapshotCalls() []struct { +// len(mockedSnapshotter.CreateVirtualDiskSnapshotCalls()) +func (mock *SnapshotterMock) CreateVirtualDiskSnapshotCalls() []struct { Ctx context.Context VdSnapshot *virtv2.VirtualDiskSnapshot } { @@ -435,9 +435,9 @@ func (mock *LifeCycleSnapshotterMock) CreateVirtualDiskSnapshotCalls() []struct } // Freeze calls FreezeFunc. -func (mock *LifeCycleSnapshotterMock) Freeze(ctx context.Context, name string, namespace string) error { +func (mock *SnapshotterMock) Freeze(ctx context.Context, name string, namespace string) error { if mock.FreezeFunc == nil { - panic("LifeCycleSnapshotterMock.FreezeFunc: method is nil but LifeCycleSnapshotter.Freeze was just called") + panic("SnapshotterMock.FreezeFunc: method is nil but Snapshotter.Freeze was just called") } callInfo := struct { Ctx context.Context @@ -457,8 +457,8 @@ func (mock *LifeCycleSnapshotterMock) Freeze(ctx context.Context, name string, n // FreezeCalls gets all the calls that were made to Freeze. // Check the length with: // -// len(mockedLifeCycleSnapshotter.FreezeCalls()) -func (mock *LifeCycleSnapshotterMock) FreezeCalls() []struct { +// len(mockedSnapshotter.FreezeCalls()) +func (mock *SnapshotterMock) FreezeCalls() []struct { Ctx context.Context Name string Namespace string @@ -475,9 +475,9 @@ func (mock *LifeCycleSnapshotterMock) FreezeCalls() []struct { } // GetPersistentVolumeClaim calls GetPersistentVolumeClaimFunc. -func (mock *LifeCycleSnapshotterMock) GetPersistentVolumeClaim(ctx context.Context, name string, namespace string) (*corev1.PersistentVolumeClaim, error) { +func (mock *SnapshotterMock) GetPersistentVolumeClaim(ctx context.Context, name string, namespace string) (*corev1.PersistentVolumeClaim, error) { if mock.GetPersistentVolumeClaimFunc == nil { - panic("LifeCycleSnapshotterMock.GetPersistentVolumeClaimFunc: method is nil but LifeCycleSnapshotter.GetPersistentVolumeClaim was just called") + panic("SnapshotterMock.GetPersistentVolumeClaimFunc: method is nil but Snapshotter.GetPersistentVolumeClaim was just called") } callInfo := struct { Ctx context.Context @@ -497,8 +497,8 @@ func (mock *LifeCycleSnapshotterMock) GetPersistentVolumeClaim(ctx context.Conte // GetPersistentVolumeClaimCalls gets all the calls that were made to GetPersistentVolumeClaim. // Check the length with: // -// len(mockedLifeCycleSnapshotter.GetPersistentVolumeClaimCalls()) -func (mock *LifeCycleSnapshotterMock) GetPersistentVolumeClaimCalls() []struct { +// len(mockedSnapshotter.GetPersistentVolumeClaimCalls()) +func (mock *SnapshotterMock) GetPersistentVolumeClaimCalls() []struct { Ctx context.Context Name string Namespace string @@ -515,9 +515,9 @@ func (mock *LifeCycleSnapshotterMock) GetPersistentVolumeClaimCalls() []struct { } // GetSecret calls GetSecretFunc. -func (mock *LifeCycleSnapshotterMock) GetSecret(ctx context.Context, name string, namespace string) (*corev1.Secret, error) { +func (mock *SnapshotterMock) GetSecret(ctx context.Context, name string, namespace string) (*corev1.Secret, error) { if mock.GetSecretFunc == nil { - panic("LifeCycleSnapshotterMock.GetSecretFunc: method is nil but LifeCycleSnapshotter.GetSecret was just called") + panic("SnapshotterMock.GetSecretFunc: method is nil but Snapshotter.GetSecret was just called") } callInfo := struct { Ctx context.Context @@ -537,8 +537,8 @@ func (mock *LifeCycleSnapshotterMock) GetSecret(ctx context.Context, name string // GetSecretCalls gets all the calls that were made to GetSecret. // Check the length with: // -// len(mockedLifeCycleSnapshotter.GetSecretCalls()) -func (mock *LifeCycleSnapshotterMock) GetSecretCalls() []struct { +// len(mockedSnapshotter.GetSecretCalls()) +func (mock *SnapshotterMock) GetSecretCalls() []struct { Ctx context.Context Name string Namespace string @@ -555,9 +555,9 @@ func (mock *LifeCycleSnapshotterMock) GetSecretCalls() []struct { } // GetVirtualDisk calls GetVirtualDiskFunc. -func (mock *LifeCycleSnapshotterMock) GetVirtualDisk(ctx context.Context, name string, namespace string) (*virtv2.VirtualDisk, error) { +func (mock *SnapshotterMock) GetVirtualDisk(ctx context.Context, name string, namespace string) (*virtv2.VirtualDisk, error) { if mock.GetVirtualDiskFunc == nil { - panic("LifeCycleSnapshotterMock.GetVirtualDiskFunc: method is nil but LifeCycleSnapshotter.GetVirtualDisk was just called") + panic("SnapshotterMock.GetVirtualDiskFunc: method is nil but Snapshotter.GetVirtualDisk was just called") } callInfo := struct { Ctx context.Context @@ -577,8 +577,8 @@ func (mock *LifeCycleSnapshotterMock) GetVirtualDisk(ctx context.Context, name s // GetVirtualDiskCalls gets all the calls that were made to GetVirtualDisk. // Check the length with: // -// len(mockedLifeCycleSnapshotter.GetVirtualDiskCalls()) -func (mock *LifeCycleSnapshotterMock) GetVirtualDiskCalls() []struct { +// len(mockedSnapshotter.GetVirtualDiskCalls()) +func (mock *SnapshotterMock) GetVirtualDiskCalls() []struct { Ctx context.Context Name string Namespace string @@ -595,9 +595,9 @@ func (mock *LifeCycleSnapshotterMock) GetVirtualDiskCalls() []struct { } // GetVirtualDiskSnapshot calls GetVirtualDiskSnapshotFunc. -func (mock *LifeCycleSnapshotterMock) GetVirtualDiskSnapshot(ctx context.Context, name string, namespace string) (*virtv2.VirtualDiskSnapshot, error) { +func (mock *SnapshotterMock) GetVirtualDiskSnapshot(ctx context.Context, name string, namespace string) (*virtv2.VirtualDiskSnapshot, error) { if mock.GetVirtualDiskSnapshotFunc == nil { - panic("LifeCycleSnapshotterMock.GetVirtualDiskSnapshotFunc: method is nil but LifeCycleSnapshotter.GetVirtualDiskSnapshot was just called") + panic("SnapshotterMock.GetVirtualDiskSnapshotFunc: method is nil but Snapshotter.GetVirtualDiskSnapshot was just called") } callInfo := struct { Ctx context.Context @@ -617,8 +617,8 @@ func (mock *LifeCycleSnapshotterMock) GetVirtualDiskSnapshot(ctx context.Context // GetVirtualDiskSnapshotCalls gets all the calls that were made to GetVirtualDiskSnapshot. // Check the length with: // -// len(mockedLifeCycleSnapshotter.GetVirtualDiskSnapshotCalls()) -func (mock *LifeCycleSnapshotterMock) GetVirtualDiskSnapshotCalls() []struct { +// len(mockedSnapshotter.GetVirtualDiskSnapshotCalls()) +func (mock *SnapshotterMock) GetVirtualDiskSnapshotCalls() []struct { Ctx context.Context Name string Namespace string @@ -635,9 +635,9 @@ func (mock *LifeCycleSnapshotterMock) GetVirtualDiskSnapshotCalls() []struct { } // GetVirtualMachine calls GetVirtualMachineFunc. -func (mock *LifeCycleSnapshotterMock) GetVirtualMachine(ctx context.Context, name string, namespace string) (*virtv2.VirtualMachine, error) { +func (mock *SnapshotterMock) GetVirtualMachine(ctx context.Context, name string, namespace string) (*virtv2.VirtualMachine, error) { if mock.GetVirtualMachineFunc == nil { - panic("LifeCycleSnapshotterMock.GetVirtualMachineFunc: method is nil but LifeCycleSnapshotter.GetVirtualMachine was just called") + panic("SnapshotterMock.GetVirtualMachineFunc: method is nil but Snapshotter.GetVirtualMachine was just called") } callInfo := struct { Ctx context.Context @@ -657,8 +657,8 @@ func (mock *LifeCycleSnapshotterMock) GetVirtualMachine(ctx context.Context, nam // GetVirtualMachineCalls gets all the calls that were made to GetVirtualMachine. // Check the length with: // -// len(mockedLifeCycleSnapshotter.GetVirtualMachineCalls()) -func (mock *LifeCycleSnapshotterMock) GetVirtualMachineCalls() []struct { +// len(mockedSnapshotter.GetVirtualMachineCalls()) +func (mock *SnapshotterMock) GetVirtualMachineCalls() []struct { Ctx context.Context Name string Namespace string @@ -675,9 +675,9 @@ func (mock *LifeCycleSnapshotterMock) GetVirtualMachineCalls() []struct { } // IsFrozen calls IsFrozenFunc. -func (mock *LifeCycleSnapshotterMock) IsFrozen(vm *virtv2.VirtualMachine) bool { +func (mock *SnapshotterMock) IsFrozen(vm *virtv2.VirtualMachine) bool { if mock.IsFrozenFunc == nil { - panic("LifeCycleSnapshotterMock.IsFrozenFunc: method is nil but LifeCycleSnapshotter.IsFrozen was just called") + panic("SnapshotterMock.IsFrozenFunc: method is nil but Snapshotter.IsFrozen was just called") } callInfo := struct { VM *virtv2.VirtualMachine @@ -693,8 +693,8 @@ func (mock *LifeCycleSnapshotterMock) IsFrozen(vm *virtv2.VirtualMachine) bool { // IsFrozenCalls gets all the calls that were made to IsFrozen. // Check the length with: // -// len(mockedLifeCycleSnapshotter.IsFrozenCalls()) -func (mock *LifeCycleSnapshotterMock) IsFrozenCalls() []struct { +// len(mockedSnapshotter.IsFrozenCalls()) +func (mock *SnapshotterMock) IsFrozenCalls() []struct { VM *virtv2.VirtualMachine } { var calls []struct { @@ -707,9 +707,9 @@ func (mock *LifeCycleSnapshotterMock) IsFrozenCalls() []struct { } // Unfreeze calls UnfreezeFunc. -func (mock *LifeCycleSnapshotterMock) Unfreeze(ctx context.Context, name string, namespace string) error { +func (mock *SnapshotterMock) Unfreeze(ctx context.Context, name string, namespace string) error { if mock.UnfreezeFunc == nil { - panic("LifeCycleSnapshotterMock.UnfreezeFunc: method is nil but LifeCycleSnapshotter.Unfreeze was just called") + panic("SnapshotterMock.UnfreezeFunc: method is nil but Snapshotter.Unfreeze was just called") } callInfo := struct { Ctx context.Context @@ -729,8 +729,8 @@ func (mock *LifeCycleSnapshotterMock) Unfreeze(ctx context.Context, name string, // UnfreezeCalls gets all the calls that were made to Unfreeze. // Check the length with: // -// len(mockedLifeCycleSnapshotter.UnfreezeCalls()) -func (mock *LifeCycleSnapshotterMock) UnfreezeCalls() []struct { +// len(mockedSnapshotter.UnfreezeCalls()) +func (mock *SnapshotterMock) UnfreezeCalls() []struct { Ctx context.Context Name string Namespace string diff --git a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/secret_builder.go b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/secret_builder.go deleted file mode 100644 index f2795b013..000000000 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/secret_builder.go +++ /dev/null @@ -1,235 +0,0 @@ -/* -Copyright 2024 Flant JSC - -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. -*/ - -package internal - -import ( - "context" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "strings" - - 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" - - "github.com/deckhouse/virtualization-controller/pkg/controller/service" - "github.com/deckhouse/virtualization-controller/pkg/sdk/framework/helper" - virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" -) - -type SecretBuilder struct { - client client.Client -} - -func NewSecretBuilder(client client.Client) *SecretBuilder { - return &SecretBuilder{ - client: client, - } -} - -func (b SecretBuilder) Build(ctx context.Context, vm *virtv2.VirtualMachine, vmSnapshot *virtv2.VirtualMachineSnapshot) (*corev1.Secret, error) { - secret := corev1.Secret{ - TypeMeta: metav1.TypeMeta{ - Kind: "Secret", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: vmSnapshot.Status.VirtualMachineSnapshotSecretName, - Namespace: vmSnapshot.Namespace, - OwnerReferences: []metav1.OwnerReference{ - service.MakeOwnerReference(vmSnapshot), - }, - }, - Data: make(map[string][]byte), - Type: "virtualmachine.virtualization.deckhouse.io/snapshot", - } - - err := b.setVirtualMachine(&secret, vm) - if err != nil { - return nil, err - } - - err = b.setVirtualDisks(ctx, &secret, vm) - if err != nil { - return nil, err - } - - err = b.setVirtualMachineBlockDeviceAttachments(ctx, &secret, vm) - if err != nil { - return nil, err - } - - err = b.setVirtualMachineIPAddress(ctx, &secret, vm) - if err != nil { - return nil, err - } - - err = b.setProvisioning(ctx, &secret, vm) - if err != nil { - return nil, err - } - - return &secret, nil -} - -func (b SecretBuilder) setVirtualMachine(secret *corev1.Secret, vm *virtv2.VirtualMachine) error { - err := b.push(secret, "vm", vm) - if err != nil { - return err - } - - return nil -} - -func (b SecretBuilder) setVirtualDisks(ctx context.Context, secret *corev1.Secret, vm *virtv2.VirtualMachine) error { - for _, bdr := range vm.Status.BlockDeviceRefs { - if bdr.Kind != virtv2.DiskDevice { - continue - } - - vd, err := helper.FetchObject(ctx, types.NamespacedName{ - Name: bdr.Name, - Namespace: vm.Namespace, - }, b.client, &virtv2.VirtualDisk{}) - if err != nil { - return err - } - - if vd == nil { - return fmt.Errorf("the virtual disk %q not found", bdr.Name) - } - - err = b.push(secret, "vd-"+vd.Name, vd) - if err != nil { - return err - } - } - - return nil -} - -func (b SecretBuilder) setVirtualMachineBlockDeviceAttachments(ctx context.Context, secret *corev1.Secret, vm *virtv2.VirtualMachine) error { - for _, bdr := range vm.Status.BlockDeviceRefs { - if bdr.Kind != virtv2.DiskDevice || !bdr.Hotplugged { - continue - } - - vmbda, err := helper.FetchObject(ctx, types.NamespacedName{ - Name: bdr.VirtualMachineBlockDeviceAttachmentName, - Namespace: vm.Namespace, - }, b.client, &virtv2.VirtualMachineBlockDeviceAttachment{}) - if err != nil { - return err - } - - if vmbda == nil { - return fmt.Errorf("the virtual machine block device attachment %q not found", bdr.VirtualMachineBlockDeviceAttachmentName) - } - - err = b.push(secret, "vmbda-"+vmbda.Name, vmbda) - if err != nil { - return err - } - } - - return nil -} - -func (b SecretBuilder) setVirtualMachineIPAddress(ctx context.Context, secret *corev1.Secret, vm *virtv2.VirtualMachine) error { - vmip, err := helper.FetchObject(ctx, types.NamespacedName{ - Namespace: vm.Namespace, - Name: vm.Status.VirtualMachineIPAddress, - }, b.client, &virtv2.VirtualMachineIPAddress{}) - if err != nil { - return err - } - - if vmip == nil { - return fmt.Errorf("the virtual machine ip address %q not found", vm.Status.VirtualMachineIPAddress) - } - - err = b.push(secret, "vmip-"+vmip.Name, vmip) - if err != nil { - return err - } - - return nil -} - -func (b SecretBuilder) setProvisioning(ctx context.Context, secret *corev1.Secret, vm *virtv2.VirtualMachine) error { - var secretName string - - switch vm.Spec.Provisioning.Type { - case virtv2.ProvisioningTypeSysprepRef: - if vm.Spec.Provisioning.SysprepRef == nil { - return errors.New("the virtual machine sysprep ref provisioning is nil") - } - - switch vm.Spec.Provisioning.SysprepRef.Kind { - case virtv2.SysprepRefKindSecret: - secretName = vm.Spec.Provisioning.SysprepRef.Name - default: - return fmt.Errorf("unknown sysprep ref kind %s", vm.Spec.Provisioning.SysprepRef.Kind) - } - case virtv2.ProvisioningTypeUserDataRef: - if vm.Spec.Provisioning.UserDataRef == nil { - return errors.New("the virtual machine user data ref provisioning is nil") - } - - switch vm.Spec.Provisioning.UserDataRef.Kind { - case virtv2.UserDataRefKindSecret: - secretName = vm.Spec.Provisioning.UserDataRef.Name - default: - return fmt.Errorf("unknown user data ref kind %s", vm.Spec.Provisioning.UserDataRef.Kind) - } - default: - return nil - } - - ref, err := helper.FetchObject(ctx, types.NamespacedName{ - Name: secretName, - Namespace: vm.Namespace, - }, b.client, &corev1.Secret{}) - if err != nil { - return err - } - - if ref == nil { - return fmt.Errorf("the virtual machine provisioning secret %q not found", secretName) - } - - err = b.push(secret, strings.ToLower(string(vm.Spec.Provisioning.Type))+"-"+ref.Name, ref) - if err != nil { - return err - } - - return nil -} - -func (b SecretBuilder) push(secret *corev1.Secret, key string, value client.Object) error { - JSON, err := json.Marshal(value) - if err != nil { - return err - } - - secret.Data[key] = []byte(base64.StdEncoding.EncodeToString(JSON)) - - return nil -} diff --git a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/virtual_machine_ready.go b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/virtual_machine_ready.go index ae518dbe8..c233f626b 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/virtual_machine_ready.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/virtual_machine_ready.go @@ -23,6 +23,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" "github.com/deckhouse/virtualization-controller/pkg/controller/service" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/api/core/v1alpha2/vmcondition" @@ -44,28 +45,20 @@ func NewVirtualMachineReadyHandler(snapshotter VirtualMachineReadySnapshotter) * } func (h VirtualMachineReadyHandler) Handle(ctx context.Context, vmSnapshot *virtv2.VirtualMachineSnapshot) (reconcile.Result, error) { - condition, ok := service.GetCondition(vmscondition.VirtualMachineReadyType, vmSnapshot.Status.Conditions) - if !ok { - condition = metav1.Condition{ - Type: vmscondition.VirtualMachineReadyType, - Status: metav1.ConditionUnknown, - Reason: vmscondition.VirtualMachineUnknown, - } - } + cb := conditions.NewConditionBuilder(vmscondition.VirtualMachineReadyType) + defer func() { conditions.SetCondition(cb.Generation(vmSnapshot.Generation), &vmSnapshot.Status.Conditions) }() - defer func() { service.SetCondition(condition, &vmSnapshot.Status.Conditions) }() + if !conditions.HasCondition(cb.GetType(), vmSnapshot.Status.Conditions) { + cb.Status(metav1.ConditionUnknown).Reason(vmscondition.VirtualMachineUnknown) + } if vmSnapshot.DeletionTimestamp != nil { - condition.Status = metav1.ConditionUnknown - condition.Reason = vmscondition.VirtualMachineUnknown - condition.Message = "" + cb.Status(metav1.ConditionUnknown).Reason(vmscondition.VirtualMachineUnknown) return reconcile.Result{}, nil } if vmSnapshot.Status.Phase == virtv2.VirtualMachineSnapshotPhaseReady { - condition.Status = metav1.ConditionTrue - condition.Reason = vmscondition.VirtualMachineReady - condition.Message = "" + cb.Status(metav1.ConditionTrue).Reason(vmscondition.VirtualMachineReady) return reconcile.Result{}, nil } @@ -75,16 +68,18 @@ func (h VirtualMachineReadyHandler) Handle(ctx context.Context, vmSnapshot *virt } if vm == nil { - condition.Status = metav1.ConditionFalse - condition.Reason = vmscondition.VirtualMachineNotReadyForSnapshotting - condition.Message = fmt.Sprintf("The virtual machine %q not found.", vmSnapshot.Spec.VirtualMachineName) + cb. + Status(metav1.ConditionFalse). + Reason(vmscondition.VirtualMachineNotReadyForSnapshotting). + Message(fmt.Sprintf("The virtual machine %q not found.", vmSnapshot.Spec.VirtualMachineName)) return reconcile.Result{}, nil } if vm.GetDeletionTimestamp() != nil { - condition.Status = metav1.ConditionFalse - condition.Reason = vmscondition.VirtualMachineNotReadyForSnapshotting - condition.Message = fmt.Sprintf("The virtual machine %q is in process of deletion.", vm.Name) + cb. + Status(metav1.ConditionFalse). + Reason(vmscondition.VirtualMachineNotReadyForSnapshotting). + Message(fmt.Sprintf("The virtual machine %q is in process of deletion.", vm.Name)) return reconcile.Result{}, nil } @@ -92,25 +87,23 @@ func (h VirtualMachineReadyHandler) Handle(ctx context.Context, vmSnapshot *virt case virtv2.MachineRunning, virtv2.MachineStopped: snapshotting, _ := service.GetCondition(vmcondition.TypeSnapshotting.String(), vm.Status.Conditions) if snapshotting.Status != metav1.ConditionTrue { - condition.Status = metav1.ConditionFalse - condition.Reason = vmscondition.VirtualMachineNotReadyForSnapshotting + cb.Status(metav1.ConditionFalse).Reason(vmscondition.VirtualMachineNotReadyForSnapshotting) if snapshotting.Message == "" { - condition.Message = "The VirtualMachineSnapshot resource has not been detected for the virtual machine yet." + cb.Message("The VirtualMachineSnapshot resource has not been detected for the virtual machine yet.") } else { - condition.Message = snapshotting.Message + cb.Message(snapshotting.Message) } return reconcile.Result{}, nil } - condition.Status = metav1.ConditionTrue - condition.Reason = vmscondition.VirtualMachineReady - condition.Message = "" + cb.Status(metav1.ConditionTrue).Reason(vmscondition.VirtualMachineReady) return reconcile.Result{}, nil default: - condition.Status = metav1.ConditionFalse - condition.Reason = vmscondition.VirtualMachineNotReadyForSnapshotting - condition.Message = fmt.Sprintf("The virtual machine %q is in the %q phase: waiting for it to reach the Running or Stopped phase.", vm.Name, vm.Status.Phase) + cb. + Status(metav1.ConditionFalse). + Reason(vmscondition.VirtualMachineNotReadyForSnapshotting). + Message(fmt.Sprintf("The virtual machine %q is in the %q phase: waiting for it to reach the Running or Stopped phase.", vm.Name, vm.Status.Phase)) return reconcile.Result{}, nil } } diff --git a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/watcher/vd_watcher.go b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/watcher/vd_watcher.go index f6f2b4b02..5796440a6 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/watcher/vd_watcher.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/watcher/vd_watcher.go @@ -61,7 +61,6 @@ func (w VirtualDiskWatcher) Watch(mgr manager.Manager, ctr controller.Controller func (w VirtualDiskWatcher) enqueueRequests(ctx context.Context, obj client.Object) (requests []reconcile.Request) { var vmSnapshots virtv2.VirtualMachineSnapshotList - // TODO use index. err := w.client.List(ctx, &vmSnapshots, &client.ListOptions{ Namespace: obj.GetNamespace(), }) diff --git a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/watcher/vdsnapshot_watcher.go b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/watcher/vdsnapshot_watcher.go index 0bf8944c6..06df8cd5b 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/watcher/vdsnapshot_watcher.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/watcher/vdsnapshot_watcher.go @@ -21,6 +21,7 @@ import ( "fmt" "log/slog" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -31,6 +32,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" + "github.com/deckhouse/virtualization-controller/pkg/controller/indexer" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" ) @@ -58,15 +60,16 @@ func (w VirtualDiskSnapshotWatcher) Watch(mgr manager.Manager, ctr controller.Co func (w VirtualDiskSnapshotWatcher) enqueueRequests(ctx context.Context, obj client.Object) (requests []reconcile.Request) { var vmSnapshots virtv2.VirtualMachineSnapshotList - // TODO use index. err := w.client.List(ctx, &vmSnapshots, &client.ListOptions{ - Namespace: obj.GetNamespace(), + Namespace: obj.GetNamespace(), + FieldSelector: fields.OneTermEqualSelector(indexer.IndexFieldVMSnapshotByVDSnapshot, obj.GetName()), }) if err != nil { slog.Default().Error(fmt.Sprintf("failed to list virtual machine snapshots: %s", err)) return } + // TODO? for _, vmSnapshot := range vmSnapshots.Items { for _, vdSnapshotName := range vmSnapshot.Status.VirtualDiskSnapshotNames { if vdSnapshotName == obj.GetName() { diff --git a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/watcher/vm_watcher.go b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/watcher/vm_watcher.go index 66befde09..182bf9e3b 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/watcher/vm_watcher.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/watcher/vm_watcher.go @@ -21,6 +21,7 @@ import ( "fmt" "log/slog" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -31,6 +32,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" + "github.com/deckhouse/virtualization-controller/pkg/controller/indexer" "github.com/deckhouse/virtualization-controller/pkg/controller/service" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/api/core/v1alpha2/vmcondition" @@ -60,9 +62,9 @@ func (w VirtualMachineWatcher) Watch(mgr manager.Manager, ctr controller.Control func (w VirtualMachineWatcher) enqueueRequests(ctx context.Context, obj client.Object) (requests []reconcile.Request) { var vmSnapshots virtv2.VirtualMachineSnapshotList - // TODO use index. err := w.client.List(ctx, &vmSnapshots, &client.ListOptions{ - Namespace: obj.GetNamespace(), + Namespace: obj.GetNamespace(), + FieldSelector: fields.OneTermEqualSelector(indexer.IndexFieldVMSnapshotByVM, obj.GetName()), }) if err != nil { slog.Default().Error(fmt.Sprintf("failed to list virtual machine snapshots: %s", err)) @@ -99,7 +101,7 @@ func (w VirtualMachineWatcher) filterUpdateEvents(e event.UpdateEvent) bool { oldFSReady, _ := service.GetCondition(vmcondition.TypeFilesystemReady.String(), oldVM.Status.Conditions) newFSReady, _ := service.GetCondition(vmcondition.TypeFilesystemReady.String(), newVM.Status.Conditions) - if oldFSReady.Status != newFSReady.Status { + if oldFSReady.Reason != newFSReady.Reason { return true } diff --git a/images/virtualization-artifact/pkg/controller/vmsnapshot/vmsnapshot_controller.go b/images/virtualization-artifact/pkg/controller/vmsnapshot/vmsnapshot_controller.go index 5d1b70a3e..e6212b898 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/vmsnapshot_controller.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/vmsnapshot_controller.go @@ -19,6 +19,7 @@ package vmsnapshot import ( "context" "log/slog" + "time" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/builder" @@ -26,6 +27,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/manager" "github.com/deckhouse/virtualization-controller/pkg/controller/service" + "github.com/deckhouse/virtualization-controller/pkg/controller/service/restorer" "github.com/deckhouse/virtualization-controller/pkg/controller/vmsnapshot/internal" "github.com/deckhouse/virtualization-controller/pkg/logger" "github.com/deckhouse/virtualization/api/client/kubeclient" @@ -44,19 +46,19 @@ func NewController( protection := service.NewProtectionService(mgr.GetClient(), virtv2.FinalizerVMSnapshotProtection) snapshotter := service.NewSnapshotService(virtClient, mgr.GetClient(), protection) - secretBuilder := internal.NewSecretBuilder(mgr.GetClient()) reconciler := NewReconciler( mgr.GetClient(), internal.NewVirtualMachineReadyHandler(snapshotter), - internal.NewLifeCycleHandler(snapshotter, secretBuilder), + internal.NewLifeCycleHandler(snapshotter, restorer.NewSecretRestorer(mgr.GetClient())), internal.NewDeletionHandler(), ) vmSnapshotController, err := controller.New(ControllerName, mgr, controller.Options{ - Reconciler: reconciler, - RecoverPanic: ptr.To(true), - LogConstructor: logger.NewConstructor(log), + Reconciler: reconciler, + RecoverPanic: ptr.To(true), + LogConstructor: logger.NewConstructor(log), + CacheSyncTimeout: 10 * time.Minute, }) if err != nil { return nil, err diff --git a/images/virtualization-artifact/pkg/controller/vmsnapshot/vmsnapshot_webhook.go b/images/virtualization-artifact/pkg/controller/vmsnapshot/vmsnapshot_webhook.go index b50baa469..a50198594 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/vmsnapshot_webhook.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/vmsnapshot_webhook.go @@ -38,15 +38,8 @@ func NewValidator(logger *slog.Logger) *Validator { } func (v *Validator) ValidateCreate(_ context.Context, obj runtime.Object) (admission.Warnings, error) { - vmSnapshot, ok := obj.(*virtv2.VirtualMachineSnapshot) - if !ok { - return nil, fmt.Errorf("expected a VirtualMachineSnapshot but got a %T", obj) - } - - if vmSnapshot.Spec.VirtualMachineName == "" { - return nil, fmt.Errorf("virtual machine name cannot be empty") - } - + err := fmt.Errorf("misconfigured webhook rules: delete operation not implemented") + v.logger.Error("Ensure the correctness of ValidatingWebhookConfiguration", "err", err) return nil, nil } diff --git a/templates/virtualization-controller/rbac-for-us.yaml b/templates/virtualization-controller/rbac-for-us.yaml index 30c99bf3d..3861e2cf8 100644 --- a/templates/virtualization-controller/rbac-for-us.yaml +++ b/templates/virtualization-controller/rbac-for-us.yaml @@ -182,6 +182,7 @@ rules: - virtualmachineclasses - virtualdisksnapshots - virtualmachinesnapshots + - virtualmachinerestores verbs: - create - delete @@ -204,6 +205,7 @@ rules: - virtualmachineclasses/finalizers - virtualdisksnapshots/finalizers - virtualmachinesnapshots/finalizers + - virtualmachinerestores/finalizers - virtualmachineipaddresses/status - virtualmachineipaddressleases/status - virtualdisks/status @@ -215,6 +217,7 @@ rules: - virtualmachineclasses/status - virtualdisksnapshots/status - virtualmachinesnapshots/status + - virtualmachinerestores/status verbs: - patch - update diff --git a/templates/virtualization-controller/validation-webhook.yaml b/templates/virtualization-controller/validation-webhook.yaml index 2c6035af4..3eabeba17 100644 --- a/templates/virtualization-controller/validation-webhook.yaml +++ b/templates/virtualization-controller/validation-webhook.yaml @@ -143,7 +143,7 @@ webhooks: rules: - apiGroups: ["virtualization.deckhouse.io"] apiVersions: ["v1alpha2"] - operations: ["CREATE", "UPDATE"] + operations: ["UPDATE"] resources: ["virtualdisksnapshots"] scope: "Namespaced" clientConfig: @@ -156,6 +156,40 @@ webhooks: {{ .Values.virtualization.internal.controller.cert.ca }} admissionReviewVersions: ["v1"] sideEffects: None + - name: "vmsnapshot.virtualization-controller.validate.d8-virtualization" + rules: + - apiGroups: ["virtualization.deckhouse.io"] + apiVersions: ["v1alpha2"] + operations: ["UPDATE"] + resources: ["virtualmachinesnapshots"] + scope: "Namespaced" + clientConfig: + service: + namespace: d8-{{ .Chart.Name }} + name: virtualization-controller + path: /validate-virtualization-deckhouse-io-v1alpha2-virtualmachinesnapshot + port: 443 + caBundle: | + {{ .Values.virtualization.internal.controller.cert.ca }} + admissionReviewVersions: ["v1"] + sideEffects: None + - name: "vmrestore.virtualization-controller.validate.d8-virtualization" + rules: + - apiGroups: ["virtualization.deckhouse.io"] + apiVersions: ["v1alpha2"] + operations: ["UPDATE"] + resources: ["virtualmachinerestores"] + scope: "Namespaced" + clientConfig: + service: + namespace: d8-{{ .Chart.Name }} + name: virtualization-controller + path: /validate-virtualization-deckhouse-io-v1alpha2-virtualmachinerestore + port: 443 + caBundle: | + {{ .Values.virtualization.internal.controller.cert.ca }} + admissionReviewVersions: ["v1"] + sideEffects: None - name: "vmclass.virtualization-controller.validate.d8-virtualization" rules: - apiGroups: ["virtualization.deckhouse.io"] @@ -172,4 +206,4 @@ webhooks: caBundle: | {{ .Values.virtualization.internal.controller.cert.ca }} admissionReviewVersions: ["v1"] - sideEffects: None \ No newline at end of file + sideEffects: None