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 44c441691..85a392296 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 @@ -29,6 +29,7 @@ type VirtualizationV1alpha2Interface interface { RESTClient() rest.Interface ClusterVirtualImagesGetter VirtualDisksGetter + VirtualDiskSnapshotsGetter VirtualImagesGetter VirtualMachinesGetter VirtualMachineBlockDeviceAttachmentsGetter @@ -51,6 +52,10 @@ func (c *VirtualizationV1alpha2Client) VirtualDisks(namespace string) VirtualDis return newVirtualDisks(c, namespace) } +func (c *VirtualizationV1alpha2Client) VirtualDiskSnapshots(namespace string) VirtualDiskSnapshotInterface { + return newVirtualDiskSnapshots(c, namespace) +} + func (c *VirtualizationV1alpha2Client) VirtualImages(namespace string) VirtualImageInterface { return newVirtualImages(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 eca3d2eca..a0a9c895e 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 @@ -35,6 +35,10 @@ func (c *FakeVirtualizationV1alpha2) VirtualDisks(namespace string) v1alpha2.Vir return &FakeVirtualDisks{c, namespace} } +func (c *FakeVirtualizationV1alpha2) VirtualDiskSnapshots(namespace string) v1alpha2.VirtualDiskSnapshotInterface { + return &FakeVirtualDiskSnapshots{c, namespace} +} + func (c *FakeVirtualizationV1alpha2) VirtualImages(namespace string) v1alpha2.VirtualImageInterface { return &FakeVirtualImages{c, namespace} } diff --git a/api/client/generated/clientset/versioned/typed/core/v1alpha2/fake/fake_virtualdisksnapshot.go b/api/client/generated/clientset/versioned/typed/core/v1alpha2/fake/fake_virtualdisksnapshot.go new file mode 100644 index 000000000..ea813e7cd --- /dev/null +++ b/api/client/generated/clientset/versioned/typed/core/v1alpha2/fake/fake_virtualdisksnapshot.go @@ -0,0 +1,140 @@ +/* +Copyright 2022 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" +) + +// FakeVirtualDiskSnapshots implements VirtualDiskSnapshotInterface +type FakeVirtualDiskSnapshots struct { + Fake *FakeVirtualizationV1alpha2 + ns string +} + +var virtualdisksnapshotsResource = v1alpha2.SchemeGroupVersion.WithResource("virtualdisksnapshots") + +var virtualdisksnapshotsKind = v1alpha2.SchemeGroupVersion.WithKind("VirtualDiskSnapshot") + +// Get takes name of the virtualDiskSnapshot, and returns the corresponding virtualDiskSnapshot object, and an error if there is any. +func (c *FakeVirtualDiskSnapshots) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha2.VirtualDiskSnapshot, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(virtualdisksnapshotsResource, c.ns, name), &v1alpha2.VirtualDiskSnapshot{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.VirtualDiskSnapshot), err +} + +// List takes label and field selectors, and returns the list of VirtualDiskSnapshots that match those selectors. +func (c *FakeVirtualDiskSnapshots) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha2.VirtualDiskSnapshotList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(virtualdisksnapshotsResource, virtualdisksnapshotsKind, c.ns, opts), &v1alpha2.VirtualDiskSnapshotList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha2.VirtualDiskSnapshotList{ListMeta: obj.(*v1alpha2.VirtualDiskSnapshotList).ListMeta} + for _, item := range obj.(*v1alpha2.VirtualDiskSnapshotList).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 virtualDiskSnapshots. +func (c *FakeVirtualDiskSnapshots) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(virtualdisksnapshotsResource, c.ns, opts)) + +} + +// Create takes the representation of a virtualDiskSnapshot and creates it. Returns the server's representation of the virtualDiskSnapshot, and an error, if there is any. +func (c *FakeVirtualDiskSnapshots) Create(ctx context.Context, virtualDiskSnapshot *v1alpha2.VirtualDiskSnapshot, opts v1.CreateOptions) (result *v1alpha2.VirtualDiskSnapshot, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(virtualdisksnapshotsResource, c.ns, virtualDiskSnapshot), &v1alpha2.VirtualDiskSnapshot{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.VirtualDiskSnapshot), err +} + +// Update takes the representation of a virtualDiskSnapshot and updates it. Returns the server's representation of the virtualDiskSnapshot, and an error, if there is any. +func (c *FakeVirtualDiskSnapshots) Update(ctx context.Context, virtualDiskSnapshot *v1alpha2.VirtualDiskSnapshot, opts v1.UpdateOptions) (result *v1alpha2.VirtualDiskSnapshot, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(virtualdisksnapshotsResource, c.ns, virtualDiskSnapshot), &v1alpha2.VirtualDiskSnapshot{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.VirtualDiskSnapshot), 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 *FakeVirtualDiskSnapshots) UpdateStatus(ctx context.Context, virtualDiskSnapshot *v1alpha2.VirtualDiskSnapshot, opts v1.UpdateOptions) (*v1alpha2.VirtualDiskSnapshot, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(virtualdisksnapshotsResource, "status", c.ns, virtualDiskSnapshot), &v1alpha2.VirtualDiskSnapshot{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.VirtualDiskSnapshot), err +} + +// Delete takes name of the virtualDiskSnapshot and deletes it. Returns an error if one occurs. +func (c *FakeVirtualDiskSnapshots) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(virtualdisksnapshotsResource, c.ns, name, opts), &v1alpha2.VirtualDiskSnapshot{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeVirtualDiskSnapshots) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(virtualdisksnapshotsResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha2.VirtualDiskSnapshotList{}) + return err +} + +// Patch applies the patch and returns the patched virtualDiskSnapshot. +func (c *FakeVirtualDiskSnapshots) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.VirtualDiskSnapshot, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(virtualdisksnapshotsResource, c.ns, name, pt, data, subresources...), &v1alpha2.VirtualDiskSnapshot{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.VirtualDiskSnapshot), err +} 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 d0cf841a8..3eb448f15 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 @@ -21,6 +21,8 @@ type ClusterVirtualImageExpansion interface{} type VirtualDiskExpansion interface{} +type VirtualDiskSnapshotExpansion interface{} + type VirtualImageExpansion interface{} type VirtualMachineExpansion interface{} diff --git a/api/client/generated/clientset/versioned/typed/core/v1alpha2/virtualdisksnapshot.go b/api/client/generated/clientset/versioned/typed/core/v1alpha2/virtualdisksnapshot.go new file mode 100644 index 000000000..17a647690 --- /dev/null +++ b/api/client/generated/clientset/versioned/typed/core/v1alpha2/virtualdisksnapshot.go @@ -0,0 +1,194 @@ +/* +Copyright 2022 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" +) + +// VirtualDiskSnapshotsGetter has a method to return a VirtualDiskSnapshotInterface. +// A group's client should implement this interface. +type VirtualDiskSnapshotsGetter interface { + VirtualDiskSnapshots(namespace string) VirtualDiskSnapshotInterface +} + +// VirtualDiskSnapshotInterface has methods to work with VirtualDiskSnapshot resources. +type VirtualDiskSnapshotInterface interface { + Create(ctx context.Context, virtualDiskSnapshot *v1alpha2.VirtualDiskSnapshot, opts v1.CreateOptions) (*v1alpha2.VirtualDiskSnapshot, error) + Update(ctx context.Context, virtualDiskSnapshot *v1alpha2.VirtualDiskSnapshot, opts v1.UpdateOptions) (*v1alpha2.VirtualDiskSnapshot, error) + UpdateStatus(ctx context.Context, virtualDiskSnapshot *v1alpha2.VirtualDiskSnapshot, opts v1.UpdateOptions) (*v1alpha2.VirtualDiskSnapshot, 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.VirtualDiskSnapshot, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha2.VirtualDiskSnapshotList, 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.VirtualDiskSnapshot, err error) + VirtualDiskSnapshotExpansion +} + +// virtualDiskSnapshots implements VirtualDiskSnapshotInterface +type virtualDiskSnapshots struct { + client rest.Interface + ns string +} + +// newVirtualDiskSnapshots returns a VirtualDiskSnapshots +func newVirtualDiskSnapshots(c *VirtualizationV1alpha2Client, namespace string) *virtualDiskSnapshots { + return &virtualDiskSnapshots{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the virtualDiskSnapshot, and returns the corresponding virtualDiskSnapshot object, and an error if there is any. +func (c *virtualDiskSnapshots) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha2.VirtualDiskSnapshot, err error) { + result = &v1alpha2.VirtualDiskSnapshot{} + err = c.client.Get(). + Namespace(c.ns). + Resource("virtualdisksnapshots"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of VirtualDiskSnapshots that match those selectors. +func (c *virtualDiskSnapshots) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha2.VirtualDiskSnapshotList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha2.VirtualDiskSnapshotList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("virtualdisksnapshots"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested virtualDiskSnapshots. +func (c *virtualDiskSnapshots) 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("virtualdisksnapshots"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a virtualDiskSnapshot and creates it. Returns the server's representation of the virtualDiskSnapshot, and an error, if there is any. +func (c *virtualDiskSnapshots) Create(ctx context.Context, virtualDiskSnapshot *v1alpha2.VirtualDiskSnapshot, opts v1.CreateOptions) (result *v1alpha2.VirtualDiskSnapshot, err error) { + result = &v1alpha2.VirtualDiskSnapshot{} + err = c.client.Post(). + Namespace(c.ns). + Resource("virtualdisksnapshots"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(virtualDiskSnapshot). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a virtualDiskSnapshot and updates it. Returns the server's representation of the virtualDiskSnapshot, and an error, if there is any. +func (c *virtualDiskSnapshots) Update(ctx context.Context, virtualDiskSnapshot *v1alpha2.VirtualDiskSnapshot, opts v1.UpdateOptions) (result *v1alpha2.VirtualDiskSnapshot, err error) { + result = &v1alpha2.VirtualDiskSnapshot{} + err = c.client.Put(). + Namespace(c.ns). + Resource("virtualdisksnapshots"). + Name(virtualDiskSnapshot.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(virtualDiskSnapshot). + 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 *virtualDiskSnapshots) UpdateStatus(ctx context.Context, virtualDiskSnapshot *v1alpha2.VirtualDiskSnapshot, opts v1.UpdateOptions) (result *v1alpha2.VirtualDiskSnapshot, err error) { + result = &v1alpha2.VirtualDiskSnapshot{} + err = c.client.Put(). + Namespace(c.ns). + Resource("virtualdisksnapshots"). + Name(virtualDiskSnapshot.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(virtualDiskSnapshot). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the virtualDiskSnapshot and deletes it. Returns an error if one occurs. +func (c *virtualDiskSnapshots) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("virtualdisksnapshots"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *virtualDiskSnapshots) 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("virtualdisksnapshots"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched virtualDiskSnapshot. +func (c *virtualDiskSnapshots) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.VirtualDiskSnapshot, err error) { + result = &v1alpha2.VirtualDiskSnapshot{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("virtualdisksnapshots"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/api/client/generated/informers/externalversions/core/v1alpha2/interface.go b/api/client/generated/informers/externalversions/core/v1alpha2/interface.go index 1fd4e0df5..e4ad41bdc 100644 --- a/api/client/generated/informers/externalversions/core/v1alpha2/interface.go +++ b/api/client/generated/informers/externalversions/core/v1alpha2/interface.go @@ -27,6 +27,8 @@ type Interface interface { ClusterVirtualImages() ClusterVirtualImageInformer // VirtualDisks returns a VirtualDiskInformer. VirtualDisks() VirtualDiskInformer + // VirtualDiskSnapshots returns a VirtualDiskSnapshotInformer. + VirtualDiskSnapshots() VirtualDiskSnapshotInformer // VirtualImages returns a VirtualImageInformer. VirtualImages() VirtualImageInformer // VirtualMachines returns a VirtualMachineInformer. @@ -64,6 +66,11 @@ func (v *version) VirtualDisks() VirtualDiskInformer { return &virtualDiskInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } +// VirtualDiskSnapshots returns a VirtualDiskSnapshotInformer. +func (v *version) VirtualDiskSnapshots() VirtualDiskSnapshotInformer { + return &virtualDiskSnapshotInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // VirtualImages returns a VirtualImageInformer. func (v *version) VirtualImages() VirtualImageInformer { return &virtualImageInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/api/client/generated/informers/externalversions/core/v1alpha2/virtualdisksnapshot.go b/api/client/generated/informers/externalversions/core/v1alpha2/virtualdisksnapshot.go new file mode 100644 index 000000000..b6aa52b63 --- /dev/null +++ b/api/client/generated/informers/externalversions/core/v1alpha2/virtualdisksnapshot.go @@ -0,0 +1,89 @@ +/* +Copyright 2022 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" +) + +// VirtualDiskSnapshotInformer provides access to a shared informer and lister for +// VirtualDiskSnapshots. +type VirtualDiskSnapshotInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha2.VirtualDiskSnapshotLister +} + +type virtualDiskSnapshotInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewVirtualDiskSnapshotInformer constructs a new informer for VirtualDiskSnapshot 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 NewVirtualDiskSnapshotInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredVirtualDiskSnapshotInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredVirtualDiskSnapshotInformer constructs a new informer for VirtualDiskSnapshot 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 NewFilteredVirtualDiskSnapshotInformer(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().VirtualDiskSnapshots(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.VirtualizationV1alpha2().VirtualDiskSnapshots(namespace).Watch(context.TODO(), options) + }, + }, + &corev1alpha2.VirtualDiskSnapshot{}, + resyncPeriod, + indexers, + ) +} + +func (f *virtualDiskSnapshotInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredVirtualDiskSnapshotInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *virtualDiskSnapshotInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&corev1alpha2.VirtualDiskSnapshot{}, f.defaultInformer) +} + +func (f *virtualDiskSnapshotInformer) Lister() v1alpha2.VirtualDiskSnapshotLister { + return v1alpha2.NewVirtualDiskSnapshotLister(f.Informer().GetIndexer()) +} diff --git a/api/client/generated/informers/externalversions/generic.go b/api/client/generated/informers/externalversions/generic.go index ff7e015d9..715f77b05 100644 --- a/api/client/generated/informers/externalversions/generic.go +++ b/api/client/generated/informers/externalversions/generic.go @@ -56,6 +56,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Virtualization().V1alpha2().ClusterVirtualImages().Informer()}, nil case v1alpha2.SchemeGroupVersion.WithResource("virtualdisks"): return &genericInformer{resource: resource.GroupResource(), informer: f.Virtualization().V1alpha2().VirtualDisks().Informer()}, nil + case v1alpha2.SchemeGroupVersion.WithResource("virtualdisksnapshots"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Virtualization().V1alpha2().VirtualDiskSnapshots().Informer()}, nil case v1alpha2.SchemeGroupVersion.WithResource("virtualimages"): return &genericInformer{resource: resource.GroupResource(), informer: f.Virtualization().V1alpha2().VirtualImages().Informer()}, nil case v1alpha2.SchemeGroupVersion.WithResource("virtualmachines"): diff --git a/api/client/generated/listers/core/v1alpha2/expansion_generated.go b/api/client/generated/listers/core/v1alpha2/expansion_generated.go index cbdf66178..85471e5df 100644 --- a/api/client/generated/listers/core/v1alpha2/expansion_generated.go +++ b/api/client/generated/listers/core/v1alpha2/expansion_generated.go @@ -29,6 +29,14 @@ type VirtualDiskListerExpansion interface{} // VirtualDiskNamespaceLister. type VirtualDiskNamespaceListerExpansion interface{} +// VirtualDiskSnapshotListerExpansion allows custom methods to be added to +// VirtualDiskSnapshotLister. +type VirtualDiskSnapshotListerExpansion interface{} + +// VirtualDiskSnapshotNamespaceListerExpansion allows custom methods to be added to +// VirtualDiskSnapshotNamespaceLister. +type VirtualDiskSnapshotNamespaceListerExpansion interface{} + // VirtualImageListerExpansion allows custom methods to be added to // VirtualImageLister. type VirtualImageListerExpansion interface{} diff --git a/api/client/generated/listers/core/v1alpha2/virtualdisksnapshot.go b/api/client/generated/listers/core/v1alpha2/virtualdisksnapshot.go new file mode 100644 index 000000000..ac2e910fd --- /dev/null +++ b/api/client/generated/listers/core/v1alpha2/virtualdisksnapshot.go @@ -0,0 +1,98 @@ +/* +Copyright 2022 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" +) + +// VirtualDiskSnapshotLister helps list VirtualDiskSnapshots. +// All objects returned here must be treated as read-only. +type VirtualDiskSnapshotLister interface { + // List lists all VirtualDiskSnapshots in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha2.VirtualDiskSnapshot, err error) + // VirtualDiskSnapshots returns an object that can list and get VirtualDiskSnapshots. + VirtualDiskSnapshots(namespace string) VirtualDiskSnapshotNamespaceLister + VirtualDiskSnapshotListerExpansion +} + +// virtualDiskSnapshotLister implements the VirtualDiskSnapshotLister interface. +type virtualDiskSnapshotLister struct { + indexer cache.Indexer +} + +// NewVirtualDiskSnapshotLister returns a new VirtualDiskSnapshotLister. +func NewVirtualDiskSnapshotLister(indexer cache.Indexer) VirtualDiskSnapshotLister { + return &virtualDiskSnapshotLister{indexer: indexer} +} + +// List lists all VirtualDiskSnapshots in the indexer. +func (s *virtualDiskSnapshotLister) List(selector labels.Selector) (ret []*v1alpha2.VirtualDiskSnapshot, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha2.VirtualDiskSnapshot)) + }) + return ret, err +} + +// VirtualDiskSnapshots returns an object that can list and get VirtualDiskSnapshots. +func (s *virtualDiskSnapshotLister) VirtualDiskSnapshots(namespace string) VirtualDiskSnapshotNamespaceLister { + return virtualDiskSnapshotNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// VirtualDiskSnapshotNamespaceLister helps list and get VirtualDiskSnapshots. +// All objects returned here must be treated as read-only. +type VirtualDiskSnapshotNamespaceLister interface { + // List lists all VirtualDiskSnapshots in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha2.VirtualDiskSnapshot, err error) + // Get retrieves the VirtualDiskSnapshot from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha2.VirtualDiskSnapshot, error) + VirtualDiskSnapshotNamespaceListerExpansion +} + +// virtualDiskSnapshotNamespaceLister implements the VirtualDiskSnapshotNamespaceLister +// interface. +type virtualDiskSnapshotNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all VirtualDiskSnapshots in the indexer for a given namespace. +func (s virtualDiskSnapshotNamespaceLister) List(selector labels.Selector) (ret []*v1alpha2.VirtualDiskSnapshot, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha2.VirtualDiskSnapshot)) + }) + return ret, err +} + +// Get retrieves the VirtualDiskSnapshot from the indexer for a given namespace and name. +func (s virtualDiskSnapshotNamespaceLister) Get(name string) (*v1alpha2.VirtualDiskSnapshot, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha2.Resource("virtualdisksnapshot"), name) + } + return obj.(*v1alpha2.VirtualDiskSnapshot), nil +} diff --git a/api/core/v1alpha2/finalizers.go b/api/core/v1alpha2/finalizers.go index 45bb8504a..04c537361 100644 --- a/api/core/v1alpha2/finalizers.go +++ b/api/core/v1alpha2/finalizers.go @@ -22,7 +22,6 @@ const ( FinalizerVDProtection = "virtualization.deckhouse.io/vd-protection" FinalizerKVVMProtection = "virtualization.deckhouse.io/kvvm-protection" FinalizerVMOPProtection = "virtualization.deckhouse.io/vmop-protection" - FinalizerVMCPUProtection = "virtualization.deckhouse.io/vmcpu-protection" FinalizerIPAddressProtection = "virtualization.deckhouse.io/vmip-protection" FinalizerPodProtection = "virtualization.deckhouse.io/pod-protection" @@ -33,6 +32,7 @@ const ( FinalizerIPAddressCleanup = "virtualization.deckhouse.io/vmip-cleanup" FinalizerIPAddressLeaseCleanup = "virtualization.deckhouse.io/vmipl-cleanup" FinalizerVMBDACleanup = "virtualization.deckhouse.io/vmbda-cleanup" + FinalizerVDSnapshotCleanup = "virtualization.deckhouse.io/vdsnapshot-cleanup" FinalizerVMOPCleanup = "virtualization.deckhouse.io/vmop-cleanup" FinalizerVMClassCleanup = "virtualization.deckhouse.io/vmclass-cleanup" ) diff --git a/api/core/v1alpha2/register.go b/api/core/v1alpha2/register.go index d841323a0..62b5c908e 100644 --- a/api/core/v1alpha2/register.go +++ b/api/core/v1alpha2/register.go @@ -80,6 +80,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { &VirtualMachineIPAddressLeaseList{}, &VirtualMachineOperation{}, &VirtualMachineOperationList{}, + &VirtualDiskSnapshot{}, + &VirtualDiskSnapshotList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil diff --git a/api/core/v1alpha2/vdcondition/condition.go b/api/core/v1alpha2/vdcondition/condition.go index a381b2d9c..bce0ab56d 100644 --- a/api/core/v1alpha2/vdcondition/condition.go +++ b/api/core/v1alpha2/vdcondition/condition.go @@ -26,6 +26,8 @@ const ( ReadyType Type = "Ready" // ResizedType indicates whether the disk resizing operation is completed. ResizedType Type = "Resized" + // SnapshottingType indicates whether the disk snapshotting operation is in progress. + SnapshottingType Type = "Snapshotting" ) type ( @@ -35,6 +37,8 @@ type ( ReadyReason = string // ResizedReason represents the various reasons for the Resized condition type. ResizedReason = string + // SnapshottingReason represents the various reasons for the Snapshotting condition type. + SnapshottingReason = string ) const ( @@ -46,6 +50,8 @@ const ( ImageNotReady DatasourceReadyReason = "ImageNotReady" // ClusterImageNotReady indicates that the `VirtualDisk` datasource is not ready, which prevents the import process from starting. ClusterImageNotReady DatasourceReadyReason = "ClusterImageNotReady" + // VirtualDiskSnapshotNotReady indicates that the `VirtualDiskSnapshot` datasource is not ready, which prevents the import process from starting. + VirtualDiskSnapshotNotReady DatasourceReadyReason = "VirtualDiskSnapshot" // WaitForUserUpload indicates that the `VirtualDisk` is waiting for the user to upload a datasource for the import process to continue. WaitForUserUpload ReadyReason = "WaitForUserUpload" @@ -62,10 +68,15 @@ const ( // Lost indicates that the underlying PersistentVolumeClaim has been lost and the `VirtualDisk` can no longer be used. Lost ReadyReason = "PVCLost" - // NotRequested indicates that the resize operation has not been requested yet. - NotRequested ResizedReason = "NotRequested" + // ResizingNotRequested indicates that the resize operation has not been requested yet. + ResizingNotRequested ResizedReason = "NotRequested" // InProgress indicates that the resize request has been detected and the operation is currently in progress. InProgress ResizedReason = "InProgress" // Resized indicates that the resize operation has been successfully completed. Resized ResizedReason = "Resized" + + // SnapshottingNotRequested indicates that the snapshotting operation has been successfully started and is in progress now. + SnapshottingNotRequested SnapshottingReason = "NotRequested" + // Snapshotting indicates that the snapshotting operation has been successfully started and is in progress now. + Snapshotting SnapshottingReason = "Snapshotting" ) diff --git a/api/core/v1alpha2/vdscondition/condition.go b/api/core/v1alpha2/vdscondition/condition.go new file mode 100644 index 000000000..163af4af8 --- /dev/null +++ b/api/core/v1alpha2/vdscondition/condition.go @@ -0,0 +1,52 @@ +/* +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 vdscondition + +// Type represents the various condition types for the `VirtualDiskSnapshot`. +type Type = string + +const ( + // VirtualDiskReadyType indicates that the block device (for example, a `VirtualDisk`) is ready to be hot-plugged. + VirtualDiskReadyType Type = "VirtualDiskReady" + // VirtualDiskSnapshotReadyType indicates that the virtual machine is ready for hot-plugging a block device. + VirtualDiskSnapshotReadyType Type = "VirtualDiskSnapshotReady" +) + +type ( + // VirtualDiskReadyReason represents the various reasons for the `BlockDeviceReady` condition type. + VirtualDiskReadyReason = string + // VirtualDiskSnapshotReadyReason represents the various reasons for the `VirtualMachineReady` condition type. + VirtualDiskSnapshotReadyReason = string +) + +const ( + // VirtualDiskReady signifies that the block device is ready to be hot-plugged, allowing the hot-plug process to start. + VirtualDiskReady VirtualDiskReadyReason = "VirtualDiskReady" + // VirtualDiskNotReady signifies that the block device is not ready, preventing the hot-plug process from starting. + VirtualDiskNotReady VirtualDiskReadyReason = "VirtualDiskNotReady" + + // WaitingForTheVirtualDisk signifies that the virtual machine is ready for hot-plugging a disk, allowing the hot-plug process to start. + WaitingForTheVirtualDisk VirtualDiskSnapshotReadyReason = "WaitingForTheVirtualDisk" + // FileSystemFreezing signifies that the virtual machine is ready for hot-plugging a disk, allowing the hot-plug process to start. + FileSystemFreezing VirtualDiskSnapshotReadyReason = "FileSystemFreezing" + // Snapshotting signifies that the virtual machine is ready for hot-plugging a disk, allowing the hot-plug process to start. + Snapshotting VirtualDiskSnapshotReadyReason = "Snapshotting" + // VirtualDiskSnapshotReady signifies that the virtual machine is ready for hot-plugging a disk, allowing the hot-plug process to start. + VirtualDiskSnapshotReady VirtualDiskSnapshotReadyReason = "VirtualDiskSnapshotReady" + // VirtualDiskSnapshotFailed signifies that the virtual machine is not ready, preventing the hot-plug process from starting. + VirtualDiskSnapshotFailed VirtualDiskSnapshotReadyReason = "VirtualDiskSnapshotFailed" +) diff --git a/api/core/v1alpha2/virtual_disk.go b/api/core/v1alpha2/virtual_disk.go index 83c6ccc7d..ac2f7f3a5 100644 --- a/api/core/v1alpha2/virtual_disk.go +++ b/api/core/v1alpha2/virtual_disk.go @@ -96,6 +96,7 @@ type VirtualDiskObjectRefKind string const ( VirtualDiskObjectRefKindVirtualImage VirtualDiskObjectRefKind = "VirtualImage" VirtualDiskObjectRefKindClusterVirtualImage VirtualDiskObjectRefKind = "ClusterVirtualImage" + VirtualDiskObjectRefKindVirtualDiskSnapshot VirtualDiskObjectRefKind = "VirtualDiskSnapshot" ) type DiskTarget struct { diff --git a/api/core/v1alpha2/virtual_disk_snapshot.go b/api/core/v1alpha2/virtual_disk_snapshot.go new file mode 100644 index 000000000..7ee21adae --- /dev/null +++ b/api/core/v1alpha2/virtual_disk_snapshot.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 v1alpha2 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + VirtualDiskSnapshotKind = "VirtualDiskSnapshot" + VirtualDiskSnapshotResource = "virtualdisksnapshots" +) + +// VirtualDiskSnapshot +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type VirtualDiskSnapshot struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec VirtualDiskSnapshotSpec `json:"spec"` + Status VirtualDiskSnapshotStatus `json:"status,omitempty"` +} + +// VirtualDiskSnapshotList contains a list of VirtualDiskSnapshot +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type VirtualDiskSnapshotList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + Items []VirtualDiskSnapshot `json:"items"` +} + +type VirtualDiskSnapshotSpec struct { + VirtualDiskName string `json:"virtualDiskName"` + VolumeSnapshotClassName string `json:"volumeSnapshotClassName"` + AllowPotentiallyInconsistent bool `json:"allowPotentiallyInconsistent"` +} + +type VirtualDiskSnapshotStatus struct { + Phase VirtualDiskSnapshotPhase `json:"phase"` + VolumeSnapshotName string `json:"volumeSnapshotName,omitempty"` + Conditions []metav1.Condition `json:"conditions,omitempty"` + ObservedGeneration int64 `json:"observedGeneration,omitempty"` +} + +type VirtualDiskSnapshotPhase string + +const ( + VirtualDiskSnapshotPhasePending VirtualDiskSnapshotPhase = "Pending" + VirtualDiskSnapshotPhaseInProgress VirtualDiskSnapshotPhase = "InProgress" + VirtualDiskSnapshotPhaseReady VirtualDiskSnapshotPhase = "Ready" + VirtualDiskSnapshotPhaseFailed VirtualDiskSnapshotPhase = "Failed" + VirtualDiskSnapshotPhaseTerminating VirtualDiskSnapshotPhase = "Terminating" +) diff --git a/api/core/v1alpha2/zz_generated.deepcopy.go b/api/core/v1alpha2/zz_generated.deepcopy.go index 106176653..4511a7e50 100644 --- a/api/core/v1alpha2/zz_generated.deepcopy.go +++ b/api/core/v1alpha2/zz_generated.deepcopy.go @@ -886,6 +886,106 @@ func (in *VirtualDiskPersistentVolumeClaim) DeepCopy() *VirtualDiskPersistentVol return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VirtualDiskSnapshot) DeepCopyInto(out *VirtualDiskSnapshot) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualDiskSnapshot. +func (in *VirtualDiskSnapshot) DeepCopy() *VirtualDiskSnapshot { + if in == nil { + return nil + } + out := new(VirtualDiskSnapshot) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VirtualDiskSnapshot) 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 *VirtualDiskSnapshotList) DeepCopyInto(out *VirtualDiskSnapshotList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]VirtualDiskSnapshot, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualDiskSnapshotList. +func (in *VirtualDiskSnapshotList) DeepCopy() *VirtualDiskSnapshotList { + if in == nil { + return nil + } + out := new(VirtualDiskSnapshotList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VirtualDiskSnapshotList) 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 *VirtualDiskSnapshotSpec) DeepCopyInto(out *VirtualDiskSnapshotSpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualDiskSnapshotSpec. +func (in *VirtualDiskSnapshotSpec) DeepCopy() *VirtualDiskSnapshotSpec { + if in == nil { + return nil + } + out := new(VirtualDiskSnapshotSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VirtualDiskSnapshotStatus) DeepCopyInto(out *VirtualDiskSnapshotStatus) { + *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 VirtualDiskSnapshotStatus. +func (in *VirtualDiskSnapshotStatus) DeepCopy() *VirtualDiskSnapshotStatus { + if in == nil { + return nil + } + out := new(VirtualDiskSnapshotStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VirtualDiskSpec) DeepCopyInto(out *VirtualDiskSpec) { *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 c5c919753..2240ea68a 100644 --- a/api/pkg/apiserver/api/generated/openapi/zz_generated.openapi.go +++ b/api/pkg/apiserver/api/generated/openapi/zz_generated.openapi.go @@ -72,6 +72,10 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualDiskList": schema_virtualization_api_core_v1alpha2_VirtualDiskList(ref), "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualDiskObjectRef": schema_virtualization_api_core_v1alpha2_VirtualDiskObjectRef(ref), "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualDiskPersistentVolumeClaim": schema_virtualization_api_core_v1alpha2_VirtualDiskPersistentVolumeClaim(ref), + "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualDiskSnapshot": schema_virtualization_api_core_v1alpha2_VirtualDiskSnapshot(ref), + "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualDiskSnapshotList": schema_virtualization_api_core_v1alpha2_VirtualDiskSnapshotList(ref), + "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualDiskSnapshotSpec": schema_virtualization_api_core_v1alpha2_VirtualDiskSnapshotSpec(ref), + "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualDiskSnapshotStatus": schema_virtualization_api_core_v1alpha2_VirtualDiskSnapshotStatus(ref), "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualDiskSpec": schema_virtualization_api_core_v1alpha2_VirtualDiskSpec(ref), "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualDiskStats": schema_virtualization_api_core_v1alpha2_VirtualDiskStats(ref), "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualDiskStatsCreationDuration": schema_virtualization_api_core_v1alpha2_VirtualDiskStatsCreationDuration(ref), @@ -119,8 +123,10 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/deckhouse/virtualization/api/core/v1alpha2.WeightedVirtualMachineAndPodAffinityTerm": schema_virtualization_api_core_v1alpha2_WeightedVirtualMachineAndPodAffinityTerm(ref), "github.com/deckhouse/virtualization/api/subresources/v1alpha2.VirtualMachineAddVolume": schema_virtualization_api_subresources_v1alpha2_VirtualMachineAddVolume(ref), "github.com/deckhouse/virtualization/api/subresources/v1alpha2.VirtualMachineConsole": schema_virtualization_api_subresources_v1alpha2_VirtualMachineConsole(ref), + "github.com/deckhouse/virtualization/api/subresources/v1alpha2.VirtualMachineFreeze": schema_virtualization_api_subresources_v1alpha2_VirtualMachineFreeze(ref), "github.com/deckhouse/virtualization/api/subresources/v1alpha2.VirtualMachinePortForward": schema_virtualization_api_subresources_v1alpha2_VirtualMachinePortForward(ref), "github.com/deckhouse/virtualization/api/subresources/v1alpha2.VirtualMachineRemoveVolume": schema_virtualization_api_subresources_v1alpha2_VirtualMachineRemoveVolume(ref), + "github.com/deckhouse/virtualization/api/subresources/v1alpha2.VirtualMachineUnfreeze": schema_virtualization_api_subresources_v1alpha2_VirtualMachineUnfreeze(ref), "github.com/deckhouse/virtualization/api/subresources/v1alpha2.VirtualMachineVNC": schema_virtualization_api_subresources_v1alpha2_VirtualMachineVNC(ref), "k8s.io/api/core/v1.AWSElasticBlockStoreVolumeSource": schema_k8sio_api_core_v1_AWSElasticBlockStoreVolumeSource(ref), "k8s.io/api/core/v1.Affinity": schema_k8sio_api_core_v1_Affinity(ref), @@ -1705,7 +1711,6 @@ func schema_virtualization_api_core_v1alpha2_SizingPolicyMemory(ref common.Refer }, }, }, - Required: []string{"step", "perCore"}, }, }, Dependencies: []string{ @@ -2063,6 +2068,184 @@ func schema_virtualization_api_core_v1alpha2_VirtualDiskPersistentVolumeClaim(re } } +func schema_virtualization_api_core_v1alpha2_VirtualDiskSnapshot(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "VirtualDiskSnapshot", + 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.VirtualDiskSnapshotSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualDiskSnapshotStatus"), + }, + }, + }, + Required: []string{"spec"}, + }, + }, + Dependencies: []string{ + "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualDiskSnapshotSpec", "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualDiskSnapshotStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_virtualization_api_core_v1alpha2_VirtualDiskSnapshotList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "VirtualDiskSnapshotList contains a list of VirtualDiskSnapshot", + 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.VirtualDiskSnapshot"), + }, + }, + }, + }, + }, + }, + Required: []string{"metadata", "items"}, + }, + }, + Dependencies: []string{ + "github.com/deckhouse/virtualization/api/core/v1alpha2.VirtualDiskSnapshot", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_virtualization_api_core_v1alpha2_VirtualDiskSnapshotSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "virtualDiskName": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "volumeSnapshotClassName": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "allowPotentiallyInconsistent": { + SchemaProps: spec.SchemaProps{ + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"virtualDiskName", "volumeSnapshotClassName", "allowPotentiallyInconsistent"}, + }, + }, + } +} + +func schema_virtualization_api_core_v1alpha2_VirtualDiskSnapshotStatus(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: "", + }, + }, + "volumeSnapshotName": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "conditions": { + SchemaProps: spec.SchemaProps{ + 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{ + Type: []string{"integer"}, + Format: "int64", + }, + }, + }, + Required: []string{"phase"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + } +} + func schema_virtualization_api_core_v1alpha2_VirtualDiskSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -4183,6 +4366,40 @@ func schema_virtualization_api_subresources_v1alpha2_VirtualMachineConsole(ref c } } +func schema_virtualization_api_subresources_v1alpha2_VirtualMachineFreeze(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + 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: "", + }, + }, + "unfreezeTimeout": { + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, + }, + Required: []string{"unfreezeTimeout"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, + } +} + func schema_virtualization_api_subresources_v1alpha2_VirtualMachinePortForward(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -4250,6 +4467,32 @@ func schema_virtualization_api_subresources_v1alpha2_VirtualMachineRemoveVolume( } } +func schema_virtualization_api_subresources_v1alpha2_VirtualMachineUnfreeze(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + 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: "", + }, + }, + }, + }, + }, + } +} + func schema_virtualization_api_subresources_v1alpha2_VirtualMachineVNC(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/api/subresources/register.go b/api/subresources/register.go index 4b81d7f3a..1f714aff0 100644 --- a/api/subresources/register.go +++ b/api/subresources/register.go @@ -56,6 +56,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { &VirtualMachinePortForward{}, &VirtualMachineAddVolume{}, &VirtualMachineRemoveVolume{}, + &VirtualMachineFreeze{}, + &VirtualMachineUnfreeze{}, &virtv2.VirtualMachine{}, &virtv2.VirtualMachineList{}, ) diff --git a/api/subresources/types.go b/api/subresources/types.go index 9b800f223..321c2e3d6 100644 --- a/api/subresources/types.go +++ b/api/subresources/types.go @@ -60,3 +60,21 @@ type VirtualMachineAddVolume struct { type VirtualMachineRemoveVolume struct { metav1.TypeMeta } + +// +genclient +// +genclient:readonly +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type VirtualMachineFreeze struct { + metav1.TypeMeta + + UnfreezeTimeout *metav1.Duration +} + +// +genclient +// +genclient:readonly +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type VirtualMachineUnfreeze struct { + metav1.TypeMeta +} diff --git a/api/subresources/v1alpha2/register.go b/api/subresources/v1alpha2/register.go index 8d5e381a7..87d8d39c6 100644 --- a/api/subresources/v1alpha2/register.go +++ b/api/subresources/v1alpha2/register.go @@ -56,6 +56,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { &VirtualMachinePortForward{}, &VirtualMachineAddVolume{}, &VirtualMachineRemoveVolume{}, + &VirtualMachineFreeze{}, + &VirtualMachineUnfreeze{}, &virtv2.VirtualMachine{}, &virtv2.VirtualMachineList{}, ) diff --git a/api/subresources/v1alpha2/types.go b/api/subresources/v1alpha2/types.go index ccefa45d8..9e1437bc0 100644 --- a/api/subresources/v1alpha2/types.go +++ b/api/subresources/v1alpha2/types.go @@ -65,3 +65,23 @@ type VirtualMachineAddVolume struct { type VirtualMachineRemoveVolume struct { metav1.TypeMeta `json:",inline"` } + +// +genclient +// +genclient:readonly +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:conversion-gen:explicit-from=net/url.Values + +type VirtualMachineFreeze struct { + metav1.TypeMeta `json:",inline"` + + UnfreezeTimeout *metav1.Duration `json:"unfreezeTimeout"` +} + +// +genclient +// +genclient:readonly +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:conversion-gen:explicit-from=net/url.Values + +type VirtualMachineUnfreeze struct { + metav1.TypeMeta `json:",inline"` +} diff --git a/api/subresources/v1alpha2/zz_generated.conversion.go b/api/subresources/v1alpha2/zz_generated.conversion.go index e62c6817f..575db04a4 100644 --- a/api/subresources/v1alpha2/zz_generated.conversion.go +++ b/api/subresources/v1alpha2/zz_generated.conversion.go @@ -22,8 +22,10 @@ package v1alpha2 import ( url "net/url" + unsafe "unsafe" subresources "github.com/deckhouse/virtualization/api/subresources" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -55,6 +57,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*VirtualMachineFreeze)(nil), (*subresources.VirtualMachineFreeze)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_VirtualMachineFreeze_To_subresources_VirtualMachineFreeze(a.(*VirtualMachineFreeze), b.(*subresources.VirtualMachineFreeze), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*subresources.VirtualMachineFreeze)(nil), (*VirtualMachineFreeze)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_subresources_VirtualMachineFreeze_To_v1alpha2_VirtualMachineFreeze(a.(*subresources.VirtualMachineFreeze), b.(*VirtualMachineFreeze), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*VirtualMachinePortForward)(nil), (*subresources.VirtualMachinePortForward)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha2_VirtualMachinePortForward_To_subresources_VirtualMachinePortForward(a.(*VirtualMachinePortForward), b.(*subresources.VirtualMachinePortForward), scope) }); err != nil { @@ -75,6 +87,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*VirtualMachineUnfreeze)(nil), (*subresources.VirtualMachineUnfreeze)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_VirtualMachineUnfreeze_To_subresources_VirtualMachineUnfreeze(a.(*VirtualMachineUnfreeze), b.(*subresources.VirtualMachineUnfreeze), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*subresources.VirtualMachineUnfreeze)(nil), (*VirtualMachineUnfreeze)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_subresources_VirtualMachineUnfreeze_To_v1alpha2_VirtualMachineUnfreeze(a.(*subresources.VirtualMachineUnfreeze), b.(*VirtualMachineUnfreeze), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*VirtualMachineVNC)(nil), (*subresources.VirtualMachineVNC)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha2_VirtualMachineVNC_To_subresources_VirtualMachineVNC(a.(*VirtualMachineVNC), b.(*subresources.VirtualMachineVNC), scope) }); err != nil { @@ -95,6 +117,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*url.Values)(nil), (*VirtualMachineFreeze)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_url_Values_To_v1alpha2_VirtualMachineFreeze(a.(*url.Values), b.(*VirtualMachineFreeze), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*url.Values)(nil), (*VirtualMachinePortForward)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_url_Values_To_v1alpha2_VirtualMachinePortForward(a.(*url.Values), b.(*VirtualMachinePortForward), scope) }); err != nil { @@ -105,6 +132,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*url.Values)(nil), (*VirtualMachineUnfreeze)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_url_Values_To_v1alpha2_VirtualMachineUnfreeze(a.(*url.Values), b.(*VirtualMachineUnfreeze), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*url.Values)(nil), (*VirtualMachineVNC)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_url_Values_To_v1alpha2_VirtualMachineVNC(a.(*url.Values), b.(*VirtualMachineVNC), scope) }); err != nil { @@ -171,6 +203,42 @@ func Convert_url_Values_To_v1alpha2_VirtualMachineConsole(in *url.Values, out *V return autoConvert_url_Values_To_v1alpha2_VirtualMachineConsole(in, out, s) } +func autoConvert_v1alpha2_VirtualMachineFreeze_To_subresources_VirtualMachineFreeze(in *VirtualMachineFreeze, out *subresources.VirtualMachineFreeze, s conversion.Scope) error { + out.UnfreezeTimeout = (*v1.Duration)(unsafe.Pointer(in.UnfreezeTimeout)) + return nil +} + +// Convert_v1alpha2_VirtualMachineFreeze_To_subresources_VirtualMachineFreeze is an autogenerated conversion function. +func Convert_v1alpha2_VirtualMachineFreeze_To_subresources_VirtualMachineFreeze(in *VirtualMachineFreeze, out *subresources.VirtualMachineFreeze, s conversion.Scope) error { + return autoConvert_v1alpha2_VirtualMachineFreeze_To_subresources_VirtualMachineFreeze(in, out, s) +} + +func autoConvert_subresources_VirtualMachineFreeze_To_v1alpha2_VirtualMachineFreeze(in *subresources.VirtualMachineFreeze, out *VirtualMachineFreeze, s conversion.Scope) error { + out.UnfreezeTimeout = (*v1.Duration)(unsafe.Pointer(in.UnfreezeTimeout)) + return nil +} + +// Convert_subresources_VirtualMachineFreeze_To_v1alpha2_VirtualMachineFreeze is an autogenerated conversion function. +func Convert_subresources_VirtualMachineFreeze_To_v1alpha2_VirtualMachineFreeze(in *subresources.VirtualMachineFreeze, out *VirtualMachineFreeze, s conversion.Scope) error { + return autoConvert_subresources_VirtualMachineFreeze_To_v1alpha2_VirtualMachineFreeze(in, out, s) +} + +func autoConvert_url_Values_To_v1alpha2_VirtualMachineFreeze(in *url.Values, out *VirtualMachineFreeze, s conversion.Scope) error { + // WARNING: Field TypeMeta does not have json tag, skipping. + + if values, ok := map[string][]string(*in)["unfreezeTimeout"]; ok && len(values) > 0 { + // FIXME: out.UnfreezeTimeout is of not yet supported type and requires manual conversion + } else { + out.UnfreezeTimeout = nil + } + return nil +} + +// Convert_url_Values_To_v1alpha2_VirtualMachineFreeze is an autogenerated conversion function. +func Convert_url_Values_To_v1alpha2_VirtualMachineFreeze(in *url.Values, out *VirtualMachineFreeze, s conversion.Scope) error { + return autoConvert_url_Values_To_v1alpha2_VirtualMachineFreeze(in, out, s) +} + func autoConvert_v1alpha2_VirtualMachinePortForward_To_subresources_VirtualMachinePortForward(in *VirtualMachinePortForward, out *subresources.VirtualMachinePortForward, s conversion.Scope) error { out.Protocol = in.Protocol out.Port = in.Port @@ -247,6 +315,35 @@ func Convert_url_Values_To_v1alpha2_VirtualMachineRemoveVolume(in *url.Values, o return autoConvert_url_Values_To_v1alpha2_VirtualMachineRemoveVolume(in, out, s) } +func autoConvert_v1alpha2_VirtualMachineUnfreeze_To_subresources_VirtualMachineUnfreeze(in *VirtualMachineUnfreeze, out *subresources.VirtualMachineUnfreeze, s conversion.Scope) error { + return nil +} + +// Convert_v1alpha2_VirtualMachineUnfreeze_To_subresources_VirtualMachineUnfreeze is an autogenerated conversion function. +func Convert_v1alpha2_VirtualMachineUnfreeze_To_subresources_VirtualMachineUnfreeze(in *VirtualMachineUnfreeze, out *subresources.VirtualMachineUnfreeze, s conversion.Scope) error { + return autoConvert_v1alpha2_VirtualMachineUnfreeze_To_subresources_VirtualMachineUnfreeze(in, out, s) +} + +func autoConvert_subresources_VirtualMachineUnfreeze_To_v1alpha2_VirtualMachineUnfreeze(in *subresources.VirtualMachineUnfreeze, out *VirtualMachineUnfreeze, s conversion.Scope) error { + return nil +} + +// Convert_subresources_VirtualMachineUnfreeze_To_v1alpha2_VirtualMachineUnfreeze is an autogenerated conversion function. +func Convert_subresources_VirtualMachineUnfreeze_To_v1alpha2_VirtualMachineUnfreeze(in *subresources.VirtualMachineUnfreeze, out *VirtualMachineUnfreeze, s conversion.Scope) error { + return autoConvert_subresources_VirtualMachineUnfreeze_To_v1alpha2_VirtualMachineUnfreeze(in, out, s) +} + +func autoConvert_url_Values_To_v1alpha2_VirtualMachineUnfreeze(in *url.Values, out *VirtualMachineUnfreeze, s conversion.Scope) error { + // WARNING: Field TypeMeta does not have json tag, skipping. + + return nil +} + +// Convert_url_Values_To_v1alpha2_VirtualMachineUnfreeze is an autogenerated conversion function. +func Convert_url_Values_To_v1alpha2_VirtualMachineUnfreeze(in *url.Values, out *VirtualMachineUnfreeze, s conversion.Scope) error { + return autoConvert_url_Values_To_v1alpha2_VirtualMachineUnfreeze(in, out, s) +} + func autoConvert_v1alpha2_VirtualMachineVNC_To_subresources_VirtualMachineVNC(in *VirtualMachineVNC, out *subresources.VirtualMachineVNC, s conversion.Scope) error { return nil } diff --git a/api/subresources/v1alpha2/zz_generated.deepcopy.go b/api/subresources/v1alpha2/zz_generated.deepcopy.go index 89ba8a7b6..4c52d8567 100644 --- a/api/subresources/v1alpha2/zz_generated.deepcopy.go +++ b/api/subresources/v1alpha2/zz_generated.deepcopy.go @@ -21,6 +21,7 @@ limitations under the License. package v1alpha2 import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -74,6 +75,36 @@ func (in *VirtualMachineConsole) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VirtualMachineFreeze) DeepCopyInto(out *VirtualMachineFreeze) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.UnfreezeTimeout != nil { + in, out := &in.UnfreezeTimeout, &out.UnfreezeTimeout + *out = new(v1.Duration) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualMachineFreeze. +func (in *VirtualMachineFreeze) DeepCopy() *VirtualMachineFreeze { + if in == nil { + return nil + } + out := new(VirtualMachineFreeze) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VirtualMachineFreeze) 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 *VirtualMachinePortForward) DeepCopyInto(out *VirtualMachinePortForward) { *out = *in @@ -124,6 +155,31 @@ func (in *VirtualMachineRemoveVolume) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VirtualMachineUnfreeze) DeepCopyInto(out *VirtualMachineUnfreeze) { + *out = *in + out.TypeMeta = in.TypeMeta + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualMachineUnfreeze. +func (in *VirtualMachineUnfreeze) DeepCopy() *VirtualMachineUnfreeze { + if in == nil { + return nil + } + out := new(VirtualMachineUnfreeze) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VirtualMachineUnfreeze) 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 *VirtualMachineVNC) DeepCopyInto(out *VirtualMachineVNC) { *out = *in diff --git a/api/subresources/zz_generated.deepcopy.go b/api/subresources/zz_generated.deepcopy.go index 02ab1e07f..1e9627216 100644 --- a/api/subresources/zz_generated.deepcopy.go +++ b/api/subresources/zz_generated.deepcopy.go @@ -21,6 +21,7 @@ limitations under the License. package subresources import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -74,6 +75,36 @@ func (in *VirtualMachineConsole) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VirtualMachineFreeze) DeepCopyInto(out *VirtualMachineFreeze) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.UnfreezeTimeout != nil { + in, out := &in.UnfreezeTimeout, &out.UnfreezeTimeout + *out = new(v1.Duration) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualMachineFreeze. +func (in *VirtualMachineFreeze) DeepCopy() *VirtualMachineFreeze { + if in == nil { + return nil + } + out := new(VirtualMachineFreeze) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VirtualMachineFreeze) 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 *VirtualMachinePortForward) DeepCopyInto(out *VirtualMachinePortForward) { *out = *in @@ -124,6 +155,31 @@ func (in *VirtualMachineRemoveVolume) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VirtualMachineUnfreeze) DeepCopyInto(out *VirtualMachineUnfreeze) { + *out = *in + out.TypeMeta = in.TypeMeta + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualMachineUnfreeze. +func (in *VirtualMachineUnfreeze) DeepCopy() *VirtualMachineUnfreeze { + if in == nil { + return nil + } + out := new(VirtualMachineUnfreeze) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VirtualMachineUnfreeze) 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 *VirtualMachineVNC) DeepCopyInto(out *VirtualMachineVNC) { *out = *in diff --git a/crds/doc-ru-virtualdisk.yaml b/crds/doc-ru-virtualdisk.yaml index a878efdfc..5a7e4faa2 100644 --- a/crds/doc-ru-virtualdisk.yaml +++ b/crds/doc-ru-virtualdisk.yaml @@ -69,14 +69,14 @@ spec: * xz. objectRef: description: | - Для создания образа использовать существующий `VirtualImage` или `ClusterVirtualImage`. + Для создания образа использовать существующий `VirtualImage`, `ClusterVirtualImage` или `VirtualDiskSnapshot`. properties: kind: description: | - Ссылка на существующий `VirtualImage` или `ClusterVirtualImage`. + Ссылка на существующий `VirtualImage`, `ClusterVirtualImage` или `VirtualDiskSnapshot`. name: description: | - Имя существующего `VirtualImage` или `ClusterVirtualImage`. + Имя существующего `VirtualImage`, `ClusterVirtualImage` или `VirtualDiskSnapshot`. type: description: | Тип источника, из которого будет создан диск: diff --git a/crds/doc-ru-virtualdisksnapshot.yaml b/crds/doc-ru-virtualdisksnapshot.yaml new file mode 100644 index 000000000..a878efdfc --- /dev/null +++ b/crds/doc-ru-virtualdisksnapshot.yaml @@ -0,0 +1,187 @@ +spec: + versions: + - name: v1alpha2 + schema: + openAPIV3Schema: + description: | + Описывает диск виртуальной машины, который может быть смонтирован в `VirtualMachines`. После создания ресурса изменить можно только размер диска `.spec.persistentVolumeClaim.size`, все остальные поля неизменяемы. + + Под капотом ресурса создается PVC, в который заливаются данные из источника. + properties: + spec: + properties: + bindingMode: + description: | + Типы режимов привязки диска: + + * `WaitForFirstConsumer` — отложить создание диска до тех пор, пока использующая этот диск виртуальная машина не будет назначена на узел. + * `Immediate` — начать создание диска, не дожидаясь создания виртуальной машины. + dataSource: + description: | + Тип источника, из которого будет создан диск. Если источник (.spec.dataSource) отсутствует, то будет создан пустой диск. + properties: + containerImage: + description: | + Для использования образа, хранящегося во внешнем реестре контейнеров, необходимо убедиться, что используется реестр, работающий по протоколу TLS. При необходимости предоставьте поле caBundle для передачи цепочки сертификатов. + properties: + caBundle: + description: | + Цепочка сертификатов в формате Base64 для проверки подключения к container registry. + image: + description: | + Путь к образу в container registry. + imagePullSecret: + properties: + name: + description: | + Имя Secret'а, содержащего учетные данные для подключения к container registry. + http: + description: | + Создать диск из файла, опубликованного по URL. Поддерживаемые схемы: + + * HTTP; + * HTTPS. + + Для схемы HTTPS есть возможность пропустить проверку TLS. + properties: + caBundle: + description: | + Цепочка сертификатов в формате Base64 для проверки TLS-сертификата сервера, на котором размещается образ. + checksum: + description: | + Контрольная сумма файла для проверки правильности загрузки или отсутствия изменений. Файл должен соответствовать всем указанным контрольным суммам. + properties: + md5: + description: "" + sha256: + description: "" + url: + description: | + URL с образом. Поддерживаются следующие типы образов: + * qcow2; + * vmdk; + * vdi; + * iso; + * raw. + + Для всех представленных типов образов поддерживается сжатие образов в следующих форматах: + * gz; + * xz. + objectRef: + description: | + Для создания образа использовать существующий `VirtualImage` или `ClusterVirtualImage`. + properties: + kind: + description: | + Ссылка на существующий `VirtualImage` или `ClusterVirtualImage`. + name: + description: | + Имя существующего `VirtualImage` или `ClusterVirtualImage`. + type: + description: | + Тип источника, из которого будет создан диск: + + * `HTTP` — создать диск из файла, опубликованного на HTTP/HTTPS-сервере. + * `ContainerImage` — создать диск из образа в container registry. + * `VirtualImage` — создать диск из существующего `VirtualImage`. + * `ClusterVirtualImage` — создать диск из существующего `ClusterVirtualImage`. + * `Upload` — загрузить образ диска вручную, через веб-интерфейс. + persistentVolumeClaim: + description: | + Настройки для создания PVC для хранения диска. + properties: + size: + description: | + Желаемый размер PVC для хранения диска. Если диск создается из образа, то размер должен быть не меньше, чем исходный образ в распакованном состоянии. + storageClassName: + description: | + Имя класса StorageClass, требуемого для PersistentVolumeClaim. Дополнительная информация — https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1. + status: + properties: + conditions: + description: | + Последнее подтвержденное состояние данного ресурса. + items: + properties: + lastProbeTime: + description: Время проверки условия. + lastTransitionTime: + description: Время перехода условия из одного состояния в другое. + message: + description: Удобочитаемое сообщение с подробной информацией о последнем переходе. + reason: + description: Краткая причина последнего перехода состояния. + status: + description: | + Статус условия. Возможные значения: `True`, `False`, `Unknown`. + type: + description: Тип условия. + attachedToVirtualMachines: + description: | + Список виртуальных машин, использующих этот диск. + stats: + description: | + Статистика по виртуальному диску. + properties: + creationDuration: + description: | + Время создания виртуального диска. + properties: + waitingForDependencies: + description: | + Длительность ожидания зависимостей для создания виртуального диска. + dvcrProvisioning: + description: | + Длительность загрузки в dvcr. + totalProvisioning: + description: | + Длительность создания ресурса с момента готовности зависимостей до перехода ресурса в Ready состояние (копирование/загрузка/создание диска). + capacity: + description: | + Емкость PVC в человекочитаемом формате. + downloadSpeed: + description: | + Скорость загрузки образа из внешнего источника. Появляется только на этапе `Provisioning`. + properties: + avg: + description: | + Средняя скорость загрузки. + avgBytes: + description: | + Средняя скорость загрузки в байтах в секунду. + current: + description: | + Текущая скорость загрузки. + currentBytes: + description: | + Текущая скорость загрузки в байтах в секунду. + phase: + description: | + Текущее состояние ресурса `VirtualDisk`: + + * Pending — ресурс был создан и находится в очереди ожидания. + * Provisioning — идет процесс создания ресурса (копирование/загрузка/создание диска). + * WaitForUserUpload — ожидание загрузки образа пользователем. Путь для загрузки образа указывается в `.status.uploadCommand`. + * WaitForFirstConsumer - ожидание пока использующая этот диск виртуальная машина не будет назначена на узел. + * Ready — ресурс создан и готов к использованию. + * Resizing — идет процесс увеличения размера диска. + * Failed — при создании ресурса возникла проблема. + * PVCLost — дочерний PVC ресурса отсутствует. Ресурс не может быть использован. + * Terminating - Ресурс находится в процессе удаления. + progress: + description: | + Ход копирования образа из источника в DVCR. Отображается только на этапе `Provisioning`. + target: + properties: + persistentVolumeClaimName: + description: | + Имя созданного PersistentVolumeClaim для хранилища Kubernetes. + uploadCommand: + description: | + Команда для загрузки образа для типа 'Upload'. + sourceUID: + description: | + UID источника (`VirtualImage` или `ClusterVirtualImage`), использовавшегося при создании виртуального диска. + observedGeneration: + description: | + Поколение ресурса, которое в последний раз обрабатывалось контроллером. diff --git a/crds/virtualdisk.yaml b/crds/virtualdisk.yaml index 30de0fac5..79b959a05 100644 --- a/crds/virtualdisk.yaml +++ b/crds/virtualdisk.yaml @@ -167,19 +167,20 @@ spec: objectRef: type: object description: | - Use an existing `VirtualImage` or `ClusterVirtualImage` to create an image. + Use an existing `VirtualImage`, `ClusterVirtualImage` or `VirtualDiskSnapshot` to create a disk. required: ["kind", "name"] properties: kind: type: string - description: A kind of existing `VirtualImage` or `ClusterVirtualImage`. + description: A kind of existing `VirtualImage`, `ClusterVirtualImage` or `VirtualDiskSnapshot`. enum: - - "ClusterVirtualImage" - "VirtualImage" + - "ClusterVirtualImage" + - "VirtualDiskSnapshot" name: type: string description: | - A name of existing `VirtualImage` or `ClusterVirtualImage`. + A name of existing `VirtualImage`, `ClusterVirtualImage` or `VirtualDiskSnapshot`. oneOf: - properties: type: diff --git a/crds/virtualdisksnapshot.yaml b/crds/virtualdisksnapshot.yaml new file mode 100644 index 000000000..66f3f7e5a --- /dev/null +++ b/crds/virtualdisksnapshot.yaml @@ -0,0 +1,112 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: virtualdisksnapshots.virtualization.deckhouse.io + labels: + heritage: deckhouse + module: virtualization +spec: + group: virtualization.deckhouse.io + scope: Namespaced + names: + categories: + - all + - virtualization + plural: virtualdisksnapshots + singular: virtualdisksnapshot + kind: VirtualDiskSnapshot + shortNames: + - vdsnapshots + - vdsnapshot + preserveUnknownFields: false + versions: + - name: v1alpha2 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + description: | + Describes a virtual disk that can be mounted in `VirtualMachines`. Once the resource is created, only the disk size `.spec.persistentVolumeClaim.size` can be changed, all other fields are immutable. + + A PVC is created under the hood of the resource, into which the data from the source is filled. + required: + - spec + properties: + spec: + type: object + required: + - virtualDiskName + - volumeSnapshotClassName + properties: + virtualDiskName: + type: string + description: | + The types of disk binding modes are. + volumeSnapshotClassName: + type: string + description: | + The types of disk binding modes are. + allowPotentiallyInconsistent: + type: boolean + description: | + The types of disk binding modes are. + status: + type: object + properties: + conditions: + description: | + The latest available observations of an object's current state. + type: array + items: + type: object + properties: + lastProbeTime: + description: Last time the condition was checked. + format: date-time + type: string + lastTransitionTime: + description: Last time the condition transit from one status to another. + format: date-time + type: string + message: + description: Human readable message indicating details about last transition. + type: string + reason: + description: (brief) reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + enum: ["True", "False", "Unknown"] + type: + description: Type of condition. + type: string + required: + - status + - type + volumeSnapshotName: + type: string + description: | + The name of the virtual machine to which this disk is attached. + phase: + type: string + description: | + Current status of `VirtualDiskSnapshot` resource: + * Pending - The resource has been created and is on a waiting queue. + * InProgress - The process of resource creation (copying/downloading/filling the PVC with data/extending PVC) is in progress. + * Ready - Waiting for the user to upload the image. The endpoint to upload the image is specified in `.status.uploadCommand`. + * Failed - Waiting for the virtual machine that uses the disk is scheduled. + * Terminating - The process of resource deletion is in progress. + enum: + ["Pending", "InProgress", "Ready", "Failed", "Terminating"] + observedGeneration: + type: integer + description: | + The generation last processed by the controller. + additionalPrinterColumns: + - name: Phase + type: string + jsonPath: .status.phase + subresources: + status: {} diff --git a/crds/virtualmachineclasses.yaml b/crds/virtualmachineclasses.yaml index 4debbb001..68638d596 100644 --- a/crds/virtualmachineclasses.yaml +++ b/crds/virtualmachineclasses.yaml @@ -9,455 +9,439 @@ spec: group: virtualization.deckhouse.io names: categories: - - virtualization + - virtualization kind: VirtualMachineClass listKind: VirtualMachineClassList plural: virtualmachineclasses shortNames: - - vmc - - vmcs - - vmclass - - vmclasses + - vmc + - vmcs + - vmclass + - vmclasses singular: virtualmachineclass scope: Cluster versions: - - additionalPrinterColumns: - - description: VirtualMachineClass phase. - jsonPath: .status.phase - name: Phase - type: string - - description: Time of creation resource. - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha2 - schema: - openAPIV3Schema: - description: |- - VirtualMachineClass resource describes a cpu requirements, node placement and sizing policy for VM resources. - A resource cannot be deleted as long as it is used in one of the VMs. - 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: - cpu: - description: CPU defines the requirements for the virtual CPU model. - properties: - discovery: - description: - Create CPU model based on an intersection CPU features - for selected nodes. - properties: - nodeSelector: - description: - A selection of nodes on the basis of which a - universal CPU model will be created. - properties: - matchExpressions: - description: - matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: |- - A label selector requirement is a selector that contains values, a key, and an operator that - relates the key and values. - properties: - key: - description: - key is the label key that the selector - applies to. - type: string - operator: - description: |- - operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: |- - values is an array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced during a strategic - merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels - map is equivalent to an element of matchExpressions, whose key field is "key", the - operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - type: object - features: - description: |- - A list of CPU instructions (features) required when type=Features. - More information about features [here](https://libvirt.org/formatdomain.html#cpu-model-and-topology) - example: - - mmx - - vmx - - sse2 - items: - type: string - minItems: 1 - type: array - model: - description: - The name of CPU model. More information about models - [here](https://libvirt.org/formatdomain.html#cpu-model-and-topology) - example: IvyBridge - minLength: 1 - type: string - type: - description: |- - CPUType defines cpu type, the following options are supported: - * `Host` - a virtual CPU is used that is as close as possible to the platform node's CPU in terms of instruction set. - This provides high performance and functionality, as well as compatibility with live migration for nodes with similar processor types. - For example, VM migration between nodes with Intel and AMD processors will not work. - This is also true for different generations of processors, as their instruction set is different. - * `HostPassthrough` - uses the physical CPU of the platform node directly without any modifications. - When using this class, the guest VM can only be transferred to a target node that has a CPU that exactly matches the CPU of the source node. - * `Discovery` - create a CPU model based on an intersecton CPU features for selected nodes. - * `Model` - CPU model name. A CPU model is a named and previously defined set of supported CPU instructions. - * `Features` - the required set of supported instructions for the CPU. - enum: - - Host - - HostPassthrough - - Discovery - - Model - - Features - type: string - required: - - type - type: object - x-kubernetes-validations: - - message: - HostPassthrough and Host cannot have model, features or - discovery - rule: - "self.type == 'HostPassthrough' || self.type == 'Host' - ? !has(self.model) && !has(self.features) && !has(self.discovery) - : true" - - message: Discovery cannot have model or features - rule: - "self.type == 'Discovery' ? !has(self.model) && !has(self.features) - : true" - - message: Model requires model and cannot have features or discovery - rule: - "self.type == 'Model' ? has(self.model) && !has(self.features) - && !has(self.discovery) : true" - - message: Features requires features and cannot have model or discovery - rule: - "self.type == 'Features' ? has(self.features) && !has(self.model) - && !has(self.discovery): true" - nodeSelector: - description: - NodeSelector defines selects the nodes that are targeted - to VM scheduling. - properties: - matchExpressions: - items: - description: |- - A node selector requirement is a selector that contains values, a key, and an operator - that relates the key and values. + - additionalPrinterColumns: + - description: VirtualMachineClass phase. + jsonPath: .status.phase + name: Phase + type: string + - description: Time of creation resource. + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: |- + VirtualMachineClass resource describes a cpu requirements, node placement and sizing policy for VM resources. + A resource cannot be deleted as long as it is used in one of the VMs. + 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: + cpu: + description: CPU defines the requirements for the virtual CPU model. + properties: + discovery: + description: Create CPU model based on an intersection CPU features + for selected nodes. + properties: + nodeSelector: + description: A selection of nodes on the basis of which a + universal CPU model will be created. properties: - key: - description: The label key that the selector applies to. - type: string - operator: - description: |- - Represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: |- - An array of string values. If the operator is In or NotIn, - the values array must be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. If the operator is Gt or Lt, the values - array must have a single element, which will be interpreted as an integer. - This array is replaced during a strategic merge patch. + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. items: - type: string + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: |- - A map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". - The requirements are ANDed. - type: object - type: object - sizingPolicies: - items: - description: |- - SizingPolicy define policy for allocating computational resources to VMs. - It is represented as a list. - The cores.min - cores.max ranges for different elements of the list must not overlap. - properties: - coreFractions: - description: Allowed values of the `coreFraction` parameter. - items: - enum: - - 5 - - 10 - - 20 - - 50 - - 100 - type: integer - type: array - cores: - description: - The policy applies for a specified range of the - number of CPU cores. - properties: - max: - description: Maximum cpu core count. - example: 10 - maximum: 1024 - type: integer - min: - description: Minimum cpu core count. - example: 1 - minimum: 1 - type: integer - step: - description: - Cpu cores count discretization step. I.e. min=2, - max=10, step=4 allows to set virtual machine cpu cores - to 2, 6, or 10. - example: 1 - minimum: 1 - type: integer - required: - - max - - min - type: object - x-kubernetes-validations: - - message: The maximum must be greater than the minimum - rule: self.max > self.min - - message: The maximum must be greater than the step - rule: "has(self.step) ? self.max > self.step : true" - dedicatedCores: - description: Allowed values of the `dedicatedCores` parameter. - items: - type: boolean - type: array - memory: - description: Memory sizing policy. - properties: - max: - anyOf: - - type: integer - - type: string - description: Maximum amount of memory. - example: 8Gi - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - min: - anyOf: - - type: integer - - type: string - description: Minimum amount of memory. - example: 1Gi - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - perCore: - description: Amount of memory per CPU core. - properties: - max: - anyOf: - - type: integer - - type: string - description: Maximum amount of memory. - example: 8Gi - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - min: - anyOf: - - type: integer - - type: string - description: Minimum amount of memory. - example: 1Gi - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. type: object - step: - anyOf: - - type: integer - - type: string - description: - Memory size discretization step. I.e. min=2Gi, - max=4Gi, step=1Gi allows to set virtual machine memory - size to 2Gi, 3Gi, or 4Gi. - example: 512Mi - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true type: object + x-kubernetes-map-type: atomic type: object - type: array - required: - - cpu - type: object - status: - properties: - availableNodes: - description: |- - A list of nodes that support this CPU model. - It is not displayed for the types: `Host`, `HostPassthrough` - example: - - node-1 - - node-2 - items: + features: + description: |- + A list of CPU instructions (features) required when type=Features. + More information about features [here](https://libvirt.org/formatdomain.html#cpu-model-and-topology) + example: + - mmx + - vmx + - sse2 + items: + type: string + minItems: 1 + type: array + model: + description: The name of CPU model. More information about models + [here](https://libvirt.org/formatdomain.html#cpu-model-and-topology) + example: IvyBridge + minLength: 1 type: string - type: array - conditions: - 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: + description: |- + CPUType defines cpu type, the following options are supported: + * `Host` - a virtual CPU is used that is as close as possible to the platform node's CPU in terms of instruction set. + This provides high performance and functionality, as well as compatibility with live migration for nodes with similar processor types. + For example, VM migration between nodes with Intel and AMD processors will not work. + This is also true for different generations of processors, as their instruction set is different. + * `HostPassthrough` - uses the physical CPU of the platform node directly without any modifications. + When using this class, the guest VM can only be transferred to a target node that has a CPU that exactly matches the CPU of the source node. + * `Discovery` - create a CPU model based on an intersecton CPU features for selected nodes. + * `Model` - CPU model name. A CPU model is a named and previously defined set of supported CPU instructions. + * `Features` - the required set of supported instructions for the CPU. + enum: + - Host + - HostPassthrough + - Discovery + - Model + - Features + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: HostPassthrough and Host cannot have model, features or + discovery + rule: 'self.type == ''HostPassthrough'' || self.type == ''Host'' + ? !has(self.model) && !has(self.features) && !has(self.discovery) + : true' + - message: Discovery cannot have model or features + rule: 'self.type == ''Discovery'' ? !has(self.model) && !has(self.features) + : true' + - message: Model requires model and cannot have features or discovery + rule: 'self.type == ''Model'' ? has(self.model) && !has(self.features) + && !has(self.discovery) : true' + - message: Features requires features and cannot have model or discovery + rule: 'self.type == ''Features'' ? has(self.features) && !has(self.model) + && !has(self.discovery): true' + nodeSelector: + description: NodeSelector defines selects the nodes that are targeted + to VM scheduling. + properties: + matchExpressions: + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: |- + A map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". + The requirements are ANDed. type: object - type: array - cpuFeatures: + type: object + sizingPolicies: + items: description: |- - CpuFeatures - Information on CPU features for this model. - Shown only for types `Features` or `Discovery`. + SizingPolicy define policy for allocating computational resources to VMs. + It is represented as a list. + The cores.min - cores.max ranges for different elements of the list must not overlap. properties: - enabled: - description: " A list of CPU features for this model." - example: - - mmx - - vmx - - sse2 + coreFractions: + description: Allowed values of the `coreFraction` parameter. items: - type: string + enum: + - 5 + - 10 + - 20 + - 50 + - 100 + type: integer type: array - notEnabledCommon: - description: - A list of unused processor features additionally - available for a given group of nodes. - example: - - ssse3 - - vme + cores: + description: The policy applies for a specified range of the + number of CPU cores. + properties: + max: + description: Maximum cpu core count. + example: 10 + maximum: 1024 + type: integer + min: + description: Minimum cpu core count. + example: 1 + minimum: 1 + type: integer + step: + description: Cpu cores count discretization step. I.e. min=2, + max=10, step=4 allows to set virtual machine cpu cores + to 2, 6, or 10. + example: 1 + minimum: 1 + type: integer + required: + - max + - min + type: object + x-kubernetes-validations: + - message: The maximum must be greater than the minimum + rule: self.max > self.min + - message: The maximum must be greater than the step + rule: 'has(self.step) ? self.max > self.step : true' + dedicatedCores: + description: Allowed values of the `dedicatedCores` parameter. items: - type: string + type: boolean type: array + memory: + description: Memory sizing policy. + properties: + max: + anyOf: + - type: integer + - type: string + description: Maximum amount of memory. + example: 8Gi + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + min: + anyOf: + - type: integer + - type: string + description: Minimum amount of memory. + example: 1Gi + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + perCore: + description: Amount of memory per CPU core. + properties: + max: + anyOf: + - type: integer + - type: string + description: Maximum amount of memory. + example: 8Gi + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + min: + anyOf: + - type: integer + - type: string + description: Minimum amount of memory. + example: 1Gi + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + step: + anyOf: + - type: integer + - type: string + description: Memory size discretization step. I.e. min=2Gi, + max=4Gi, step=1Gi allows to set virtual machine memory + size to 2Gi, 3Gi, or 4Gi. + example: 512Mi + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object type: object - observedGeneration: - description: The generation last processed by the controller - format: int64 - type: integer - phase: - description: |- - VirtualMachineClassPhase defines current status of resource: - * Pending - resource is not ready, waits until suitable nodes supporting the required CPU model become available. - * Ready - the resource is ready and available for use. - * Terminating - the resource is terminating. - enum: - - Pending - - Ready - - Terminating + type: array + required: + - cpu + type: object + status: + properties: + availableNodes: + description: |- + A list of nodes that support this CPU model. + It is not displayed for the types: `Host`, `HostPassthrough` + example: + - node-1 + - node-2 + items: type: string - required: - - phase - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} + type: array + conditions: + 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 + cpuFeatures: + description: |- + CpuFeatures + Information on CPU features for this model. + Shown only for types `Features` or `Discovery`. + properties: + enabled: + description: ' A list of CPU features for this model.' + example: + - mmx + - vmx + - sse2 + items: + type: string + type: array + notEnabledCommon: + description: A list of unused processor features additionally + available for a given group of nodes. + example: + - ssse3 + - vme + items: + type: string + type: array + type: object + observedGeneration: + description: The generation last processed by the controller + format: int64 + type: integer + phase: + description: |- + VirtualMachineClassPhase defines current status of resource: + * Pending - resource is not ready, waits until suitable nodes supporting the required CPU model become available. + * Ready - the resource is ready and available for use. + * Terminating - the resource is terminating. + enum: + - Pending + - Ready + - Terminating + type: string + required: + - phase + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/images/dvcr-artifact/build/importer.Dockerfile b/images/dvcr-artifact/build/importer.Dockerfile index 437ab0ef7..4c2525883 100644 --- a/images/dvcr-artifact/build/importer.Dockerfile +++ b/images/dvcr-artifact/build/importer.Dockerfile @@ -1,5 +1,5 @@ ARG BUILDER_CACHE_IMAGE=golang:1.21-bookworm -FROM $BUILDER_CACHE_IMAGE as builder +FROM $BUILDER_CACHE_IMAGE AS builder # Cache-friendly download modules. ADD go.mod go.sum /app/ diff --git a/images/virt-artifact/patches/017-fix-vmi-subresource-url.patch b/images/virt-artifact/patches/017-fix-vmi-subresource-url.patch index 8bed9c233..44f6c6a26 100644 --- a/images/virt-artifact/patches/017-fix-vmi-subresource-url.patch +++ b/images/virt-artifact/patches/017-fix-vmi-subresource-url.patch @@ -1,28 +1,5 @@ -diff --git a/pkg/virt-operator/resource/generate/rbac/controller.go b/pkg/virt-operator/resource/generate/rbac/controller.go -index 8b8313112..2b9061aef 100644 ---- a/pkg/virt-operator/resource/generate/rbac/controller.go -+++ b/pkg/virt-operator/resource/generate/rbac/controller.go -@@ -363,6 +363,18 @@ func newControllerClusterRole() *rbacv1.ClusterRole { - "*", - }, - }, -+ { -+ APIGroups: []string{ -+ "subresources.virtualization.deckhouse.io", -+ }, -+ Resources: []string{ -+ "virtualmachines/addvolume", -+ "virtualmachines/removevolume", -+ }, -+ Verbs: []string{ -+ "update", -+ }, -+ }, - { - APIGroups: []string{ - "subresources.kubevirt.io", diff --git a/staging/src/kubevirt.io/client-go/kubecli/vmi.go b/staging/src/kubevirt.io/client-go/kubecli/vmi.go -index a9e071350..59c17b0f8 100644 +index a9e071350..9be270018 100644 --- a/staging/src/kubevirt.io/client-go/kubecli/vmi.go +++ b/staging/src/kubevirt.io/client-go/kubecli/vmi.go @@ -47,7 +47,10 @@ import ( @@ -37,6 +14,24 @@ index a9e071350..59c17b0f8 100644 func (k *kubevirt) VirtualMachineInstance(namespace string) VirtualMachineInstanceInterface { return &vmis{ +@@ -242,7 +245,7 @@ func (v *vmis) SerialConsole(name string, options *SerialConsoleOptions) (Stream + + func (v *vmis) Freeze(ctx context.Context, name string, unfreezeTimeout time.Duration) error { + log.Log.Infof("Freeze VMI %s", name) +- uri := fmt.Sprintf(vmiSubresourceURL, v1.ApiStorageVersion, v.namespace, name, "freeze") ++ uri := fmt.Sprintf(vmiSubresourceVirtualizationURL, v.namespace, name, "freeze") + + freezeUnfreezeTimeout := &v1.FreezeUnfreezeTimeout{ + UnfreezeTimeout: &metav1.Duration{ +@@ -260,7 +263,7 @@ func (v *vmis) Freeze(ctx context.Context, name string, unfreezeTimeout time.Dur + + func (v *vmis) Unfreeze(ctx context.Context, name string) error { + log.Log.Infof("Unfreeze VMI %s", name) +- uri := fmt.Sprintf(vmiSubresourceURL, v1.ApiStorageVersion, v.namespace, name, "unfreeze") ++ uri := fmt.Sprintf(vmiSubresourceVirtualizationURL, v.namespace, name, "unfreeze") + return v.restClient.Put().AbsPath(uri).Do(ctx).Error() + } + @@ -470,7 +473,7 @@ func (v *vmis) Screenshot(ctx context.Context, name string, screenshotOptions *v } diff --git a/images/virtualization-artifact/cmd/virtualization-controller/main.go b/images/virtualization-artifact/cmd/virtualization-controller/main.go index 8d61df931..e8228f288 100644 --- a/images/virtualization-artifact/cmd/virtualization-controller/main.go +++ b/images/virtualization-artifact/cmd/virtualization-controller/main.go @@ -25,12 +25,15 @@ import ( "strconv" "strings" + 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/client-go/rest" 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" + crconfig "sigs.k8s.io/controller-runtime/pkg/config" "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/manager/signals" @@ -39,6 +42,7 @@ import ( "github.com/deckhouse/virtualization-controller/pkg/controller/cvi" "github.com/deckhouse/virtualization-controller/pkg/controller/indexer" "github.com/deckhouse/virtualization-controller/pkg/controller/vd" + "github.com/deckhouse/virtualization-controller/pkg/controller/vdsnapshot" "github.com/deckhouse/virtualization-controller/pkg/controller/vi" "github.com/deckhouse/virtualization-controller/pkg/controller/vm" "github.com/deckhouse/virtualization-controller/pkg/controller/vmbda" @@ -125,6 +129,7 @@ func main() { // Override content type to JSON so proxy can rewrite payloads. cfg.ContentType = apiruntime.ContentTypeJSON + cfg.NegotiatedSerializer = clientgoscheme.Codecs.WithoutConversion() leaderElectionNS := os.Getenv(common.PodNamespaceVar) if leaderElectionNS == "" { @@ -140,6 +145,7 @@ func main() { virtv2alpha1.AddToScheme, cdiv1beta1.AddToScheme, virtv1.AddToScheme, + vsv1.AddToScheme, } { err = f(scheme) if err != nil { @@ -155,6 +161,9 @@ func main() { LeaderElectionID: "d8-virt-operator-leader-election-helper", LeaderElectionResourceLock: "leases", Scheme: scheme, + Controller: crconfig.Controller{ + CacheSyncTimeout: 0, + }, } if pprofBindAddr != "" { managerOpts.PprofBindAddress = pprofBindAddr @@ -180,6 +189,12 @@ func main() { os.Exit(1) } + restClient, err := rest.UnversionedRESTClientFor(cfg) + if err != nil { + log.Error(err.Error()) + os.Exit(1) + } + log.Info("Registering Components.") // Setup context to gracefully handle termination. @@ -221,14 +236,22 @@ func main() { } if _, err = vmiplease.NewController(ctx, mgr, log, virtualMachineIPLeasesRetentionDuration); err != nil { + log.Error(err.Error()) os.Exit(1) } if _, err = vmclass.NewController(ctx, mgr, log); err != nil { + log.Error(err.Error()) os.Exit(1) } if _, err = vmop.NewController(ctx, mgr, log); err != nil { + log.Error(err.Error()) + os.Exit(1) + } + + if _, err = vdsnapshot.NewController(ctx, mgr, log, restClient); err != nil { + log.Error(err.Error()) os.Exit(1) } diff --git a/images/virtualization-artifact/go.mod b/images/virtualization-artifact/go.mod index 51c126549..7ba4b3d3f 100644 --- a/images/virtualization-artifact/go.mod +++ b/images/virtualization-artifact/go.mod @@ -10,6 +10,7 @@ require ( github.com/docker/cli v23.0.5+incompatible github.com/fsnotify/fsnotify v1.7.0 github.com/go-logr/logr v1.4.1 + github.com/kubernetes-csi/external-snapshotter/client/v6 v6.3.0 github.com/onsi/ginkgo/v2 v2.14.0 github.com/onsi/gomega v1.30.0 github.com/prometheus/client_golang v1.18.0 @@ -23,6 +24,7 @@ require ( k8s.io/client-go v0.29.2 k8s.io/code-generator v0.29.2 k8s.io/component-base v0.29.2 + k8s.io/component-helpers v0.29.2 k8s.io/klog/v2 v2.110.1 k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 k8s.io/utils v0.0.0-20230726121419-3b25d923346b @@ -126,7 +128,6 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.1 // indirect - k8s.io/component-helpers v0.29.2 // indirect k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 // indirect k8s.io/kms v0.29.2 // indirect kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 // indirect diff --git a/images/virtualization-artifact/go.sum b/images/virtualization-artifact/go.sum index 4c6a61898..286ad6049 100644 --- a/images/virtualization-artifact/go.sum +++ b/images/virtualization-artifact/go.sum @@ -192,6 +192,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kubernetes-csi/external-snapshotter/client/v6 v6.3.0 h1:qS4r4ljINLWKJ9m9Ge3Q3sGZ/eIoDVDT2RhAdQFHb1k= +github.com/kubernetes-csi/external-snapshotter/client/v6 v6.3.0/go.mod h1:oGXx2XTEzs9ikW2V6IC1dD8trgjRsS/Mvc2JRiC618Y= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= diff --git a/images/virtualization-artifact/pkg/apiserver/api/install.go b/images/virtualization-artifact/pkg/apiserver/api/install.go index 5cf813bb2..cc3cf0082 100644 --- a/images/virtualization-artifact/pkg/apiserver/api/install.go +++ b/images/virtualization-artifact/pkg/apiserver/api/install.go @@ -63,6 +63,8 @@ func Build(store *storage.VirtualMachineStorage) genericapiserver.APIGroupInfo { "virtualmachines/portforward": store.PortForwardREST(), "virtualmachines/addvolume": store.AddVolumeREST(), "virtualmachines/removevolume": store.RemoveVolumeREST(), + "virtualmachines/freeze": store.FreezeREST(), + "virtualmachines/unfreeze": store.UnfreezeREST(), } apiGroupInfo.VersionedResourcesStorageMap[v1alpha2.SchemeGroupVersion.Version] = resources return apiGroupInfo diff --git a/images/virtualization-artifact/pkg/apiserver/registry/vm/rest/freeze.go b/images/virtualization-artifact/pkg/apiserver/registry/vm/rest/freeze.go new file mode 100644 index 000000000..fa0f8991c --- /dev/null +++ b/images/virtualization-artifact/pkg/apiserver/registry/vm/rest/freeze.go @@ -0,0 +1,91 @@ +/* +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 rest + +import ( + "context" + "fmt" + "net/http" + "net/url" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/rest" + "k8s.io/client-go/tools/cache" + + "github.com/deckhouse/virtualization-controller/pkg/tls/certmanager" + "github.com/deckhouse/virtualization/api/subresources" +) + +type FreezeREST struct { + vmLister cache.GenericLister + proxyCertManager certmanager.CertificateManager + kubevirt KubevirtApiServerConfig +} + +var ( + _ rest.Storage = &FreezeREST{} + _ rest.Connecter = &FreezeREST{} +) + +func NewFreezeREST(vmLister cache.GenericLister, kubevirt KubevirtApiServerConfig, proxyCertManager certmanager.CertificateManager) *FreezeREST { + return &FreezeREST{ + vmLister: vmLister, + kubevirt: kubevirt, + proxyCertManager: proxyCertManager, + } +} + +func (r FreezeREST) New() runtime.Object { + return &subresources.VirtualMachineFreeze{} +} + +func (r FreezeREST) Destroy() { +} + +func (r FreezeREST) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) { + freezeOpts, ok := opts.(*subresources.VirtualMachineFreeze) + if !ok { + return nil, fmt.Errorf("invalid options object: %#v", opts) + } + location, transport, err := FreezeLocation(ctx, r.vmLister, name, freezeOpts, r.kubevirt, r.proxyCertManager) + if err != nil { + return nil, err + } + handler := newThrottledUpgradeAwareProxyHandler(location, transport, false, responder, r.kubevirt.ServiceAccount) + return handler, nil +} + +// NewConnectOptions implements rest.Connecter interface +func (r FreezeREST) NewConnectOptions() (runtime.Object, bool, string) { + return &subresources.VirtualMachineFreeze{}, false, "" +} + +// ConnectMethods implements rest.Connecter interface +func (r FreezeREST) ConnectMethods() []string { + return []string{http.MethodPut} +} + +func FreezeLocation( + ctx context.Context, + getter cache.GenericLister, + name string, + opts *subresources.VirtualMachineFreeze, + kubevirt KubevirtApiServerConfig, + proxyCertManager certmanager.CertificateManager, +) (*url.URL, *http.Transport, error) { + return streamLocation(ctx, getter, name, opts, "freeze", kubevirt, proxyCertManager) +} diff --git a/images/virtualization-artifact/pkg/apiserver/registry/vm/rest/stream.go b/images/virtualization-artifact/pkg/apiserver/registry/vm/rest/stream.go index 6b1308e2a..9e09f6ef5 100644 --- a/images/virtualization-artifact/pkg/apiserver/registry/vm/rest/stream.go +++ b/images/virtualization-artifact/pkg/apiserver/registry/vm/rest/stream.go @@ -120,6 +120,10 @@ func streamParams(_ url.Values, opts runtime.Object) error { return nil case *subresources.VirtualMachineRemoveVolume: return nil + case *subresources.VirtualMachineFreeze: + return nil + case *subresources.VirtualMachineUnfreeze: + return nil default: return fmt.Errorf("unknown object for streaming: %v", opts) } diff --git a/images/virtualization-artifact/pkg/apiserver/registry/vm/rest/unfreeze.go b/images/virtualization-artifact/pkg/apiserver/registry/vm/rest/unfreeze.go new file mode 100644 index 000000000..58ae0da9d --- /dev/null +++ b/images/virtualization-artifact/pkg/apiserver/registry/vm/rest/unfreeze.go @@ -0,0 +1,91 @@ +/* +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 rest + +import ( + "context" + "fmt" + "net/http" + "net/url" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/rest" + "k8s.io/client-go/tools/cache" + + "github.com/deckhouse/virtualization-controller/pkg/tls/certmanager" + "github.com/deckhouse/virtualization/api/subresources" +) + +type UnfreezeREST struct { + vmLister cache.GenericLister + proxyCertManager certmanager.CertificateManager + kubevirt KubevirtApiServerConfig +} + +var ( + _ rest.Storage = &UnfreezeREST{} + _ rest.Connecter = &UnfreezeREST{} +) + +func NewUnfreezeREST(vmLister cache.GenericLister, kubevirt KubevirtApiServerConfig, proxyCertManager certmanager.CertificateManager) *UnfreezeREST { + return &UnfreezeREST{ + vmLister: vmLister, + kubevirt: kubevirt, + proxyCertManager: proxyCertManager, + } +} + +func (r UnfreezeREST) New() runtime.Object { + return &subresources.VirtualMachineUnfreeze{} +} + +func (r UnfreezeREST) Destroy() { +} + +func (r UnfreezeREST) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) { + unfreezeOpts, ok := opts.(*subresources.VirtualMachineUnfreeze) + if !ok { + return nil, fmt.Errorf("invalid options object: %#v", opts) + } + location, transport, err := UnfreezeLocation(ctx, r.vmLister, name, unfreezeOpts, r.kubevirt, r.proxyCertManager) + if err != nil { + return nil, err + } + handler := newThrottledUpgradeAwareProxyHandler(location, transport, false, responder, r.kubevirt.ServiceAccount) + return handler, nil +} + +// NewConnectOptions implements rest.Connecter interface +func (r UnfreezeREST) NewConnectOptions() (runtime.Object, bool, string) { + return &subresources.VirtualMachineUnfreeze{}, false, "" +} + +// ConnectMethods implements rest.Connecter interface +func (r UnfreezeREST) ConnectMethods() []string { + return []string{http.MethodPut} +} + +func UnfreezeLocation( + ctx context.Context, + getter cache.GenericLister, + name string, + opts *subresources.VirtualMachineUnfreeze, + kubevirt KubevirtApiServerConfig, + proxyCertManager certmanager.CertificateManager, +) (*url.URL, *http.Transport, error) { + return streamLocation(ctx, getter, name, opts, "unfreeze", kubevirt, proxyCertManager) +} diff --git a/images/virtualization-artifact/pkg/apiserver/registry/vm/storage/storage.go b/images/virtualization-artifact/pkg/apiserver/registry/vm/storage/storage.go index 233031d4f..7f4329ab2 100644 --- a/images/virtualization-artifact/pkg/apiserver/registry/vm/storage/storage.go +++ b/images/virtualization-artifact/pkg/apiserver/registry/vm/storage/storage.go @@ -46,6 +46,8 @@ type VirtualMachineStorage struct { portforward *vmrest.PortForwardREST addVolume *vmrest.AddVolumeREST removeVolume *vmrest.RemoveVolumeREST + freeze *vmrest.FreezeREST + unfreeze *vmrest.UnfreezeREST convertor rest.TableConvertor } @@ -89,6 +91,8 @@ func NewStorage( portforward: vmrest.NewPortForwardREST(vmLister, kubevirt, proxyCertManager), addVolume: vmrest.NewAddVolumeREST(vmLister, kubevirt, proxyCertManager), removeVolume: vmrest.NewRemoveVolumeREST(vmLister, kubevirt, proxyCertManager), + freeze: vmrest.NewFreezeREST(vmLister, kubevirt, proxyCertManager), + unfreeze: vmrest.NewUnfreezeREST(vmLister, kubevirt, proxyCertManager), convertor: convertor, } } @@ -113,6 +117,14 @@ func (store VirtualMachineStorage) RemoveVolumeREST() *vmrest.RemoveVolumeREST { return store.removeVolume } +func (store VirtualMachineStorage) FreezeREST() *vmrest.FreezeREST { + return store.freeze +} + +func (store VirtualMachineStorage) UnfreezeREST() *vmrest.UnfreezeREST { + return store.unfreeze +} + // New implements rest.Storage interface func (store VirtualMachineStorage) New() runtime.Object { return &virtv2.VirtualMachine{} diff --git a/images/virtualization-artifact/pkg/controller/service/disk_service.go b/images/virtualization-artifact/pkg/controller/service/disk_service.go index 813395952..d5ec2d313 100644 --- a/images/virtualization-artifact/pkg/controller/service/disk_service.go +++ b/images/virtualization-artifact/pkg/controller/service/disk_service.go @@ -23,6 +23,7 @@ import ( "slices" "strings" + vsv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1" corev1 "k8s.io/api/core/v1" storev1 "k8s.io/api/storage/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" @@ -39,6 +40,7 @@ import ( "github.com/deckhouse/virtualization-controller/pkg/dvcr" "github.com/deckhouse/virtualization-controller/pkg/sdk/framework/helper" "github.com/deckhouse/virtualization-controller/pkg/util" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" ) type DiskService struct { @@ -92,6 +94,15 @@ func (s DiskService) Start( return supplements.EnsureForDataVolume(ctx, s.client, sup, dvBuilder.GetResource(), s.dvcrSettings) } +func (s DiskService) CreatePersistentVolumeClaim(ctx context.Context, pvc *corev1.PersistentVolumeClaim) error { + err := s.client.Create(ctx, pvc) + if err != nil && !k8serrors.IsAlreadyExists(err) { + return err + } + + return nil +} + func (s DiskService) CleanUp(ctx context.Context, sup *supplements.Generator) (bool, error) { subResourcesHaveDeleted, err := s.CleanUpSupplements(ctx, sup) if err != nil { @@ -267,6 +278,26 @@ func (s DiskService) GetPersistentVolume(ctx context.Context, pvc *corev1.Persis return helper.FetchObject(ctx, types.NamespacedName{Name: pvc.Spec.VolumeName}, s.client, &corev1.PersistentVolume{}) } +func (s DiskService) GetVolumeSnapshot(ctx context.Context, name, namespace string) (*vsv1.VolumeSnapshot, error) { + return helper.FetchObject(ctx, types.NamespacedName{Name: name, Namespace: namespace}, s.client, &vsv1.VolumeSnapshot{}) +} + +func (s DiskService) ListVirtualDiskSnapshots(ctx context.Context, namespace string) ([]virtv2.VirtualDiskSnapshot, error) { + var vdSnapshots virtv2.VirtualDiskSnapshotList + err := s.client.List(ctx, &vdSnapshots, &client.ListOptions{ + Namespace: namespace, + }) + if err != nil { + return nil, err + } + + return vdSnapshots.Items, nil +} + +func (s DiskService) GetVirtualDiskSnapshot(ctx context.Context, name, namespace string) (*virtv2.VirtualDiskSnapshot, error) { + return helper.FetchObject(ctx, types.NamespacedName{Name: name, Namespace: namespace}, s.client, &virtv2.VirtualDiskSnapshot{}) +} + func (s DiskService) CheckImportProcess(ctx context.Context, dv *cdiv1.DataVolume, pvc *corev1.PersistentVolumeClaim, storageClassName *string) error { var err error diff --git a/images/virtualization-artifact/pkg/controller/service/freezer_service.go b/images/virtualization-artifact/pkg/controller/service/freezer_service.go new file mode 100644 index 000000000..f3ab576ab --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/service/freezer_service.go @@ -0,0 +1,160 @@ +/* +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" + "encoding/json" + "fmt" + + vsv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/rest" + virtv1 "kubevirt.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/deckhouse/virtualization-controller/pkg/controller/kvbuilder" + "github.com/deckhouse/virtualization-controller/pkg/sdk/framework/helper" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +type FreezerService struct { + restClient *rest.RESTClient + client Client +} + +func NewFreezerService(restClient *rest.RESTClient, client Client) *FreezerService { + return &FreezerService{ + restClient: restClient, + client: client, + } +} + +const virtualizationSubresourcePath = "/apis/subresources.virtualization.deckhouse.io/v1alpha2/namespaces/%s/virtualmachines/%s/%s" + +func (s *FreezerService) Freeze(ctx context.Context, name, namespace string) error { + path := fmt.Sprintf(virtualizationSubresourcePath, namespace, name, "freeze") + + body, err := json.Marshal(&virtv1.FreezeUnfreezeTimeout{ + UnfreezeTimeout: &metav1.Duration{}, + }) + if err != nil { + return err + } + + err = s.restClient.Put().AbsPath(path).Body(body).Do(ctx).Error() + if err != nil { + return fmt.Errorf("failed to freeze internal virtual machine %s/%s: %w", namespace, name, err) + } + + return nil +} + +func (s *FreezerService) CanUnfreeze(ctx context.Context, vdSnapshotName string, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { + if kvvmi == nil || kvvmi.Status.FSFreezeStatus != "frozen" { + return false, nil + } + + vdByName := make(map[string]struct{}) + for _, volume := range kvvmi.Spec.Volumes { + if volume.PersistentVolumeClaim != nil { + vdName, ok := kvbuilder.GetOriginalDiskName(volume.Name) + if !ok { + continue + } + + vdByName[vdName] = struct{}{} + } + } + + var vdSnapshots virtv2.VirtualDiskSnapshotList + err := s.client.List(ctx, &vdSnapshots, &client.ListOptions{ + Namespace: kvvmi.Namespace, + }) + if err != nil { + return false, err + } + + for _, vdSnapshot := range vdSnapshots.Items { + if vdSnapshot.Name == vdSnapshotName { + continue + } + + _, ok := vdByName[vdSnapshot.Spec.VirtualDiskName] + if ok { + return false, nil + } + } + + return true, nil +} + +func (s *FreezerService) Unfreeze(ctx context.Context, name, namespace string) error { + path := fmt.Sprintf(virtualizationSubresourcePath, namespace, name, "unfreeze") + + err := s.restClient.Put().AbsPath(path).Do(ctx).Error() + if err != nil { + return fmt.Errorf("failed to unfreeze internal virtual machine %s/%s: %w", namespace, name, err) + } + + return nil +} + +func (s *FreezerService) TakeSnapshot(ctx context.Context, vds *virtv2.VirtualDiskSnapshot, vd *virtv2.VirtualDisk) (*vsv1.VolumeSnapshot, error) { + volumeSnapshot := vsv1.VolumeSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Name: vds.Name, + Namespace: vds.Namespace, + }, + Spec: vsv1.VolumeSnapshotSpec{ + Source: vsv1.VolumeSnapshotSource{ + PersistentVolumeClaimName: &vd.Status.Target.PersistentVolumeClaim, + }, + VolumeSnapshotClassName: &vds.Spec.VolumeSnapshotClassName, + }, + } + + err := s.client.Create(ctx, &volumeSnapshot) + if err != nil && !k8serrors.IsAlreadyExists(err) { + return nil, err + } + + return &volumeSnapshot, nil +} + +func (s *FreezerService) DeleteSnapshot(ctx context.Context, vs *vsv1.VolumeSnapshot) error { + err := s.client.Delete(ctx, vs) + if err != nil && !k8serrors.IsAlreadyExists(err) { + return err + } + + return nil +} + +func (s *FreezerService) GetVirtualDisk(ctx context.Context, name, namespace string) (*virtv2.VirtualDisk, error) { + return helper.FetchObject(ctx, types.NamespacedName{Namespace: namespace, Name: name}, s.client, &virtv2.VirtualDisk{}) +} + +func (s *FreezerService) GetVirtualMachineInstance(ctx context.Context, name, namespace string) (*virtv1.VirtualMachineInstance, error) { + return helper.FetchObject(ctx, types.NamespacedName{Namespace: namespace, Name: name}, s.client, &virtv1.VirtualMachineInstance{}) +} + +func (s *FreezerService) GetVolumeSnapshot(ctx context.Context, name, namespace string) (*vsv1.VolumeSnapshot, error) { + return helper.FetchObject(ctx, types.NamespacedName{Namespace: namespace, Name: name}, s.client, &vsv1.VolumeSnapshot{}) +} diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/datasource_ready.go b/images/virtualization-artifact/pkg/controller/vd/internal/datasource_ready.go index 7917829e4..795a02d68 100644 --- a/images/virtualization-artifact/pkg/controller/vd/internal/datasource_ready.go +++ b/images/virtualization-artifact/pkg/controller/vd/internal/datasource_ready.go @@ -92,6 +92,11 @@ func (h DatasourceReadyHandler) Handle(ctx context.Context, vd *virtv2.VirtualDi condition.Reason = vdcondition.ClusterImageNotReady condition.Message = service.CapitalizeFirstLetter(err.Error()) + "." return reconcile.Result{}, nil + case errors.As(err, &source.VirtualDiskSnapshotNotReadyError{}): + condition.Status = metav1.ConditionFalse + condition.Reason = vdcondition.VirtualDiskSnapshotNotReady + condition.Message = service.CapitalizeFirstLetter(err.Error()) + "." + return reconcile.Result{}, nil default: return reconcile.Result{}, fmt.Errorf("validation failed for data source %s: %w", ds.Name(), err) } diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/resizing.go b/images/virtualization-artifact/pkg/controller/vd/internal/resizing.go index e7b45e112..5db53b831 100644 --- a/images/virtualization-artifact/pkg/controller/vd/internal/resizing.go +++ b/images/virtualization-artifact/pkg/controller/vd/internal/resizing.go @@ -133,7 +133,7 @@ func (h ResizingHandler) Handle(ctx context.Context, vd *virtv2.VirtualDisk) (re condition.Message = "" default: condition.Status = metav1.ConditionFalse - condition.Reason = vdcondition.NotRequested + condition.Reason = vdcondition.ResizingNotRequested condition.Message = "" } diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/resizing_test.go b/images/virtualization-artifact/pkg/controller/vd/internal/resizing_test.go index 5e0d3f191..2d0e8758f 100644 --- a/images/virtualization-artifact/pkg/controller/vd/internal/resizing_test.go +++ b/images/virtualization-artifact/pkg/controller/vd/internal/resizing_test.go @@ -114,7 +114,7 @@ var _ = Describe("Resizing handler Run", func() { Expect(err).To(BeNil()) resized, _ := service.GetCondition(vdcondition.ResizedType, vd.Status.Conditions) Expect(resized.Status).To(Equal(metav1.ConditionFalse)) - Expect(resized.Reason).To(Equal(vdcondition.NotRequested)) + Expect(resized.Reason).To(Equal(vdcondition.ResizingNotRequested)) }) It("Resize is not requested (vd.spec.size < pvc.spec.size)", func() { @@ -126,7 +126,7 @@ var _ = Describe("Resizing handler Run", func() { Expect(err).To(BeNil()) resized, _ := service.GetCondition(vdcondition.ResizedType, vd.Status.Conditions) Expect(resized.Status).To(Equal(metav1.ConditionFalse)) - Expect(resized.Reason).To(Equal(vdcondition.NotRequested)) + Expect(resized.Reason).To(Equal(vdcondition.ResizingNotRequested)) }) It("Resize is not requested (vd.spec.size == pvc.spec.size)", func() { @@ -136,7 +136,7 @@ var _ = Describe("Resizing handler Run", func() { Expect(err).To(BeNil()) resized, _ := service.GetCondition(vdcondition.ResizedType, vd.Status.Conditions) Expect(resized.Status).To(Equal(metav1.ConditionFalse)) - Expect(resized.Reason).To(Equal(vdcondition.NotRequested)) + Expect(resized.Reason).To(Equal(vdcondition.ResizingNotRequested)) }) It("Resize has started (vd.spec.size > pvc.spec.size)", func() { diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/snapshotting.go b/images/virtualization-artifact/pkg/controller/vd/internal/snapshotting.go new file mode 100644 index 000000000..60fe0056d --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vd/internal/snapshotting.go @@ -0,0 +1,86 @@ +/* +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" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/deckhouse/virtualization-controller/pkg/controller/service" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vdcondition" +) + +type SnapshottingHandler struct { + diskService *service.DiskService +} + +func NewSnapshottingHandler(diskService *service.DiskService) *SnapshottingHandler { + return &SnapshottingHandler{ + diskService: diskService, + } +} + +func (h SnapshottingHandler) Handle(ctx context.Context, vd *virtv2.VirtualDisk) (reconcile.Result, error) { + condition, ok := service.GetCondition(vdcondition.SnapshottingType, vd.Status.Conditions) + if !ok { + condition = metav1.Condition{ + Type: vdcondition.SnapshottingType, + Status: metav1.ConditionUnknown, + } + } + + defer func() { service.SetCondition(condition, &vd.Status.Conditions) }() + + if vd.DeletionTimestamp != nil { + condition.Status = metav1.ConditionUnknown + condition.Reason = "" + condition.Message = "" + return reconcile.Result{}, nil + } + + readyCondition, ok := service.GetCondition(vdcondition.ReadyType, vd.Status.Conditions) + if !ok || readyCondition.Status != metav1.ConditionTrue { + condition.Status = metav1.ConditionUnknown + condition.Reason = "" + condition.Message = "" + return reconcile.Result{}, nil + } + + vdSnapshots, err := h.diskService.ListVirtualDiskSnapshots(ctx, vd.Namespace) + if err != nil { + return reconcile.Result{}, err + } + + for _, vdSnapshot := range vdSnapshots { + if vdSnapshot.Spec.VirtualDiskName != vd.Name { + continue + } + + condition.Status = metav1.ConditionTrue + condition.Reason = vdcondition.Snapshotting + condition.Message = "The virtual disk is selected for taking a snapshot." + return reconcile.Result{}, nil + } + + condition.Status = metav1.ConditionFalse + condition.Reason = vdcondition.SnapshottingNotRequested + condition.Message = "" + return reconcile.Result{}, nil +} diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/source/errors.go b/images/virtualization-artifact/pkg/controller/vd/internal/source/errors.go index 60c631bd0..efa742748 100644 --- a/images/virtualization-artifact/pkg/controller/vd/internal/source/errors.go +++ b/images/virtualization-artifact/pkg/controller/vd/internal/source/errors.go @@ -50,3 +50,17 @@ func NewClusterImageNotReadyError(name string) error { name: name, } } + +type VirtualDiskSnapshotNotReadyError struct { + name string +} + +func (e VirtualDiskSnapshotNotReadyError) Error() string { + return fmt.Sprintf("VirtualDiskSnapshot %s not ready", e.name) +} + +func NewVirtualDiskSnapshotNotReadyError(name string) error { + return VirtualDiskSnapshotNotReadyError{ + name: name, + } +} diff --git a/images/virtualization-artifact/pkg/controller/vd/internal/source/object_ref.go b/images/virtualization-artifact/pkg/controller/vd/internal/source/object_ref.go index 23b693f8f..86bb34f09 100644 --- a/images/virtualization-artifact/pkg/controller/vd/internal/source/object_ref.go +++ b/images/virtualization-artifact/pkg/controller/vd/internal/source/object_ref.go @@ -44,6 +44,8 @@ type ObjectRefDataSource struct { statService *service.StatService diskService *service.DiskService client client.Client + + vdSnapshotSyncer *ObjectRefVirtualDiskSnapshot } func NewObjectRefDataSource( @@ -52,9 +54,10 @@ func NewObjectRefDataSource( client client.Client, ) *ObjectRefDataSource { return &ObjectRefDataSource{ - statService: statService, - diskService: diskService, - client: client, + statService: statService, + diskService: diskService, + client: client, + vdSnapshotSyncer: NewObjectRefVirtualDiskSnapshot(diskService), } } @@ -64,6 +67,15 @@ func (ds ObjectRefDataSource) Sync(ctx context.Context, vd *virtv2.VirtualDisk) condition, _ := service.GetCondition(vdcondition.ReadyType, vd.Status.Conditions) defer func() { service.SetCondition(condition, &vd.Status.Conditions) }() + switch vd.Spec.DataSource.ObjectRef.Kind { + case virtv2.VirtualDiskObjectRefKindVirtualImage: + case virtv2.VirtualDiskObjectRefKindClusterVirtualImage: + case virtv2.VirtualDiskObjectRefKindVirtualDiskSnapshot: + return ds.vdSnapshotSyncer.Sync(ctx, vd, &condition) + default: + // TODO + } + supgen := supplements.NewGenerator(common.VDShortName, vd.Name, vd.Namespace, vd.UID) dv, err := ds.diskService.GetDataVolume(ctx, supgen) if err != nil { @@ -214,6 +226,15 @@ func (ds ObjectRefDataSource) Validate(ctx context.Context, vd *virtv2.VirtualDi return errors.New("object ref missed for data source") } + switch vd.Spec.DataSource.ObjectRef.Kind { + case virtv2.VirtualDiskObjectRefKindVirtualDiskSnapshot: + return ds.vdSnapshotSyncer.Validate(ctx, vd) + case virtv2.VirtualDiskObjectRefKindVirtualImage: + case virtv2.VirtualDiskObjectRefKindClusterVirtualImage: + default: + // TODO + } + dvcrDataSource, err := controller.NewDVCRDataSourcesForVMD(ctx, vd.Spec.DataSource, vd, ds.client) if err != nil { return err 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 new file mode 100644 index 000000000..e230e6050 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vd/internal/source/object_ref_vdsnapshot.go @@ -0,0 +1,150 @@ +/* +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 source + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + + "github.com/deckhouse/virtualization-controller/pkg/controller/common" + "github.com/deckhouse/virtualization-controller/pkg/controller/service" + "github.com/deckhouse/virtualization-controller/pkg/controller/supplements" + "github.com/deckhouse/virtualization-controller/pkg/logger" + "github.com/deckhouse/virtualization-controller/pkg/util" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vdcondition" +) + +type ObjectRefVirtualDiskSnapshot struct { + diskService *service.DiskService +} + +func NewObjectRefVirtualDiskSnapshot(diskService *service.DiskService) *ObjectRefVirtualDiskSnapshot { + return &ObjectRefVirtualDiskSnapshot{ + diskService: diskService, + } +} + +func (ds ObjectRefVirtualDiskSnapshot) Sync(ctx context.Context, vd *virtv2.VirtualDisk, condition *metav1.Condition) (bool, error) { + log, ctx := logger.GetDataSourceContext(ctx, objectRefDataSource) + + supgen := supplements.NewGenerator(common.VDShortName, vd.Name, vd.Namespace, vd.UID) + + vs, err := ds.diskService.GetVolumeSnapshot(ctx, vd.Spec.DataSource.ObjectRef.Name, vd.Namespace) + if err != nil { + return false, err + } + pvc, err := ds.diskService.GetPersistentVolumeClaim(ctx, supgen) + if err != nil { + return false, err + } + pv, err := ds.diskService.GetPersistentVolume(ctx, pvc) + if err != nil { + return false, err + } + + switch { + case isDiskProvisioningFinished(*condition): + log.Info("Disk provisioning finished: clean up") + + setPhaseConditionForFinishedDisk(pv, pvc, condition, &vd.Status.Phase, supgen) + + // Protect Ready Disk and underlying PVC and PV. + err = ds.diskService.Protect(ctx, vd, nil, pvc, pv) + if err != nil { + return false, err + } + + return false, nil + case common.AnyTerminating(pvc, pv): + log.Info("Waiting for supplements to be terminated") + return true, nil + case pvc == nil: + log.Info("Start import to PVC") + + pvc = &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: vd.Name, + Namespace: vd.Namespace, + }, + Spec: corev1.PersistentVolumeClaimSpec{ + DataSource: &corev1.TypedLocalObjectReference{ + APIGroup: ptr.To(vs.GroupVersionKind().GroupVersion().String()), + Kind: vs.Kind, + Name: vd.Spec.DataSource.ObjectRef.Name, + }, + }, + } + + err = ds.diskService.CreatePersistentVolumeClaim(ctx, pvc) + if err != nil { + setPhaseConditionToFailed(condition, &vd.Status.Phase, err) + return false, err + } + + vd.Status.Phase = virtv2.DiskProvisioning + condition.Status = metav1.ConditionFalse + condition.Reason = vdcondition.Provisioning + condition.Message = "PVC has created: waiting to be Bound." + + vd.Status.Progress = "0%" + vd.Status.SourceUID = util.GetPointer(vs.UID) + vd.Status.Capacity = ds.diskService.GetCapacity(pvc) + vd.Status.Target.PersistentVolumeClaim = pvc.Name + + return false, nil + case pvc.Status.Phase == corev1.ClaimBound: + vd.Status.Phase = virtv2.DiskReady + condition.Status = metav1.ConditionTrue + condition.Reason = vdcondition.Ready + condition.Message = "" + + vd.Status.Progress = "100%" + vd.Status.Capacity = ds.diskService.GetCapacity(pvc) + vd.Status.Target.PersistentVolumeClaim = pvc.Name + + return true, nil + default: + vd.Status.Phase = virtv2.DiskProvisioning + condition.Status = metav1.ConditionFalse + condition.Reason = vdcondition.Provisioning + condition.Message = fmt.Sprintf("Waiting for the PVC %s to be Bound.", pvc.Name) + + return false, nil + } +} + +func (ds ObjectRefVirtualDiskSnapshot) Validate(ctx context.Context, vd *virtv2.VirtualDisk) error { + if vd.Spec.DataSource == nil || vd.Spec.DataSource.ObjectRef == nil || vd.Spec.DataSource.ObjectRef.Kind != virtv2.VirtualDiskObjectRefKindVirtualDiskSnapshot { + return fmt.Errorf("not a %s data source", virtv2.VirtualDiskObjectRefKindVirtualDiskSnapshot) + } + + vdSnapshot, err := ds.diskService.GetVirtualDiskSnapshot(ctx, vd.Spec.DataSource.ObjectRef.Name, vd.Namespace) + if err != nil { + return err + } + + if vdSnapshot == nil || vdSnapshot.Status.Phase != virtv2.VirtualDiskSnapshotPhaseReady { + return NewVirtualDiskSnapshotNotReadyError(vd.Spec.DataSource.ObjectRef.Name) + } + + return nil +} diff --git a/images/virtualization-artifact/pkg/controller/vd/vd_controller.go b/images/virtualization-artifact/pkg/controller/vd/vd_controller.go index 450755bb1..6f0d34d4b 100644 --- a/images/virtualization-artifact/pkg/controller/vd/vd_controller.go +++ b/images/virtualization-artifact/pkg/controller/vd/vd_controller.go @@ -78,6 +78,7 @@ func NewController( internal.NewDatasourceReadyHandler(blank, sources), internal.NewLifeCycleHandler(blank, sources, mgr.GetClient()), internal.NewResizingHandler(disk), + internal.NewSnapshottingHandler(disk), internal.NewDeletionHandler(sources), internal.NewAttacheeHandler(mgr.GetClient()), internal.NewStatsHandler(stat, importer, uploader), diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/deletion.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/deletion.go new file mode 100644 index 000000000..c0faa0c3b --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/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, vdSnapshot *virtv2.VirtualDiskSnapshot) (reconcile.Result, error) { + if vdSnapshot.DeletionTimestamp != nil { + controllerutil.RemoveFinalizer(vdSnapshot, virtv2.FinalizerVDSnapshotCleanup) + return reconcile.Result{}, nil + } + + controllerutil.AddFinalizer(vdSnapshot, virtv2.FinalizerVDSnapshotCleanup) + return reconcile.Result{}, nil +} diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go new file mode 100644 index 000000000..aa708d0ba --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go @@ -0,0 +1,208 @@ +/* +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" + virtv1 "kubevirt.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/deckhouse/virtualization-controller/pkg/controller/service" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vdscondition" +) + +type LifeCycleHandler struct { + freezer *service.FreezerService +} + +func NewLifeCycleHandler(freezer *service.FreezerService) *LifeCycleHandler { + return &LifeCycleHandler{ + freezer: freezer, + } +} + +func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *virtv2.VirtualDiskSnapshot) (reconcile.Result, error) { + condition, ok := service.GetCondition(vdscondition.VirtualDiskSnapshotReadyType, vdSnapshot.Status.Conditions) + if !ok { + condition = metav1.Condition{ + Type: vdscondition.VirtualDiskSnapshotReadyType, + Status: metav1.ConditionUnknown, + } + } + + defer func() { service.SetCondition(condition, &vdSnapshot.Status.Conditions) }() + + vs, err := h.freezer.GetVolumeSnapshot(ctx, vdSnapshot.Name, vdSnapshot.Namespace) + if err != nil { + setPhaseConditionToFailed(&condition, &vdSnapshot.Status.Phase, err) + return reconcile.Result{}, err + } + + vd, err := h.freezer.GetVirtualDisk(ctx, vdSnapshot.Spec.VirtualDiskName, vdSnapshot.Namespace) + if err != nil { + setPhaseConditionToFailed(&condition, &vdSnapshot.Status.Phase, err) + return reconcile.Result{}, err + } + + kvvmi, err := h.getInternalVirtualMachine(ctx, vd) + if err != nil { + setPhaseConditionToFailed(&condition, &vdSnapshot.Status.Phase, err) + return reconcile.Result{}, err + } + + if vdSnapshot.DeletionTimestamp != nil { + vdSnapshot.Status.Phase = virtv2.VirtualDiskSnapshotPhaseTerminating + + if vs == nil { + err = h.freezer.DeleteSnapshot(ctx, vs) + if err != nil { + setPhaseConditionToFailed(&condition, &vdSnapshot.Status.Phase, err) + return reconcile.Result{}, err + } + } + + var canUnfreeze bool + canUnfreeze, err = h.freezer.CanUnfreeze(ctx, vdSnapshot.Name, kvvmi) + if err != nil { + setPhaseConditionToFailed(&condition, &vdSnapshot.Status.Phase, err) + return reconcile.Result{}, err + } + + if canUnfreeze { + err = h.freezer.Unfreeze(ctx, kvvmi.Name, kvvmi.Namespace) + if err != nil { + setPhaseConditionToFailed(&condition, &vdSnapshot.Status.Phase, err) + return reconcile.Result{}, err + } + } + + return reconcile.Result{}, nil + } + + if vdSnapshot.Status.Phase == "" { + vdSnapshot.Status.Phase = virtv2.VirtualDiskSnapshotPhasePending + } + + virtualDiskReadyCondition, _ := service.GetCondition(vdscondition.VirtualDiskReadyType, vdSnapshot.Status.Conditions) + if vd == nil || virtualDiskReadyCondition.Status != metav1.ConditionTrue { + vdSnapshot.Status.Phase = virtv2.VirtualDiskSnapshotPhasePending + condition.Status = metav1.ConditionFalse + condition.Reason = vdscondition.WaitingForTheVirtualDisk + condition.Message = fmt.Sprintf("Waiting for the virtual disk %s to be in phase Ready.", vdSnapshot.Spec.VirtualDiskName) + return reconcile.Result{}, nil + } + + if kvvmi != nil && kvvmi.Status.FSFreezeStatus != "frozen" { + err = h.freezer.Freeze(ctx, kvvmi.Name, kvvmi.Namespace) + if err != nil { + setPhaseConditionToFailed(&condition, &vdSnapshot.Status.Phase, err) + return reconcile.Result{}, err + } + + vdSnapshot.Status.Phase = virtv2.VirtualDiskSnapshotPhaseInProgress + condition.Status = metav1.ConditionFalse + condition.Reason = vdscondition.FileSystemFreezing + condition.Message = fmt.Sprintf( + "The virtual machine %s with an attached virtual disk %s is in the process of being frozen for taking a snapshot.", + kvvmi.Name, vdSnapshot.Spec.VirtualDiskName, + ) + return reconcile.Result{}, nil + } + + switch { + case vs == nil: + vs, err = h.freezer.TakeSnapshot(ctx, vdSnapshot, vd) + if err != nil { + setPhaseConditionToFailed(&condition, &vdSnapshot.Status.Phase, err) + return reconcile.Result{}, err + } + + vdSnapshot.Status.Phase = virtv2.VirtualDiskSnapshotPhaseInProgress + vdSnapshot.Status.VolumeSnapshotName = vs.Name + condition.Status = metav1.ConditionFalse + condition.Reason = vdscondition.Snapshotting + condition.Message = fmt.Sprintf("The snapshotting process for virtual disk %s has started.", vdSnapshot.Spec.VirtualDiskName) + return reconcile.Result{}, nil + case vs.Status.ReadyToUse == nil || !*vs.Status.ReadyToUse: + if vs.Status.Error != nil && vs.Status.Error.Message != nil { + vdSnapshot.Status.Phase = virtv2.VirtualDiskSnapshotPhaseFailed + condition.Status = metav1.ConditionFalse + condition.Reason = vdscondition.VirtualDiskSnapshotFailed + condition.Message = fmt.Sprintf("VolumeSnapshot %s has an error: %s.", vs.Name, *vs.Status.Error.Message) + return reconcile.Result{}, nil + } + + vdSnapshot.Status.Phase = virtv2.VirtualDiskSnapshotPhaseInProgress + condition.Status = metav1.ConditionFalse + condition.Reason = vdscondition.Snapshotting + condition.Message = fmt.Sprintf("Waiting fot the volume snapshot %s to be ready to use.", vdSnapshot.Name) + return reconcile.Result{}, nil + default: + var canUnfreeze bool + canUnfreeze, err = h.freezer.CanUnfreeze(ctx, vdSnapshot.Name, kvvmi) + if err != nil { + setPhaseConditionToFailed(&condition, &vdSnapshot.Status.Phase, err) + return reconcile.Result{}, err + } + + if canUnfreeze { + err = h.freezer.Unfreeze(ctx, kvvmi.Name, kvvmi.Namespace) + if err != nil { + setPhaseConditionToFailed(&condition, &vdSnapshot.Status.Phase, err) + return reconcile.Result{}, err + } + } + + vdSnapshot.Status.Phase = virtv2.VirtualDiskSnapshotPhaseReady + condition.Status = metav1.ConditionTrue + condition.Reason = vdscondition.VirtualDiskSnapshotReady + condition.Message = "" + + return reconcile.Result{}, nil + } +} + +func (h LifeCycleHandler) getInternalVirtualMachine(ctx context.Context, vd *virtv2.VirtualDisk) (*virtv1.VirtualMachineInstance, error) { + if vd == nil { + return nil, nil + } + + switch len(vd.Status.AttachedToVirtualMachines) { + case 0: + return nil, nil + case 1: + kvvmi, err := h.freezer.GetVirtualMachineInstance(ctx, vd.Status.AttachedToVirtualMachines[0].Name, vd.Namespace) + if err != nil { + return nil, err + } + + return kvvmi, nil + default: + return nil, fmt.Errorf("the virtual disk %s is attached to multiple virtual machines", vd.Name) + } +} + +func setPhaseConditionToFailed(cond *metav1.Condition, phase *virtv2.VirtualDiskSnapshotPhase, err error) { + *phase = virtv2.VirtualDiskSnapshotPhaseFailed + cond.Status = metav1.ConditionFalse + cond.Reason = vdscondition.VirtualDiskSnapshotFailed + cond.Message = service.CapitalizeFirstLetter(err.Error()) +} diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/virtual_disk_ready.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/virtual_disk_ready.go new file mode 100644 index 000000000..66a50152d --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/virtual_disk_ready.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 internal + +import ( + "context" + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/deckhouse/virtualization-controller/pkg/controller/service" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vdscondition" +) + +type VirtualDiskReadyHandler struct { + freezer *service.FreezerService +} + +func NewVirtualDiskReadyHandler(freezer *service.FreezerService) *VirtualDiskReadyHandler { + return &VirtualDiskReadyHandler{ + freezer: freezer, + } +} + +func (h VirtualDiskReadyHandler) Handle(ctx context.Context, vds *virtv2.VirtualDiskSnapshot) (reconcile.Result, error) { + condition, ok := service.GetCondition(vdscondition.VirtualDiskReadyType, vds.Status.Conditions) + if !ok { + condition = metav1.Condition{ + Type: vdscondition.VirtualDiskReadyType, + Status: metav1.ConditionUnknown, + } + } + + defer func() { service.SetCondition(condition, &vds.Status.Conditions) }() + + if vds.DeletionTimestamp != nil { + condition.Status = metav1.ConditionUnknown + condition.Reason = "" + condition.Message = "" + return reconcile.Result{}, nil + } + + vd, err := h.freezer.GetVirtualDisk(ctx, vds.Spec.VirtualDiskName, vds.Namespace) + if err != nil { + return reconcile.Result{}, err + } + + if vd == nil { + condition.Status = metav1.ConditionFalse + condition.Reason = vdscondition.VirtualDiskNotReady + condition.Message = fmt.Sprintf("The virtual disk %s not found.", vds.Spec.VirtualDiskName) + return reconcile.Result{}, nil + } + + switch vd.Status.Phase { + case virtv2.DiskReady: + condition.Status = metav1.ConditionTrue + condition.Reason = vdscondition.VirtualDiskReady + condition.Message = "" + return reconcile.Result{}, nil + default: + condition.Status = metav1.ConditionFalse + condition.Reason = vdscondition.VirtualDiskNotReady + condition.Message = fmt.Sprintf("The virtual disk is in the %s phase: waiting for it to reach the Ready phase.", vd.Status.Phase) + return reconcile.Result{}, nil + } +} diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/kvvmi_watcher.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/kvvmi_watcher.go new file mode 100644 index 000000000..00d4361d3 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/kvvmi_watcher.go @@ -0,0 +1,127 @@ +/* +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" + virtv1 "kubevirt.io/api/core/v1" + "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" + + "github.com/deckhouse/virtualization-controller/pkg/controller/kvbuilder" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +type VirtualMachineInstanceWatcher struct { + client client.Client +} + +func NewVirtualMachineInstanceWatcher(client client.Client) *VirtualMachineInstanceWatcher { + return &VirtualMachineInstanceWatcher{ + client: client, + } +} + +func (w VirtualMachineInstanceWatcher) Watch(mgr manager.Manager, ctr controller.Controller) error { + return ctr.Watch( + source.Kind(mgr.GetCache(), &virtv1.VirtualMachineInstance{}), + 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 VirtualMachineInstanceWatcher) enqueueRequests(ctx context.Context, obj client.Object) (requests []reconcile.Request) { + kvvmi, ok := obj.(*virtv1.VirtualMachineInstance) + if !ok { + slog.Default().Error(fmt.Sprintf("expected an VirtualMachineInstance but got a %T", obj)) + return + } + + vdByName := make(map[string]struct{}) + for _, volume := range kvvmi.Spec.Volumes { + if volume.PersistentVolumeClaim == nil { + continue + } + + var vdName string + vdName, ok = kvbuilder.GetOriginalDiskName(volume.PersistentVolumeClaim.ClaimName) + if !ok { + continue + } + + vdByName[vdName] = struct{}{} + } + + if len(vdByName) == 0 { + return + } + + var vdSnapshots virtv2.VirtualDiskSnapshotList + err := w.client.List(ctx, &vdSnapshots, &client.ListOptions{ + Namespace: obj.GetNamespace(), + }) + if err != nil { + slog.Default().Error(fmt.Sprintf("failed to list virtual disk snapshots: %s", err)) + return + } + + for _, vdSnapshot := range vdSnapshots.Items { + _, ok = vdByName[vdSnapshot.Spec.VirtualDiskName] + if !ok { + continue + } + + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: vdSnapshot.Name, + Namespace: vdSnapshot.Namespace, + }, + }) + } + + return +} + +func (w VirtualMachineInstanceWatcher) filterUpdateEvents(e event.UpdateEvent) bool { + oldKVVMI, ok := e.ObjectOld.(*virtv1.VirtualMachineInstance) + if !ok { + slog.Default().Error(fmt.Sprintf("expected an old VirtualMachineInstance but got a %T", e.ObjectOld)) + return false + } + + newKVVMI, ok := e.ObjectNew.(*virtv1.VirtualMachineInstance) + if !ok { + slog.Default().Error(fmt.Sprintf("expected a new VirtualMachineInstance but got a %T", e.ObjectNew)) + return false + } + + return oldKVVMI.Status.FSFreezeStatus != newKVVMI.Status.FSFreezeStatus +} diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/vd_watcher.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/vd_watcher.go new file mode 100644 index 000000000..9157c9768 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/vd_watcher.go @@ -0,0 +1,99 @@ +/* +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 VirtualDiskWatcher struct { + client client.Client +} + +func NewVirtualDiskWatcher(client client.Client) *VirtualDiskWatcher { + return &VirtualDiskWatcher{ + client: client, + } +} + +func (w VirtualDiskWatcher) Watch(mgr manager.Manager, ctr controller.Controller) error { + return ctr.Watch( + source.Kind(mgr.GetCache(), &virtv2.VirtualDisk{}), + 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 VirtualDiskWatcher) enqueueRequests(ctx context.Context, obj client.Object) (requests []reconcile.Request) { + var vdshots virtv2.VirtualDiskSnapshotList + err := w.client.List(ctx, &vdshots, &client.ListOptions{ + Namespace: obj.GetNamespace(), + }) + if err != nil { + slog.Default().Error(fmt.Sprintf("failed to list vdshots: %s", err)) + return + } + + for _, vdshot := range vdshots.Items { + if vdshot.Spec.VirtualDiskName != vdshot.Name { + continue + } + + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: vdshot.Name, + Namespace: vdshot.Namespace, + }, + }) + } + + return +} + +func (w VirtualDiskWatcher) filterUpdateEvents(e event.UpdateEvent) bool { + oldVD, ok := e.ObjectOld.(*virtv2.VirtualDisk) + if !ok { + slog.Default().Error(fmt.Sprintf("expected an old VirtualDisk but got a %T", e.ObjectOld)) + return false + } + + newVD, ok := e.ObjectNew.(*virtv2.VirtualDisk) + if !ok { + slog.Default().Error(fmt.Sprintf("expected a new VirtualDisk but got a %T", e.ObjectNew)) + return false + } + + return oldVD.Status.Phase != newVD.Status.Phase +} diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/vds_watcher.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/vds_watcher.go new file mode 100644 index 000000000..ef27d3f0c --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/vds_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 VirtualDiskSnapshotWatcher struct { + client client.Client +} + +func NewVirtualDiskSnapshotWatcher(client client.Client) *VirtualDiskSnapshotWatcher { + return &VirtualDiskSnapshotWatcher{ + client: client, + } +} + +func (w VirtualDiskSnapshotWatcher) Watch(mgr manager.Manager, ctr controller.Controller) error { + return ctr.Watch( + source.Kind(mgr.GetCache(), &virtv2.VirtualDiskSnapshot{}), + &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/vdsnapshot/internal/watcher/vs_watcher.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/vs_watcher.go new file mode 100644 index 000000000..3e0335c9b --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/vs_watcher.go @@ -0,0 +1,100 @@ +/* +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" + + vsv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1" + "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 VolumeSnapshotWatcher struct { + client client.Client +} + +func NewVolumeSnapshotWatcher(client client.Client) *VolumeSnapshotWatcher { + return &VolumeSnapshotWatcher{ + client: client, + } +} + +func (w VolumeSnapshotWatcher) Watch(mgr manager.Manager, ctr controller.Controller) error { + return ctr.Watch( + source.Kind(mgr.GetCache(), &vsv1.VolumeSnapshot{}), + 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 VolumeSnapshotWatcher) enqueueRequests(ctx context.Context, obj client.Object) (requests []reconcile.Request) { + var vdshots virtv2.VirtualDiskSnapshotList + err := w.client.List(ctx, &vdshots, &client.ListOptions{ + Namespace: obj.GetNamespace(), + }) + if err != nil { + slog.Default().Error(fmt.Sprintf("failed to list vdshots: %s", err)) + return + } + + for _, vdshot := range vdshots.Items { + if vdshot.Spec.VirtualDiskName != vdshot.Name { + continue + } + + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: vdshot.Name, + Namespace: vdshot.Namespace, + }, + }) + } + + return +} + +func (w VolumeSnapshotWatcher) filterUpdateEvents(e event.UpdateEvent) bool { + oldVD, ok := e.ObjectOld.(*vsv1.VolumeSnapshot) + if !ok { + slog.Default().Error(fmt.Sprintf("expected an old VolumeSnapshot but got a %T", e.ObjectOld)) + return false + } + + newVD, ok := e.ObjectNew.(*vsv1.VolumeSnapshot) + if !ok { + slog.Default().Error(fmt.Sprintf("expected a new VolumeSnapshot but got a %T", e.ObjectNew)) + return false + } + + return oldVD.Status.ReadyToUse != newVD.Status.ReadyToUse +} diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/vdsnapshot_controller.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/vdsnapshot_controller.go new file mode 100644 index 000000000..c95990fa4 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/vdsnapshot_controller.go @@ -0,0 +1,78 @@ +/* +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 vdsnapshot + +import ( + "context" + "log/slog" + + "k8s.io/client-go/rest" + "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" + "github.com/deckhouse/virtualization-controller/pkg/controller/vdsnapshot/internal" + "github.com/deckhouse/virtualization-controller/pkg/logger" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +const ControllerName = "vdsnapshot-controller" + +func NewController( + ctx context.Context, + mgr manager.Manager, + log *slog.Logger, + restClient *rest.RESTClient, +) (controller.Controller, error) { + log = log.With(logger.SlogController(ControllerName)) + + freezer := service.NewFreezerService(restClient, mgr.GetClient()) + + reconciler := NewReconciler( + mgr.GetClient(), + internal.NewVirtualDiskReadyHandler(freezer), + internal.NewLifeCycleHandler(freezer), + internal.NewDeletionHandler(), + ) + + vdSnapshotController, err := controller.New(ControllerName, mgr, controller.Options{ + Reconciler: reconciler, + RecoverPanic: ptr.To(true), + LogConstructor: logger.NewConstructor(log), + }) + if err != nil { + return nil, err + } + + err = reconciler.SetupController(ctx, mgr, vdSnapshotController) + if err != nil { + return nil, err + } + + if err = builder.WebhookManagedBy(mgr). + For(&virtv2.VirtualDiskSnapshot{}). + WithValidator(NewValidator(log)). + Complete(); err != nil { + return nil, err + } + + log.Info("Initialized VirtualDiskSnapshot controller") + + return vdSnapshotController, nil +} diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/vdsnapshot_reconciler.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/vdsnapshot_reconciler.go new file mode 100644 index 000000000..d72059858 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/vdsnapshot_reconciler.go @@ -0,0 +1,121 @@ +/* +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 vdsnapshot + +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/vdsnapshot/internal/watcher" + "github.com/deckhouse/virtualization-controller/pkg/logger" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +type Handler interface { + Handle(ctx context.Context, vdsnapshot *virtv2.VirtualDiskSnapshot) (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) + + vdsnapshot := service.NewResource(req.NamespacedName, r.client, r.factory, r.statusGetter) + + err := vdsnapshot.Fetch(ctx) + if err != nil { + return reconcile.Result{}, err + } + + if vdsnapshot.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, vdsnapshot.Changed()) + if err != nil { + log.Error("Failed to handle vdsnapshot", logger.SlogErr(err), logger.SlogHandler(reflect.TypeOf(h).Elem().Name())) + handlerErrs = append(handlerErrs, err) + } + + result = service.MergeResults(result, res) + } + + vdsnapshot.Changed().Status.ObservedGeneration = vdsnapshot.Changed().Generation + + err = vdsnapshot.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.NewVirtualDiskSnapshotWatcher(mgr.GetClient()), + watcher.NewVirtualDiskWatcher(mgr.GetClient()), + watcher.NewVolumeSnapshotWatcher(mgr.GetClient()), + watcher.NewVirtualMachineInstanceWatcher(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.VirtualDiskSnapshot { + return &virtv2.VirtualDiskSnapshot{} +} + +func (r *Reconciler) statusGetter(obj *virtv2.VirtualDiskSnapshot) virtv2.VirtualDiskSnapshotStatus { + return obj.Status +} diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/vdsnapshot_webhook.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/vdsnapshot_webhook.go new file mode 100644 index 000000000..419275723 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/vdsnapshot_webhook.go @@ -0,0 +1,81 @@ +/* +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 vdsnapshot + +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, 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") + } + + return nil, nil +} + +func (v *Validator) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + oldVDS, ok := oldObj.(*virtv2.VirtualDiskSnapshot) + if !ok { + return nil, fmt.Errorf("expected an old VirtualDiskSnapshot but got a %T", newObj) + } + + newVDS, ok := newObj.(*virtv2.VirtualDiskSnapshot) + if !ok { + return nil, fmt.Errorf("expected a new VirtualDiskSnapshot but got a %T", newObj) + } + + v.logger.Info("Validating VirtualDiskSnapshot") + + if oldVDS.Generation != newVDS.Generation { + return nil, fmt.Errorf("VirtualDiskSnapshot 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/local.Dockerfile b/local.Dockerfile new file mode 100644 index 000000000..8b188e508 --- /dev/null +++ b/local.Dockerfile @@ -0,0 +1,29 @@ +FROM golang:1.21-alpine as builder +RUN mkdir -p /images/virtualization-artifact +RUN mkdir /api + +ADD api/ /api/ +RUN ls -la / +WORKDIR /images/virtualization-artifact +ADD /images/virtualization-artifact/go.mod /images/virtualization-artifact/go.sum ./ +RUN go mod download + + +ADD /images/virtualization-artifact/ ./ +RUN export GO111MODULE=on +RUN export GOOS=linux +RUN export CGO_ENABLED=0 +RUN export GOARCH=amd64 +RUN go install github.com/go-delve/delve/cmd/dlv@latest +RUN CGO_ENABLED=0 go build -gcflags "all=-N -l" -a -o virtualization-controller ./cmd/virtualization-controller + +FROM scratch +COPY --from=builder /go/bin/dlv /dlv +COPY --from=builder /images/virtualization-artifact/virtualization-controller /virtualization-controller + +CMD ["/dlv", "--listen=:2345", "--headless=true", "--continue", "--log=true", "--log-output=debugger,debuglineerr,gdbwire,lldbout,rpc", "--accept-multiclient", "--api-version=2", "exec", "/virtualization-controller"] +#ENTRYPOINT [ "/dlv" ] + +#CMD ["/dlv --listen=:2345 --headless=true --log=true --log-output=debugger,debuglineerr,gdbwire,lldbout,rpc --accept-multiclient --api-version=2 exec /main"] + +#CMD ["./main"] diff --git a/templates/kubevirt/virt-operator/rbac-for-us.yaml b/templates/kubevirt/virt-operator/rbac-for-us.yaml index 41a73df81..d980fb182 100644 --- a/templates/kubevirt/virt-operator/rbac-for-us.yaml +++ b/templates/kubevirt/virt-operator/rbac-for-us.yaml @@ -587,6 +587,7 @@ rules: resources: - virtualmachines/addvolume - virtualmachines/removevolume + - virtualmachines/freeze verbs: - update - apiGroups: diff --git a/templates/user-authz-cluster-roles.yaml b/templates/user-authz-cluster-roles.yaml index e3077073c..9b61b74bf 100644 --- a/templates/user-authz-cluster-roles.yaml +++ b/templates/user-authz-cluster-roles.yaml @@ -53,6 +53,7 @@ rules: - virtualmachines/portforward - virtualmachines/addvolume - virtualmachines/removevolume + - virtualmachines/freeze verbs: - get - create diff --git a/templates/virtualization-controller/rbac-for-us.yaml b/templates/virtualization-controller/rbac-for-us.yaml index b03b2546a..168b3bfc7 100644 --- a/templates/virtualization-controller/rbac-for-us.yaml +++ b/templates/virtualization-controller/rbac-for-us.yaml @@ -122,6 +122,18 @@ rules: - update - list - delete +- apiGroups: + - snapshot.storage.k8s.io + resources: + - volumesnapshots + verbs: + - get + - watch + - create + - patch + - update + - list + - delete - apiGroups: - internal.virtualization.deckhouse.io resources: @@ -159,6 +171,7 @@ rules: - clustervirtualimages - virtualmachineoperations - virtualmachineclasses + - virtualdisksnapshots verbs: - create - delete @@ -180,6 +193,7 @@ rules: - virtualmachineipaddresses/finalizers - virtualmachineoperations/finalizers - virtualmachineclasses/finalizers + - virtualdisksnapshots/finalizers - virtualmachineipaddresses/status - virtualmachineipaddressleases/status - virtualdisks/status @@ -190,6 +204,7 @@ rules: - clustervirtualimages/status - virtualmachineoperations/status - virtualmachineclasses/status + - virtualdisksnapshots/status verbs: - patch - update diff --git a/templates/virtualization-controller/validation-webhook.yaml b/templates/virtualization-controller/validation-webhook.yaml index 22131740b..2fd79ce1d 100644 --- a/templates/virtualization-controller/validation-webhook.yaml +++ b/templates/virtualization-controller/validation-webhook.yaml @@ -139,4 +139,20 @@ webhooks: {{ .Values.virtualization.internal.controller.cert.ca }} admissionReviewVersions: ["v1"] sideEffects: None - + - name: "vdshot.virtualization-controller.validate.d8-virtualization" + rules: + - apiGroups: ["virtualization.deckhouse.io"] + apiVersions: ["v1alpha2"] + operations: ["CREATE", "UPDATE"] + resources: ["virtualdisksnapshots"] + scope: "Namespaced" + clientConfig: + service: + namespace: d8-{{ .Chart.Name }} + name: virtualization-controller + path: /validate-virtualization-deckhouse-io-v1alpha2-virtualdisksnapshot + port: 443 + caBundle: | + {{ .Values.virtualization.internal.controller.cert.ca }} + admissionReviewVersions: ["v1"] + sideEffects: None