diff --git a/.github/actions/milestone-changelog/action.yml b/.github/actions/milestone-changelog/action.yml index 4ba0c6bef9..d825b1705a 100644 --- a/.github/actions/milestone-changelog/action.yml +++ b/.github/actions/milestone-changelog/action.yml @@ -36,6 +36,7 @@ runs: vdsnapshot vmsnapshot vmrestore + vmpool disks vd images diff --git a/.github/workflows/check-changelog-entry.yml b/.github/workflows/check-changelog-entry.yml index b459953e33..49e5b9e47e 100644 --- a/.github/workflows/check-changelog-entry.yml +++ b/.github/workflows/check-changelog-entry.yml @@ -46,6 +46,7 @@ jobs: vdsnapshot vmsnapshot vmrestore + vmpool disks vd images 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 95d9f09723..9bde1c921a 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 @@ -42,6 +42,7 @@ type VirtualizationV1alpha2Interface interface { VirtualMachineMACAddressesGetter VirtualMachineMACAddressLeasesGetter VirtualMachineOperationsGetter + VirtualMachinePoolsGetter VirtualMachineSnapshotsGetter VirtualMachineSnapshotOperationsGetter } @@ -107,6 +108,10 @@ func (c *VirtualizationV1alpha2Client) VirtualMachineOperations(namespace string return newVirtualMachineOperations(c, namespace) } +func (c *VirtualizationV1alpha2Client) VirtualMachinePools(namespace string) VirtualMachinePoolInterface { + return newVirtualMachinePools(c, namespace) +} + func (c *VirtualizationV1alpha2Client) VirtualMachineSnapshots(namespace string) VirtualMachineSnapshotInterface { return newVirtualMachineSnapshots(c, namespace) } diff --git a/api/client/generated/clientset/versioned/typed/core/v1alpha2/fake/fake_core_client.go b/api/client/generated/clientset/versioned/typed/core/v1alpha2/fake/fake_core_client.go index 3e4d10a08e..b4498e50fd 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 @@ -84,6 +84,10 @@ func (c *FakeVirtualizationV1alpha2) VirtualMachineOperations(namespace string) return newFakeVirtualMachineOperations(c, namespace) } +func (c *FakeVirtualizationV1alpha2) VirtualMachinePools(namespace string) v1alpha2.VirtualMachinePoolInterface { + return newFakeVirtualMachinePools(c, namespace) +} + func (c *FakeVirtualizationV1alpha2) VirtualMachineSnapshots(namespace string) v1alpha2.VirtualMachineSnapshotInterface { return newFakeVirtualMachineSnapshots(c, namespace) } diff --git a/api/client/generated/clientset/versioned/typed/core/v1alpha2/fake/fake_virtualmachinepool.go b/api/client/generated/clientset/versioned/typed/core/v1alpha2/fake/fake_virtualmachinepool.go new file mode 100644 index 0000000000..66f8670201 --- /dev/null +++ b/api/client/generated/clientset/versioned/typed/core/v1alpha2/fake/fake_virtualmachinepool.go @@ -0,0 +1,52 @@ +/* +Copyright Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + corev1alpha2 "github.com/deckhouse/virtualization/api/client/generated/clientset/versioned/typed/core/v1alpha2" + v1alpha2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + gentype "k8s.io/client-go/gentype" +) + +// fakeVirtualMachinePools implements VirtualMachinePoolInterface +type fakeVirtualMachinePools struct { + *gentype.FakeClientWithList[*v1alpha2.VirtualMachinePool, *v1alpha2.VirtualMachinePoolList] + Fake *FakeVirtualizationV1alpha2 +} + +func newFakeVirtualMachinePools(fake *FakeVirtualizationV1alpha2, namespace string) corev1alpha2.VirtualMachinePoolInterface { + return &fakeVirtualMachinePools{ + gentype.NewFakeClientWithList[*v1alpha2.VirtualMachinePool, *v1alpha2.VirtualMachinePoolList]( + fake.Fake, + namespace, + v1alpha2.SchemeGroupVersion.WithResource("virtualmachinepools"), + v1alpha2.SchemeGroupVersion.WithKind("VirtualMachinePool"), + func() *v1alpha2.VirtualMachinePool { return &v1alpha2.VirtualMachinePool{} }, + func() *v1alpha2.VirtualMachinePoolList { return &v1alpha2.VirtualMachinePoolList{} }, + func(dst, src *v1alpha2.VirtualMachinePoolList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha2.VirtualMachinePoolList) []*v1alpha2.VirtualMachinePool { + return gentype.ToPointerSlice(list.Items) + }, + func(list *v1alpha2.VirtualMachinePoolList, items []*v1alpha2.VirtualMachinePool) { + list.Items = gentype.FromPointerSlice(items) + }, + ), + fake, + } +} diff --git a/api/client/generated/clientset/versioned/typed/core/v1alpha2/generated_expansion.go b/api/client/generated/clientset/versioned/typed/core/v1alpha2/generated_expansion.go index 3032ee8501..8a24c86510 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 @@ -44,6 +44,8 @@ type VirtualMachineMACAddressLeaseExpansion interface{} type VirtualMachineOperationExpansion interface{} +type VirtualMachinePoolExpansion interface{} + type VirtualMachineSnapshotExpansion interface{} type VirtualMachineSnapshotOperationExpansion interface{} diff --git a/api/client/generated/clientset/versioned/typed/core/v1alpha2/virtualmachinepool.go b/api/client/generated/clientset/versioned/typed/core/v1alpha2/virtualmachinepool.go new file mode 100644 index 0000000000..fb0fc5839b --- /dev/null +++ b/api/client/generated/clientset/versioned/typed/core/v1alpha2/virtualmachinepool.go @@ -0,0 +1,70 @@ +/* +Copyright Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + context "context" + + scheme "github.com/deckhouse/virtualization/api/client/generated/clientset/versioned/scheme" + corev1alpha2 "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" + gentype "k8s.io/client-go/gentype" +) + +// VirtualMachinePoolsGetter has a method to return a VirtualMachinePoolInterface. +// A group's client should implement this interface. +type VirtualMachinePoolsGetter interface { + VirtualMachinePools(namespace string) VirtualMachinePoolInterface +} + +// VirtualMachinePoolInterface has methods to work with VirtualMachinePool resources. +type VirtualMachinePoolInterface interface { + Create(ctx context.Context, virtualMachinePool *corev1alpha2.VirtualMachinePool, opts v1.CreateOptions) (*corev1alpha2.VirtualMachinePool, error) + Update(ctx context.Context, virtualMachinePool *corev1alpha2.VirtualMachinePool, opts v1.UpdateOptions) (*corev1alpha2.VirtualMachinePool, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, virtualMachinePool *corev1alpha2.VirtualMachinePool, opts v1.UpdateOptions) (*corev1alpha2.VirtualMachinePool, 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) (*corev1alpha2.VirtualMachinePool, error) + List(ctx context.Context, opts v1.ListOptions) (*corev1alpha2.VirtualMachinePoolList, 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 *corev1alpha2.VirtualMachinePool, err error) + VirtualMachinePoolExpansion +} + +// virtualMachinePools implements VirtualMachinePoolInterface +type virtualMachinePools struct { + *gentype.ClientWithList[*corev1alpha2.VirtualMachinePool, *corev1alpha2.VirtualMachinePoolList] +} + +// newVirtualMachinePools returns a VirtualMachinePools +func newVirtualMachinePools(c *VirtualizationV1alpha2Client, namespace string) *virtualMachinePools { + return &virtualMachinePools{ + gentype.NewClientWithList[*corev1alpha2.VirtualMachinePool, *corev1alpha2.VirtualMachinePoolList]( + "virtualmachinepools", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *corev1alpha2.VirtualMachinePool { return &corev1alpha2.VirtualMachinePool{} }, + func() *corev1alpha2.VirtualMachinePoolList { return &corev1alpha2.VirtualMachinePoolList{} }, + ), + } +} diff --git a/api/client/generated/informers/externalversions/core/v1alpha2/interface.go b/api/client/generated/informers/externalversions/core/v1alpha2/interface.go index ed97a49b94..c98d319363 100644 --- a/api/client/generated/informers/externalversions/core/v1alpha2/interface.go +++ b/api/client/generated/informers/externalversions/core/v1alpha2/interface.go @@ -52,6 +52,8 @@ type Interface interface { VirtualMachineMACAddressLeases() VirtualMachineMACAddressLeaseInformer // VirtualMachineOperations returns a VirtualMachineOperationInformer. VirtualMachineOperations() VirtualMachineOperationInformer + // VirtualMachinePools returns a VirtualMachinePoolInformer. + VirtualMachinePools() VirtualMachinePoolInformer // VirtualMachineSnapshots returns a VirtualMachineSnapshotInformer. VirtualMachineSnapshots() VirtualMachineSnapshotInformer // VirtualMachineSnapshotOperations returns a VirtualMachineSnapshotOperationInformer. @@ -139,6 +141,11 @@ func (v *version) VirtualMachineOperations() VirtualMachineOperationInformer { return &virtualMachineOperationInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } +// VirtualMachinePools returns a VirtualMachinePoolInformer. +func (v *version) VirtualMachinePools() VirtualMachinePoolInformer { + return &virtualMachinePoolInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // VirtualMachineSnapshots returns a VirtualMachineSnapshotInformer. func (v *version) VirtualMachineSnapshots() VirtualMachineSnapshotInformer { return &virtualMachineSnapshotInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/api/client/generated/informers/externalversions/core/v1alpha2/virtualmachinepool.go b/api/client/generated/informers/externalversions/core/v1alpha2/virtualmachinepool.go new file mode 100644 index 0000000000..de1f406bac --- /dev/null +++ b/api/client/generated/informers/externalversions/core/v1alpha2/virtualmachinepool.go @@ -0,0 +1,102 @@ +/* +Copyright Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + context "context" + time "time" + + versioned "github.com/deckhouse/virtualization/api/client/generated/clientset/versioned" + internalinterfaces "github.com/deckhouse/virtualization/api/client/generated/informers/externalversions/internalinterfaces" + corev1alpha2 "github.com/deckhouse/virtualization/api/client/generated/listers/core/v1alpha2" + apicorev1alpha2 "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" +) + +// VirtualMachinePoolInformer provides access to a shared informer and lister for +// VirtualMachinePools. +type VirtualMachinePoolInformer interface { + Informer() cache.SharedIndexInformer + Lister() corev1alpha2.VirtualMachinePoolLister +} + +type virtualMachinePoolInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewVirtualMachinePoolInformer constructs a new informer for VirtualMachinePool 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 NewVirtualMachinePoolInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredVirtualMachinePoolInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredVirtualMachinePoolInformer constructs a new informer for VirtualMachinePool 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 NewFilteredVirtualMachinePoolInformer(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().VirtualMachinePools(namespace).List(context.Background(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.VirtualizationV1alpha2().VirtualMachinePools(namespace).Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.VirtualizationV1alpha2().VirtualMachinePools(namespace).List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.VirtualizationV1alpha2().VirtualMachinePools(namespace).Watch(ctx, options) + }, + }, + &apicorev1alpha2.VirtualMachinePool{}, + resyncPeriod, + indexers, + ) +} + +func (f *virtualMachinePoolInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredVirtualMachinePoolInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *virtualMachinePoolInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apicorev1alpha2.VirtualMachinePool{}, f.defaultInformer) +} + +func (f *virtualMachinePoolInformer) Lister() corev1alpha2.VirtualMachinePoolLister { + return corev1alpha2.NewVirtualMachinePoolLister(f.Informer().GetIndexer()) +} diff --git a/api/client/generated/informers/externalversions/generic.go b/api/client/generated/informers/externalversions/generic.go index 8b16f3d58a..093bdb6a29 100644 --- a/api/client/generated/informers/externalversions/generic.go +++ b/api/client/generated/informers/externalversions/generic.go @@ -82,6 +82,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Virtualization().V1alpha2().VirtualMachineMACAddressLeases().Informer()}, nil case v1alpha2.SchemeGroupVersion.WithResource("virtualmachineoperations"): return &genericInformer{resource: resource.GroupResource(), informer: f.Virtualization().V1alpha2().VirtualMachineOperations().Informer()}, nil + case v1alpha2.SchemeGroupVersion.WithResource("virtualmachinepools"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Virtualization().V1alpha2().VirtualMachinePools().Informer()}, nil case v1alpha2.SchemeGroupVersion.WithResource("virtualmachinesnapshots"): return &genericInformer{resource: resource.GroupResource(), informer: f.Virtualization().V1alpha2().VirtualMachineSnapshots().Informer()}, nil case v1alpha2.SchemeGroupVersion.WithResource("virtualmachinesnapshotoperations"): diff --git a/api/client/generated/listers/core/v1alpha2/expansion_generated.go b/api/client/generated/listers/core/v1alpha2/expansion_generated.go index e47e2ae835..f7da265496 100644 --- a/api/client/generated/listers/core/v1alpha2/expansion_generated.go +++ b/api/client/generated/listers/core/v1alpha2/expansion_generated.go @@ -110,6 +110,14 @@ type VirtualMachineOperationListerExpansion interface{} // VirtualMachineOperationNamespaceLister. type VirtualMachineOperationNamespaceListerExpansion interface{} +// VirtualMachinePoolListerExpansion allows custom methods to be added to +// VirtualMachinePoolLister. +type VirtualMachinePoolListerExpansion interface{} + +// VirtualMachinePoolNamespaceListerExpansion allows custom methods to be added to +// VirtualMachinePoolNamespaceLister. +type VirtualMachinePoolNamespaceListerExpansion interface{} + // VirtualMachineSnapshotListerExpansion allows custom methods to be added to // VirtualMachineSnapshotLister. type VirtualMachineSnapshotListerExpansion interface{} diff --git a/api/client/generated/listers/core/v1alpha2/virtualmachinepool.go b/api/client/generated/listers/core/v1alpha2/virtualmachinepool.go new file mode 100644 index 0000000000..2bc93b5adb --- /dev/null +++ b/api/client/generated/listers/core/v1alpha2/virtualmachinepool.go @@ -0,0 +1,70 @@ +/* +Copyright Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + corev1alpha2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" +) + +// VirtualMachinePoolLister helps list VirtualMachinePools. +// All objects returned here must be treated as read-only. +type VirtualMachinePoolLister interface { + // List lists all VirtualMachinePools in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*corev1alpha2.VirtualMachinePool, err error) + // VirtualMachinePools returns an object that can list and get VirtualMachinePools. + VirtualMachinePools(namespace string) VirtualMachinePoolNamespaceLister + VirtualMachinePoolListerExpansion +} + +// virtualMachinePoolLister implements the VirtualMachinePoolLister interface. +type virtualMachinePoolLister struct { + listers.ResourceIndexer[*corev1alpha2.VirtualMachinePool] +} + +// NewVirtualMachinePoolLister returns a new VirtualMachinePoolLister. +func NewVirtualMachinePoolLister(indexer cache.Indexer) VirtualMachinePoolLister { + return &virtualMachinePoolLister{listers.New[*corev1alpha2.VirtualMachinePool](indexer, corev1alpha2.Resource("virtualmachinepool"))} +} + +// VirtualMachinePools returns an object that can list and get VirtualMachinePools. +func (s *virtualMachinePoolLister) VirtualMachinePools(namespace string) VirtualMachinePoolNamespaceLister { + return virtualMachinePoolNamespaceLister{listers.NewNamespaced[*corev1alpha2.VirtualMachinePool](s.ResourceIndexer, namespace)} +} + +// VirtualMachinePoolNamespaceLister helps list and get VirtualMachinePools. +// All objects returned here must be treated as read-only. +type VirtualMachinePoolNamespaceLister interface { + // List lists all VirtualMachinePools in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*corev1alpha2.VirtualMachinePool, err error) + // Get retrieves the VirtualMachinePool from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*corev1alpha2.VirtualMachinePool, error) + VirtualMachinePoolNamespaceListerExpansion +} + +// virtualMachinePoolNamespaceLister implements the VirtualMachinePoolNamespaceLister +// interface. +type virtualMachinePoolNamespaceLister struct { + listers.ResourceIndexer[*corev1alpha2.VirtualMachinePool] +} diff --git a/api/core/v1alpha2/register.go b/api/core/v1alpha2/register.go index 821755d18f..ec9c569262 100644 --- a/api/core/v1alpha2/register.go +++ b/api/core/v1alpha2/register.go @@ -38,6 +38,9 @@ var VirtualImageGVK = schema.GroupVersionKind{Group: SchemeGroupVersion.Group, V // VirtualDiskGVK is group version kind for VirtualDisk var VirtualDiskGVK = schema.GroupVersionKind{Group: SchemeGroupVersion.Group, Version: SchemeGroupVersion.Version, Kind: VirtualDiskKind} +// VirtualMachinePoolGVK is group version kind for VirtualMachinePool +var VirtualMachinePoolGVK = schema.GroupVersionKind{Group: SchemeGroupVersion.Group, Version: SchemeGroupVersion.Version, Kind: VirtualMachinePoolKind} + // Kind takes an unqualified kind and returns back a Group qualified GroupKind func Kind(kind string) schema.GroupKind { return SchemeGroupVersion.WithKind(kind).GroupKind() @@ -70,6 +73,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { &VirtualDiskList{}, &VirtualMachine{}, &VirtualMachineList{}, + &VirtualMachinePool{}, + &VirtualMachinePoolList{}, &VirtualMachineBlockDeviceAttachment{}, &VirtualMachineBlockDeviceAttachmentList{}, &VirtualMachineClass{}, diff --git a/api/core/v1alpha2/virtual_machine_pool.go b/api/core/v1alpha2/virtual_machine_pool.go new file mode 100644 index 0000000000..4d8b12a3b1 --- /dev/null +++ b/api/core/v1alpha2/virtual_machine_pool.go @@ -0,0 +1,255 @@ +/* +Copyright 2026 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 ( + VirtualMachinePoolKind = "VirtualMachinePool" + VirtualMachinePoolResource = "virtualmachinepools" +) + +// VirtualMachinePool declaratively manages a group of identical virtual machines: +// it keeps the requested number of replicas, scales via the standard `scale` +// subresource, and reuses "heavy" disks across replica generations. +// +// The resource is available only in paid editions (EE/SE+) and is gated behind +// the `VirtualMachinePool` module feature gate. +// +// +kubebuilder:object:root=true +// +kubebuilder:metadata:labels={heritage=deckhouse,module=virtualization} +// +kubebuilder:subresource:status +// +kubebuilder:subresource:scale:specpath=.spec.replicas,statuspath=.status.replicas,selectorpath=.status.selector +// +kubebuilder:resource:categories={virtualization},scope=Namespaced,shortName={vmpool,vmpools},singular=virtualmachinepool +// +kubebuilder:storageversion +// +kubebuilder:printcolumn:name="Replicas",type="integer",JSONPath=".status.replicas",description="Current number of pool members (including Terminating)." +// +kubebuilder:printcolumn:name="Ready",type="integer",JSONPath=".status.readyReplicas",description="Number of members ready to serve." +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Time of resource creation." +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type VirtualMachinePool struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec VirtualMachinePoolSpec `json:"spec"` + Status VirtualMachinePoolStatus `json:"status,omitempty"` +} + +// VirtualMachinePoolList contains a list of VirtualMachinePool resources. +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type VirtualMachinePoolList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []VirtualMachinePool `json:"items"` +} + +// VirtualMachinePoolSpec is the desired state of a VirtualMachinePool. +type VirtualMachinePoolSpec struct { + // Replicas is the desired number of virtual machines in the pool. + // + // The field is written only by its owner — an autoscaler or a human via the + // `scale` subresource, or by the addressed scale-down handler. The controller + // never writes it. Bounds are held by the autoscaler; the hard ceiling is the + // namespace ResourceQuota. + // + // +kubebuilder:validation:Minimum=0 + // +optional + Replicas *int32 `json:"replicas,omitempty"` + + // ScaleDownPolicy chooses how a replica is picked when the pool is scaled down + // anonymously through the `scale` subresource. It is required and has no + // default, forcing a conscious choice between "any replica may be killed" and + // "only addressed removal is allowed". + // + // - `NewestFirst` — anonymous scale-down is allowed; the youngest replicas + // (least accumulated state) are removed first. + // - `OldestFirst` — anonymous scale-down is allowed; the oldest replicas are + // removed first (faster rotation). + // - `Explicit` — anonymous scale-down through `scale` is rejected by a + // webhook; replicas can be removed only by address. For "busy" workloads + // such as CI runners and VDI. + // + // +kubebuilder:validation:Enum=NewestFirst;OldestFirst;Explicit + ScaleDownPolicy ScaleDownPolicy `json:"scaleDownPolicy"` + + // VirtualMachineTemplate is the template every replica is stamped from. Its + // `spec` is an ordinary VirtualMachineSpec, so a replica is no different from a + // manually created virtual machine. + VirtualMachineTemplate VirtualMachineTemplateSpec `json:"virtualMachineTemplate"` + + // VirtualDiskTemplates describes the per-replica disks and, by their order, the + // replica's block devices — the first template is the boot device. A disk with + // reclaim Delete belongs to its VirtualMachine and is removed with it; a disk + // with reclaim Retain belongs to the pool, outlives the replica and is reused + // on a later scale-up. This is the sole source of a replica's disks: the pool's + // virtualMachineTemplate has no blockDeviceRefs field — the controller builds it + // from these templates. + // + // +kubebuilder:validation:MinItems=1 + // +listType=map + // +listMapKey=name + VirtualDiskTemplates []VirtualDiskTemplateSpec `json:"virtualDiskTemplates"` +} + +// VirtualDiskTemplateSpec describes a per-replica disk. +type VirtualDiskTemplateSpec struct { + // Name identifies the disk template within the pool. It is a DNS-1123 label + // (no dots), because it is embedded into VirtualDisk names. + // + // +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]*[a-z0-9])?$` + // +kubebuilder:validation:MaxLength=63 + Name string `json:"name"` + + // Reclaim controls what happens to the disk when its replica is removed. + // + // +optional + Reclaim VirtualDiskReclaim `json:"reclaim,omitempty"` + + // Spec is the desired state of the disk (an ordinary VirtualDiskSpec). + Spec VirtualDiskSpec `json:"spec"` +} + +// VirtualDiskReclaimPolicy selects the fate of a per-replica disk on scale-down. +type VirtualDiskReclaimPolicy string + +const ( + // VirtualDiskReclaimDelete removes the disk together with its replica (owner + // is the VirtualMachine). This is the default. + VirtualDiskReclaimDelete VirtualDiskReclaimPolicy = "Delete" + // VirtualDiskReclaimRetain keeps the disk (owner is the pool); it is reused on + // the next scale-up. + VirtualDiskReclaimRetain VirtualDiskReclaimPolicy = "Retain" +) + +// VirtualDiskReclaim is the reclaim policy and warm-buffer settings of a disk +// template. +// +// +kubebuilder:validation:XValidation:rule="self.onScaleDown == 'Retain' || (self.keep == 0 && !has(self.ttl))",message="keep and ttl are only valid with onScaleDown: Retain" +// +kubebuilder:validation:XValidation:rule="self.keep == 0 || has(self.ttl)",message="keep requires ttl; without ttl free disks are never garbage-collected, so keep would have no effect" +type VirtualDiskReclaim struct { + // OnScaleDown is Delete (default) or Retain. + // + // +kubebuilder:validation:Enum=Delete;Retain + // +kubebuilder:default=Delete + // +optional + OnScaleDown VirtualDiskReclaimPolicy `json:"onScaleDown,omitempty"` + + // Keep is the number of free (Retain) disks always kept warm for fast + // scale-up; these are immune to the ttl. Only meaningful with Retain. + // + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:default=0 + // +optional + Keep int32 `json:"keep,omitempty"` + + // TTL is how long a free disk lives beyond the warm buffer before it is + // garbage-collected. Only meaningful with Retain. + // + // +optional + TTL *metav1.Duration `json:"ttl,omitempty"` +} + +// ScaleDownPolicy selects which replica is removed on anonymous scale-down. +type ScaleDownPolicy string + +const ( + ScaleDownPolicyNewestFirst ScaleDownPolicy = "NewestFirst" + ScaleDownPolicyOldestFirst ScaleDownPolicy = "OldestFirst" + ScaleDownPolicyExplicit ScaleDownPolicy = "Explicit" +) + +// VirtualMachineTemplateSpec describes the metadata and spec a pool replica is +// created with. +type VirtualMachineTemplateSpec struct { + // Metadata applied to every replica. Arbitrary user labels and annotations are + // allowed; the controller adds its managed pool labels on top. A curated + // struct (not the full ObjectMeta) so the CRD schema exposes labels and + // annotations instead of an opaque object. + // + // +optional + Metadata VirtualMachineTemplateMetadata `json:"metadata,omitempty"` + + // Spec of the virtual machine that backs each replica. + // + // +optional + Spec VirtualMachineSpec `json:"spec,omitempty"` +} + +// VirtualMachineTemplateMetadata is the subset of object metadata a pool +// stamps onto each replica. +type VirtualMachineTemplateMetadata struct { + // +optional + Labels map[string]string `json:"labels,omitempty"` + // +optional + Annotations map[string]string `json:"annotations,omitempty"` +} + +// VirtualMachinePoolStatus is the observed state of a VirtualMachinePool. +type VirtualMachinePoolStatus struct { + // ObservedGeneration is the generation of the spec the controller has processed. + // + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + + // Replicas is the number of existing members, including those in Terminating: + // such a machine still occupies resources, so it is real capacity, not a phantom. + // + // +optional + Replicas int32 `json:"replicas,omitempty"` + + // ReadyReplicas is the number of members ready to serve (Terminating excluded). + // + // +optional + ReadyReplicas int32 `json:"readyReplicas,omitempty"` + + // DesiredTemplateHash is the hash of the current virtualMachineTemplate — the + // revision the controller is converging replicas to (cf. updateRevision on a + // StatefulSet). + // + // +optional + DesiredTemplateHash string `json:"desiredTemplateHash,omitempty"` + + // UpdatedReplicas is the number of replicas effectively on DesiredTemplateHash + // (fully synced). + // + // +optional + UpdatedReplicas int32 `json:"updatedReplicas,omitempty"` + + // RestartPendingReplicas is the number of replicas patched to the new template + // whose disruptive part still awaits a restart. + // + // +optional + RestartPendingReplicas int32 `json:"restartPendingReplicas,omitempty"` + + // Selector is the label selector the controller publishes for the `scale` + // subresource; HPA/KEDA read it themselves. + // + // +optional + Selector string `json:"selector,omitempty"` + + // Conditions describe the current state of the pool. + // + // +optional + // +patchMergeKey=type + // +patchStrategy=merge + // +listType=map + // +listMapKey=type + Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` +} diff --git a/api/core/v1alpha2/vmpoolcondition/condition.go b/api/core/v1alpha2/vmpoolcondition/condition.go new file mode 100644 index 0000000000..ded30ba7a5 --- /dev/null +++ b/api/core/v1alpha2/vmpoolcondition/condition.go @@ -0,0 +1,76 @@ +/* +Copyright 2026 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 vmpoolcondition + +// Type is a type of VirtualMachinePool condition. +type Type string + +func (t Type) String() string { + return string(t) +} + +const ( + // TypeAvailable indicates whether the pool has enough ready replicas. + TypeAvailable Type = "Available" + // TypeProgressing indicates that a self-converging rollout is in progress + // (scaling, creation, migration). + TypeProgressing Type = "Progressing" + // TypeSynced indicates whether every live replica is effectively on the + // current virtualMachineTemplate. + TypeSynced Type = "Synced" +) + +// AvailableReason is a reason for the Available condition. +type AvailableReason string + +func (r AvailableReason) String() string { + return string(r) +} + +const ( + // The pool has no minReplicas/maxUnavailable, so Available means every desired + // replica is ready — hence "all", not "minimum". + ReasonAllReplicasReady AvailableReason = "AllReplicasReady" + ReasonInsufficientReadyReplicas AvailableReason = "InsufficientReadyReplicas" +) + +// ProgressingReason is a reason for the Progressing condition. +type ProgressingReason string + +func (r ProgressingReason) String() string { + return string(r) +} + +const ( + ReasonPoolStable ProgressingReason = "PoolStable" + // ReplicasProgressing covers any convergence of the replica count — scaling + // as well as replacing a replica that disappeared — not only scaling. + ReasonReplicasProgressing ProgressingReason = "ReplicasProgressing" +) + +// SyncedReason is a reason for the Synced condition. +type SyncedReason string + +func (r SyncedReason) String() string { + return string(r) +} + +const ( + ReasonPoolSynced SyncedReason = "PoolSynced" + ReasonRolloutInProgress SyncedReason = "RolloutInProgress" + ReasonRestartPendingApproval SyncedReason = "RestartPendingApproval" +) diff --git a/api/core/v1alpha2/zz_generated.deepcopy.go b/api/core/v1alpha2/zz_generated.deepcopy.go index 58d00cb82b..8ee5d37f0a 100644 --- a/api/core/v1alpha2/zz_generated.deepcopy.go +++ b/api/core/v1alpha2/zz_generated.deepcopy.go @@ -1472,6 +1472,27 @@ 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 *VirtualDiskReclaim) DeepCopyInto(out *VirtualDiskReclaim) { + *out = *in + if in.TTL != nil { + in, out := &in.TTL, &out.TTL + *out = new(v1.Duration) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualDiskReclaim. +func (in *VirtualDiskReclaim) DeepCopy() *VirtualDiskReclaim { + if in == nil { + return nil + } + out := new(VirtualDiskReclaim) + in.DeepCopyInto(out) + 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 @@ -1698,6 +1719,24 @@ func (in *VirtualDiskStatus) DeepCopy() *VirtualDiskStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VirtualDiskTemplateSpec) DeepCopyInto(out *VirtualDiskTemplateSpec) { + *out = *in + in.Reclaim.DeepCopyInto(&out.Reclaim) + in.Spec.DeepCopyInto(&out.Spec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualDiskTemplateSpec. +func (in *VirtualDiskTemplateSpec) DeepCopy() *VirtualDiskTemplateSpec { + if in == nil { + return nil + } + out := new(VirtualDiskTemplateSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VirtualImage) DeepCopyInto(out *VirtualImage) { *out = *in @@ -3069,6 +3108,119 @@ func (in *VirtualMachinePod) DeepCopy() *VirtualMachinePod { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VirtualMachinePool) DeepCopyInto(out *VirtualMachinePool) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualMachinePool. +func (in *VirtualMachinePool) DeepCopy() *VirtualMachinePool { + if in == nil { + return nil + } + out := new(VirtualMachinePool) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VirtualMachinePool) 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 *VirtualMachinePoolList) DeepCopyInto(out *VirtualMachinePoolList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]VirtualMachinePool, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualMachinePoolList. +func (in *VirtualMachinePoolList) DeepCopy() *VirtualMachinePoolList { + if in == nil { + return nil + } + out := new(VirtualMachinePoolList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VirtualMachinePoolList) 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 *VirtualMachinePoolSpec) DeepCopyInto(out *VirtualMachinePoolSpec) { + *out = *in + if in.Replicas != nil { + in, out := &in.Replicas, &out.Replicas + *out = new(int32) + **out = **in + } + in.VirtualMachineTemplate.DeepCopyInto(&out.VirtualMachineTemplate) + if in.VirtualDiskTemplates != nil { + in, out := &in.VirtualDiskTemplates, &out.VirtualDiskTemplates + *out = make([]VirtualDiskTemplateSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualMachinePoolSpec. +func (in *VirtualMachinePoolSpec) DeepCopy() *VirtualMachinePoolSpec { + if in == nil { + return nil + } + out := new(VirtualMachinePoolSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VirtualMachinePoolStatus) DeepCopyInto(out *VirtualMachinePoolStatus) { + *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 VirtualMachinePoolStatus. +func (in *VirtualMachinePoolStatus) DeepCopy() *VirtualMachinePoolStatus { + if in == nil { + return nil + } + out := new(VirtualMachinePoolStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VirtualMachineSnapshot) DeepCopyInto(out *VirtualMachineSnapshot) { *out = *in @@ -3472,6 +3624,54 @@ func (in *VirtualMachineStatus) DeepCopy() *VirtualMachineStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VirtualMachineTemplateMetadata) DeepCopyInto(out *VirtualMachineTemplateMetadata) { + *out = *in + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualMachineTemplateMetadata. +func (in *VirtualMachineTemplateMetadata) DeepCopy() *VirtualMachineTemplateMetadata { + if in == nil { + return nil + } + out := new(VirtualMachineTemplateMetadata) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VirtualMachineTemplateSpec) DeepCopyInto(out *VirtualMachineTemplateSpec) { + *out = *in + in.Metadata.DeepCopyInto(&out.Metadata) + in.Spec.DeepCopyInto(&out.Spec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualMachineTemplateSpec. +func (in *VirtualMachineTemplateSpec) DeepCopy() *VirtualMachineTemplateSpec { + if in == nil { + return nil + } + out := new(VirtualMachineTemplateSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WeightedVirtualMachineAndPodAffinityTerm) DeepCopyInto(out *WeightedVirtualMachineAndPodAffinityTerm) { *out = *in diff --git a/api/scripts/update-codegen.sh b/api/scripts/update-codegen.sh index b07d310fc5..40e90c306e 100755 --- a/api/scripts/update-codegen.sh +++ b/api/scripts/update-codegen.sh @@ -41,7 +41,8 @@ function source::settings { "VirtualImage" "ClusterVirtualImage" "NodeUSBDevice" - "USBDevice") + "USBDevice" + "VirtualMachinePool") # shellcheck source=/dev/null source "${CODEGEN_PKG}/kube_codegen.sh" @@ -89,6 +90,34 @@ function generate::crds { fi cp "$file" "${ROOT}/crds/$(echo "$file" | awk -Fio_ '{print $2}')" done + + generate::strip_vmpool_blockdevicerefs +} + +# generate::strip_vmpool_blockdevicerefs removes blockDeviceRefs from the +# VirtualMachinePool CRD's virtualMachineTemplate.spec. That field is inherited +# from the shared VirtualMachineSpec (required, minItems:1, user-settable), but a +# pool derives each replica's block devices from virtualDiskTemplates, so the +# field must not be exposed on the pool. It is stripped ONLY from the pool CRD — +# never touch the VirtualMachine CRD or the shared Go type. +# +# The step fails loudly if the field is not present before stripping (schema path +# moved) or still present after, so it can never silently no-op. A unit test +# (TestVMPoolCRDHasNoBlockDeviceRefs) additionally guards the committed CRD. +function generate::strip_vmpool_blockdevicerefs { + local pool_crd="${ROOT}/crds/virtualmachinepools.yaml" + local bdr='.spec.versions[].schema.openAPIV3Schema.properties.spec.properties.virtualMachineTemplate.properties.spec.properties.blockDeviceRefs' + + if [ "$(yq "$bdr | tag" "$pool_crd")" != "!!map" ]; then + echo "ERROR: ${pool_crd}: blockDeviceRefs not found at the expected path; the schema moved — refusing to emit a CRD that leaks it. Fix the path in generate::strip_vmpool_blockdevicerefs." >&2 + exit 1 + fi + yq -i "del(${bdr})" "$pool_crd" + yq -i 'del(.spec.versions[].schema.openAPIV3Schema.properties.spec.properties.virtualMachineTemplate.properties.spec.required[] | select(. == "blockDeviceRefs"))' "$pool_crd" + if [ "$(yq "$bdr | tag" "$pool_crd")" != "!!null" ]; then + echo "ERROR: ${pool_crd}: failed to strip blockDeviceRefs." >&2 + exit 1 + fi } WHAT=$1 diff --git a/api/subresources/register.go b/api/subresources/register.go index 872ad19a8f..e839a66c17 100644 --- a/api/subresources/register.go +++ b/api/subresources/register.go @@ -59,6 +59,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { &VirtualMachineCancelEvacuation{}, &VirtualMachineAddResourceClaim{}, &VirtualMachineRemoveResourceClaim{}, + &VirtualMachinePool{}, + &VirtualMachinePoolScaleDownWith{}, ) return nil } diff --git a/api/subresources/types.go b/api/subresources/types.go index 90ce98abaf..7b416efd3a 100644 --- a/api/subresources/types.go +++ b/api/subresources/types.go @@ -109,3 +109,19 @@ type VirtualMachineRemoveResourceClaim struct { Name string DryRun []string } + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type VirtualMachinePool struct { + metav1.TypeMeta + metav1.ObjectMeta +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type VirtualMachinePoolScaleDownWith struct { + metav1.TypeMeta + + Targets []string + DryRun []string +} diff --git a/api/subresources/v1alpha2/register.go b/api/subresources/v1alpha2/register.go index d978d3f4f3..39da230e19 100644 --- a/api/subresources/v1alpha2/register.go +++ b/api/subresources/v1alpha2/register.go @@ -61,6 +61,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { &VirtualMachineCancelEvacuation{}, &VirtualMachineAddResourceClaim{}, &VirtualMachineRemoveResourceClaim{}, + &VirtualMachinePool{}, + &VirtualMachinePoolScaleDownWith{}, ) return nil } diff --git a/api/subresources/v1alpha2/types.go b/api/subresources/v1alpha2/types.go index 2bfcbcedbc..7339f7a5c3 100644 --- a/api/subresources/v1alpha2/types.go +++ b/api/subresources/v1alpha2/types.go @@ -117,3 +117,22 @@ type VirtualMachineRemoveResourceClaim struct { Name string `json:"name"` DryRun []string `json:"dryRun,omitempty"` } + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type VirtualMachinePool struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:conversion-gen:explicit-from=net/url.Values + +type VirtualMachinePoolScaleDownWith struct { + metav1.TypeMeta `json:",inline"` + + // Targets are the names of the pool member VirtualMachines to remove. + Targets []string `json:"targets"` + + DryRun []string `json:"dryRun,omitempty"` +} diff --git a/api/subresources/v1alpha2/zz_generated.conversion.go b/api/subresources/v1alpha2/zz_generated.conversion.go index 7955b3f836..7f21f8d085 100644 --- a/api/subresources/v1alpha2/zz_generated.conversion.go +++ b/api/subresources/v1alpha2/zz_generated.conversion.go @@ -98,6 +98,26 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*VirtualMachinePool)(nil), (*subresources.VirtualMachinePool)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_VirtualMachinePool_To_subresources_VirtualMachinePool(a.(*VirtualMachinePool), b.(*subresources.VirtualMachinePool), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*subresources.VirtualMachinePool)(nil), (*VirtualMachinePool)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_subresources_VirtualMachinePool_To_v1alpha2_VirtualMachinePool(a.(*subresources.VirtualMachinePool), b.(*VirtualMachinePool), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*VirtualMachinePoolScaleDownWith)(nil), (*subresources.VirtualMachinePoolScaleDownWith)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha2_VirtualMachinePoolScaleDownWith_To_subresources_VirtualMachinePoolScaleDownWith(a.(*VirtualMachinePoolScaleDownWith), b.(*subresources.VirtualMachinePoolScaleDownWith), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*subresources.VirtualMachinePoolScaleDownWith)(nil), (*VirtualMachinePoolScaleDownWith)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_subresources_VirtualMachinePoolScaleDownWith_To_v1alpha2_VirtualMachinePoolScaleDownWith(a.(*subresources.VirtualMachinePoolScaleDownWith), b.(*VirtualMachinePoolScaleDownWith), 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 { @@ -173,6 +193,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*url.Values)(nil), (*VirtualMachinePoolScaleDownWith)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_url_Values_To_v1alpha2_VirtualMachinePoolScaleDownWith(a.(*url.Values), b.(*VirtualMachinePoolScaleDownWith), 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 { @@ -468,6 +493,69 @@ func Convert_url_Values_To_v1alpha2_VirtualMachineFreeze(in *url.Values, out *Vi return autoConvert_url_Values_To_v1alpha2_VirtualMachineFreeze(in, out, s) } +func autoConvert_v1alpha2_VirtualMachinePool_To_subresources_VirtualMachinePool(in *VirtualMachinePool, out *subresources.VirtualMachinePool, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + return nil +} + +// Convert_v1alpha2_VirtualMachinePool_To_subresources_VirtualMachinePool is an autogenerated conversion function. +func Convert_v1alpha2_VirtualMachinePool_To_subresources_VirtualMachinePool(in *VirtualMachinePool, out *subresources.VirtualMachinePool, s conversion.Scope) error { + return autoConvert_v1alpha2_VirtualMachinePool_To_subresources_VirtualMachinePool(in, out, s) +} + +func autoConvert_subresources_VirtualMachinePool_To_v1alpha2_VirtualMachinePool(in *subresources.VirtualMachinePool, out *VirtualMachinePool, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + return nil +} + +// Convert_subresources_VirtualMachinePool_To_v1alpha2_VirtualMachinePool is an autogenerated conversion function. +func Convert_subresources_VirtualMachinePool_To_v1alpha2_VirtualMachinePool(in *subresources.VirtualMachinePool, out *VirtualMachinePool, s conversion.Scope) error { + return autoConvert_subresources_VirtualMachinePool_To_v1alpha2_VirtualMachinePool(in, out, s) +} + +func autoConvert_v1alpha2_VirtualMachinePoolScaleDownWith_To_subresources_VirtualMachinePoolScaleDownWith(in *VirtualMachinePoolScaleDownWith, out *subresources.VirtualMachinePoolScaleDownWith, s conversion.Scope) error { + out.Targets = *(*[]string)(unsafe.Pointer(&in.Targets)) + out.DryRun = *(*[]string)(unsafe.Pointer(&in.DryRun)) + return nil +} + +// Convert_v1alpha2_VirtualMachinePoolScaleDownWith_To_subresources_VirtualMachinePoolScaleDownWith is an autogenerated conversion function. +func Convert_v1alpha2_VirtualMachinePoolScaleDownWith_To_subresources_VirtualMachinePoolScaleDownWith(in *VirtualMachinePoolScaleDownWith, out *subresources.VirtualMachinePoolScaleDownWith, s conversion.Scope) error { + return autoConvert_v1alpha2_VirtualMachinePoolScaleDownWith_To_subresources_VirtualMachinePoolScaleDownWith(in, out, s) +} + +func autoConvert_subresources_VirtualMachinePoolScaleDownWith_To_v1alpha2_VirtualMachinePoolScaleDownWith(in *subresources.VirtualMachinePoolScaleDownWith, out *VirtualMachinePoolScaleDownWith, s conversion.Scope) error { + out.Targets = *(*[]string)(unsafe.Pointer(&in.Targets)) + out.DryRun = *(*[]string)(unsafe.Pointer(&in.DryRun)) + return nil +} + +// Convert_subresources_VirtualMachinePoolScaleDownWith_To_v1alpha2_VirtualMachinePoolScaleDownWith is an autogenerated conversion function. +func Convert_subresources_VirtualMachinePoolScaleDownWith_To_v1alpha2_VirtualMachinePoolScaleDownWith(in *subresources.VirtualMachinePoolScaleDownWith, out *VirtualMachinePoolScaleDownWith, s conversion.Scope) error { + return autoConvert_subresources_VirtualMachinePoolScaleDownWith_To_v1alpha2_VirtualMachinePoolScaleDownWith(in, out, s) +} + +func autoConvert_url_Values_To_v1alpha2_VirtualMachinePoolScaleDownWith(in *url.Values, out *VirtualMachinePoolScaleDownWith, s conversion.Scope) error { + // WARNING: Field TypeMeta does not have json tag, skipping. + + if values, ok := map[string][]string(*in)["targets"]; ok && len(values) > 0 { + out.Targets = *(*[]string)(unsafe.Pointer(&values)) + } else { + out.Targets = nil + } + if values, ok := map[string][]string(*in)["dryRun"]; ok && len(values) > 0 { + out.DryRun = *(*[]string)(unsafe.Pointer(&values)) + } else { + out.DryRun = nil + } + return nil +} + +// Convert_url_Values_To_v1alpha2_VirtualMachinePoolScaleDownWith is an autogenerated conversion function. +func Convert_url_Values_To_v1alpha2_VirtualMachinePoolScaleDownWith(in *url.Values, out *VirtualMachinePoolScaleDownWith, s conversion.Scope) error { + return autoConvert_url_Values_To_v1alpha2_VirtualMachinePoolScaleDownWith(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 diff --git a/api/subresources/v1alpha2/zz_generated.deepcopy.go b/api/subresources/v1alpha2/zz_generated.deepcopy.go index 6554e97509..e83a39f0f3 100644 --- a/api/subresources/v1alpha2/zz_generated.deepcopy.go +++ b/api/subresources/v1alpha2/zz_generated.deepcopy.go @@ -192,6 +192,67 @@ func (in *VirtualMachineFreeze) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VirtualMachinePool) DeepCopyInto(out *VirtualMachinePool) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualMachinePool. +func (in *VirtualMachinePool) DeepCopy() *VirtualMachinePool { + if in == nil { + return nil + } + out := new(VirtualMachinePool) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VirtualMachinePool) 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 *VirtualMachinePoolScaleDownWith) DeepCopyInto(out *VirtualMachinePoolScaleDownWith) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.Targets != nil { + in, out := &in.Targets, &out.Targets + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.DryRun != nil { + in, out := &in.DryRun, &out.DryRun + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualMachinePoolScaleDownWith. +func (in *VirtualMachinePoolScaleDownWith) DeepCopy() *VirtualMachinePoolScaleDownWith { + if in == nil { + return nil + } + out := new(VirtualMachinePoolScaleDownWith) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VirtualMachinePoolScaleDownWith) 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 diff --git a/api/subresources/zz_generated.deepcopy.go b/api/subresources/zz_generated.deepcopy.go index 8268bde57f..dc9fa4f7a8 100644 --- a/api/subresources/zz_generated.deepcopy.go +++ b/api/subresources/zz_generated.deepcopy.go @@ -192,6 +192,67 @@ func (in *VirtualMachineFreeze) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VirtualMachinePool) DeepCopyInto(out *VirtualMachinePool) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualMachinePool. +func (in *VirtualMachinePool) DeepCopy() *VirtualMachinePool { + if in == nil { + return nil + } + out := new(VirtualMachinePool) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VirtualMachinePool) 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 *VirtualMachinePoolScaleDownWith) DeepCopyInto(out *VirtualMachinePoolScaleDownWith) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.Targets != nil { + in, out := &in.Targets, &out.Targets + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.DryRun != nil { + in, out := &in.DryRun, &out.DryRun + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualMachinePoolScaleDownWith. +func (in *VirtualMachinePoolScaleDownWith) DeepCopy() *VirtualMachinePoolScaleDownWith { + if in == nil { + return nil + } + out := new(VirtualMachinePoolScaleDownWith) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VirtualMachinePoolScaleDownWith) 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 diff --git a/crds/doc-ru-virtualmachinepools.yaml b/crds/doc-ru-virtualmachinepools.yaml new file mode 100644 index 0000000000..d524880082 --- /dev/null +++ b/crds/doc-ru-virtualmachinepools.yaml @@ -0,0 +1,97 @@ +spec: + versions: + - name: v1alpha2 + schema: + openAPIV3Schema: + description: | + VirtualMachinePool декларативно управляет группой одинаковых виртуальных машин: поддерживает заданное число реплик, масштабируется через стандартный сабресурс `scale` и переиспользует «тяжёлые» диски между поколениями реплик. + + Ресурс доступен только в платных редакциях (EE/SE+) и включается feature gate `VirtualMachinePool` модуля. + properties: + spec: + description: | + Желаемое состояние пула виртуальных машин. + properties: + replicas: + description: | + Желаемое число виртуальных машин в пуле. + + Поле пишет только его владелец — автоскейлер или человек через сабресурс `scale`, либо обработчик адресного сжатия. Контроллер его не изменяет. Границы удерживает автоскейлер; жёсткий потолок — ResourceQuota пространства имён. + scaleDownPolicy: + description: | + Определяет, какая реплика удаляется при безадресном сжатии пула через сабресурс `scale`. Поле обязательно и не имеет значения по умолчанию — выбор делается осознанно. + + * `NewestFirst` — безадресное сжатие разрешено; первыми удаляются самые молодые реплики (с наименьшим накопленным состоянием). + * `OldestFirst` — безадресное сжатие разрешено; первыми удаляются самые старые реплики (быстрая ротация). + * `Explicit` — безадресное сжатие через `scale` отклоняется вебхуком; реплики убираются только по имени. Для «занятых» нагрузок — раннеров CI и VDI. + virtualMachineTemplate: + description: | + Шаблон, из которого создаётся каждая реплика. Его `spec` — это обычный VirtualMachineSpec, поэтому реплика ничем не отличается от вручную созданной виртуальной машины. + properties: + metadata: + description: | + Метаданные, применяемые к каждой реплике. Произвольные пользовательские метки и аннотации разрешены; управляемые метки пула контроллер добавляет поверх них. + properties: + labels: + description: | + Метки, добавляемые каждой реплике. + annotations: + description: | + Аннотации, добавляемые каждой реплике. + spec: + description: | + Спецификация виртуальной машины, из которой создаётся каждая реплика (обычный VirtualMachineSpec, см. [VirtualMachine](cr.html#virtualmachine)). + + Per-replica диски указываются в `blockDeviceRefs` по имени своего шаблона из `virtualDiskTemplates` — так задаётся полный список устройств и порядок загрузки, как в обычной виртуальной машине. + virtualDiskTemplates: + description: | + Описание дисков на каждую реплику. Диск с политикой возврата `Delete` принадлежит своей виртуальной машине и удаляется вместе с ней; диск с политикой `Retain` принадлежит пулу, переживает реплику и переиспользуется при последующем scale-up. + items: + properties: + name: + description: | + Имя шаблона диска в пуле. DNS-1123 label (без точек), так как встраивается в имена VirtualDisk. + reclaim: + description: | + Управляет судьбой диска при удалении его реплики. + properties: + onScaleDown: + description: | + `Delete` (по умолчанию) — диск удаляется вместе с репликой; `Retain` — диск сохраняется и переиспользуется. + keep: + description: | + Сколько свободных (`Retain`) дисков всегда держать «тёплыми» для быстрого scale-up; они иммунны к `ttl`. Имеет смысл только с `Retain`. + ttl: + description: | + Время жизни свободного диска сверх «тёплого» буфера, после которого он удаляется сборщиком мусора. Имеет смысл только с `Retain`. + spec: + description: | + Спецификация диска (обычный VirtualDiskSpec, см. [VirtualDisk](cr.html#virtualdisk)). + status: + description: | + Наблюдаемое состояние пула виртуальных машин. + properties: + observedGeneration: + description: | + Поколение спецификации, обработанное контроллером. + replicas: + description: | + Число существующих членов пула, включая находящиеся в состоянии `Terminating`: такая машина всё ещё занимает ресурсы, поэтому это реальная ёмкость. + readyReplicas: + description: | + Число членов, готовых обслуживать нагрузку (без учёта `Terminating`). + desiredTemplateHash: + description: | + Хэш текущего `virtualMachineTemplate` — ревизия, к которой контроллер приводит реплики. + updatedReplicas: + description: | + Число реплик, фактически находящихся на ревизии `desiredTemplateHash` (полностью синхронизированных). + restartPendingReplicas: + description: | + Число реплик, спека которых уже приведена к новому шаблону, но применение его disruptive-части ждёт перезапуска. + selector: + description: | + Строковый селектор меток, публикуемый контроллером для сабресурса `scale`; HPA/KEDA читают его самостоятельно. + conditions: + description: | + Условия, описывающие текущее состояние пула. diff --git a/crds/virtualmachinepools.yaml b/crds/virtualmachinepools.yaml new file mode 100644 index 0000000000..a88672b8ea --- /dev/null +++ b/crds/virtualmachinepools.yaml @@ -0,0 +1,1571 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + labels: + heritage: deckhouse + module: virtualization + name: virtualmachinepools.virtualization.deckhouse.io +spec: + group: virtualization.deckhouse.io + names: + categories: + - virtualization + kind: VirtualMachinePool + listKind: VirtualMachinePoolList + plural: virtualmachinepools + shortNames: + - vmpool + - vmpools + singular: virtualmachinepool + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Current number of pool members (including Terminating). + jsonPath: .status.replicas + name: Replicas + type: integer + - description: Number of members ready to serve. + jsonPath: .status.readyReplicas + name: Ready + type: integer + - description: Time of resource creation. + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: |- + VirtualMachinePool declaratively manages a group of identical virtual machines: + it keeps the requested number of replicas, scales via the standard `scale` + subresource, and reuses "heavy" disks across replica generations. + + The resource is available only in paid editions (EE/SE+) and is gated behind + the `VirtualMachinePool` module feature gate. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: VirtualMachinePoolSpec is the desired state of a VirtualMachinePool. + properties: + replicas: + description: |- + Replicas is the desired number of virtual machines in the pool. + + The field is written only by its owner — an autoscaler or a human via the + `scale` subresource, or by the addressed scale-down handler. The controller + never writes it. Bounds are held by the autoscaler; the hard ceiling is the + namespace ResourceQuota. + format: int32 + minimum: 0 + type: integer + scaleDownPolicy: + description: |- + ScaleDownPolicy chooses how a replica is picked when the pool is scaled down + anonymously through the `scale` subresource. It is required and has no + default, forcing a conscious choice between "any replica may be killed" and + "only addressed removal is allowed". + + - `NewestFirst` — anonymous scale-down is allowed; the youngest replicas + (least accumulated state) are removed first. + - `OldestFirst` — anonymous scale-down is allowed; the oldest replicas are + removed first (faster rotation). + - `Explicit` — anonymous scale-down through `scale` is rejected by a + webhook; replicas can be removed only by address. For "busy" workloads + such as CI runners and VDI. + enum: + - NewestFirst + - OldestFirst + - Explicit + type: string + virtualDiskTemplates: + description: |- + VirtualDiskTemplates describes the per-replica disks and, by their order, the + replica's block devices — the first template is the boot device. A disk with + reclaim Delete belongs to its VirtualMachine and is removed with it; a disk + with reclaim Retain belongs to the pool, outlives the replica and is reused + on a later scale-up. This is the sole source of a replica's disks: the pool's + virtualMachineTemplate has no blockDeviceRefs field — the controller builds it + from these templates. + items: + description: VirtualDiskTemplateSpec describes a per-replica disk. + properties: + name: + description: |- + Name identifies the disk template within the pool. It is a DNS-1123 label + (no dots), because it is embedded into VirtualDisk names. + maxLength: 63 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + reclaim: + description: Reclaim controls what happens to the disk when its replica is removed. + properties: + keep: + default: 0 + description: |- + Keep is the number of free (Retain) disks always kept warm for fast + scale-up; these are immune to the ttl. Only meaningful with Retain. + format: int32 + minimum: 0 + type: integer + onScaleDown: + default: Delete + description: OnScaleDown is Delete (default) or Retain. + enum: + - Delete + - Retain + type: string + ttl: + description: |- + TTL is how long a free disk lives beyond the warm buffer before it is + garbage-collected. Only meaningful with Retain. + type: string + type: object + x-kubernetes-validations: + - message: "keep and ttl are only valid with onScaleDown: Retain" + rule: self.onScaleDown == 'Retain' || (self.keep == 0 && !has(self.ttl)) + - message: keep requires ttl; without ttl free disks are never garbage-collected, so keep would have no effect + rule: self.keep == 0 || has(self.ttl) + spec: + description: Spec is the desired state of the disk (an ordinary VirtualDiskSpec). + properties: + dataSource: + properties: + containerImage: + description: Use an image stored in an external container registry. Only registries with enabled TLS are supported. To provide a custom Certificate Authority (CA) chain, use the `caBundle` field. + properties: + caBundle: + description: CA chain in Base64 format to verify the container registry. + example: YWFhCg== + format: byte + type: string + image: + description: Path to the image in the container registry. + example: registry.example.com/images/slackware:15 + pattern: ^(?P(?:(?P(?:(?:localhost|[\w-]+(?:\.[\w-]+)+)(?::\d+)?)|[\w]+:\d+)/)?(?P[a-z0-9_.-]+(?:/[a-z0-9_.-]+)*))(?::(?P[\w][\w.-]{0,127}))?(?:@(?P[A-Za-z][A-Za-z0-9]*(?:[+.-_][A-Za-z][A-Za-z0-9]*)*:[0-9a-fA-F]{32,}))?$ + type: string + imagePullSecret: + properties: + name: + description: Name of the secret keeping container registry credentials, which must be located in the same namespace. + type: string + type: object + required: + - image + type: object + http: + description: |- + Fill the image with data from an external URL. The following schemas are supported: + + * HTTP + * HTTPS + + For HTTPS schema, there is an option to skip the TLS verification. + properties: + caBundle: + description: CA chain in Base64 format to verify the URL. + example: YWFhCg== + format: byte + type: string + checksum: + description: Checksum to verify integrity and consistency of the downloaded file. The file must match all specified checksums. + properties: + md5: + example: f3b59bed9f91e32fac1210184fcff6f5 + maxLength: 32 + minLength: 32 + pattern: ^[0-9a-fA-F]{32}$ + type: string + sha256: + example: 78be890d71dde316c412da2ce8332ba47b9ce7a29d573801d2777e01aa20b9b5 + maxLength: 64 + minLength: 64 + pattern: ^[0-9a-fA-F]{64}$ + type: string + type: object + url: + description: |- + URL of the file for creating an image. The following file formats are supported: + * qcow2 + * vmdk + * vdi + * iso + * raw + The file can be compressed into an archive in one of the following formats: + * gz + * xz + example: https://mirror.example.com/images/slackware-15.qcow.gz + pattern: ^http[s]?:\/\/(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+$ + type: string + required: + - url + type: object + objectRef: + description: Use an existing VirtualImage, ClusterVirtualImage, or VirtualDiskSnapshot resource to create a disk. + properties: + kind: + description: Kind of the existing VirtualImage, ClusterVirtualImage, or VirtualDiskSnapshot resource. + enum: + - ClusterVirtualImage + - VirtualImage + - VirtualDiskSnapshot + type: string + name: + description: Name of the existing VirtualImage, ClusterVirtualImage, or VirtualDiskSnapshot resource. + minLength: 1 + type: string + required: + - kind + - name + type: object + type: + description: |- + The following image sources are available for creating an image: + + * `HTTP`: From a file published on an HTTP/HTTPS service at a given URL. + * `ContainerImage`: From another image stored in a container registry. + * `ObjectRef`: From an existing resource. + * `Upload`: From data uploaded by the user via a special interface. + enum: + - HTTP + - ContainerImage + - ObjectRef + - Upload + type: string + type: object + x-kubernetes-validations: + - message: HTTP requires http and cannot have ContainerImage or ObjectRef. + rule: "self.type == 'HTTP' ? has(self.http) && !has(self.containerImage) && !has(self.objectRef) : true" + - message: ContainerImage requires containerImage and cannot have HTTP or ObjectRef. + rule: "self.type == 'ContainerImage' ? has(self.containerImage) && !has(self.http) && !has(self.objectRef) : true" + - message: ObjectRef requires objectRef and cannot have HTTP or ContainerImage. + rule: "self.type == 'ObjectRef' ? has(self.objectRef) && !has(self.http) && !has(self.containerImage) : true" + persistentVolumeClaim: + description: Settings for creating PVCs to store the disk. + properties: + size: + anyOf: + - type: integer + - type: string + description: |- + Desired size for PVC to store the disk. If the disk is created from an image, the size must be at least as large as the original unpacked image. + + This parameter can be omitted if the `.spec.dataSource` section is filled out. In this case, the controller will determine the disk size automatically, based on the size of the extracted image from the source specified in `.spec.dataSource`. + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + storageClassName: + description: |- + StorageClass name required by the claim. For details on using StorageClass for PVC, refer to https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1. + + When creating disks, the user can specify the required StorageClass. If not specified, the default StorageClass will be used. + + The disk features and virtual machine behavior depend on the selected StorageClass. + + The `VolumeBindingMode` parameter in the StorageClass affects the disk creation process. The following values are allowed: + - `Immediate`: The disk will be created and becomes available for use immediately after creation. + - `WaitForFirstConsumer`: The disk will be created when first used on the node where the virtual machine will be started. + + StorageClass supports multiple storage settings: + - Creating a block device (`Block`) or file system (`FileSystem`). + - Multiple access (`ReadWriteMany`) or single access (`ReadWriteOnce`). The `ReadWriteMany` disks support multiple access, which enables a "live" migration of virtual machines. In contrast, the `ReadWriteOnce` disks, which can be accessed from only one node, don't have this feature. + + For known storage types, Deckhouse automatically determines the most efficient settings when creating disks (by priority, in descending order): + 1. `Block` + `ReadWriteMany` + 2. `FileSystem` + `ReadWriteMany` + 3. `Block` + `ReadWriteOnce` + 4. `FileSystem` + `ReadWriteOnce` + type: string + type: object + type: object + required: + - name + - spec + type: object + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + virtualMachineTemplate: + description: |- + VirtualMachineTemplate is the template every replica is stamped from. Its + `spec` is an ordinary VirtualMachineSpec, so a replica is no different from a + manually created virtual machine. + properties: + metadata: + description: |- + Metadata applied to every replica. Arbitrary user labels and annotations are + allowed; the controller adds its managed pool labels on top. A curated + struct (not the full ObjectMeta) so the CRD schema exposes labels and + annotations instead of an opaque object. + properties: + annotations: + additionalProperties: + type: string + type: object + labels: + additionalProperties: + type: string + type: object + type: object + spec: + description: Spec of the virtual machine that backs each replica. + properties: + affinity: + description: |- + VMAffinity [The same](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity) as in the pods `spec.affinity` parameter in Kubernetes; + + The affinity setting is completely similar to the above documentation, the only difference is in the names of some parameters. In fact, the following analogs are used: + * podAffinity -> virtualMachineAndPodAffinity + * podAffinityTerm -> virtualMachineAndPodAffinityTerm + properties: + nodeAffinity: + description: Node affinity is a group of node affinity scheduling rules. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements by node's fields. + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements by node's fields. + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + virtualMachineAndPodAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + virtualMachineAndPodAffinityTerm: + description: Required. A vm affinity term, associated with the corresponding weight. + properties: + labelSelector: + description: |- + A label selector is a label query over a set of resources. The result of matchLabels and + matchExpressions are ANDed. An empty label selector matches all objects. A null + label selector matches no objects. + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + matchLabelKeys: + items: + type: string + type: array + mismatchLabelKeys: + items: + type: string + type: array + namespaceSelector: + description: |- + A label selector is a label query over a set of resources. The result of matchLabels and + matchExpressions are ANDed. An empty label selector matches all objects. A null + label selector matches no objects. + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding vmAndPodAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - virtualMachineAndPodAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + description: |- + A label selector is a label query over a set of resources. The result of matchLabels and + matchExpressions are ANDed. An empty label selector matches all objects. A null + label selector matches no objects. + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + matchLabelKeys: + items: + type: string + type: array + mismatchLabelKeys: + items: + type: string + type: array + namespaceSelector: + description: |- + A label selector is a label query over a set of resources. The result of matchLabels and + matchExpressions are ANDed. An empty label selector matches all objects. A null + label selector matches no objects. + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + virtualMachineAndPodAntiAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + virtualMachineAndPodAffinityTerm: + description: Required. A vm affinity term, associated with the corresponding weight. + properties: + labelSelector: + description: |- + A label selector is a label query over a set of resources. The result of matchLabels and + matchExpressions are ANDed. An empty label selector matches all objects. A null + label selector matches no objects. + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + matchLabelKeys: + items: + type: string + type: array + mismatchLabelKeys: + items: + type: string + type: array + namespaceSelector: + description: |- + A label selector is a label query over a set of resources. The result of matchLabels and + matchExpressions are ANDed. An empty label selector matches all objects. A null + label selector matches no objects. + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding vmAndPodAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - virtualMachineAndPodAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + description: |- + A label selector is a label query over a set of resources. The result of matchLabels and + matchExpressions are ANDed. An empty label selector matches all objects. A null + label selector matches no objects. + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + matchLabelKeys: + items: + type: string + type: array + mismatchLabelKeys: + items: + type: string + type: array + namespaceSelector: + description: |- + A label selector is a label query over a set of resources. The result of matchLabels and + matchExpressions are ANDed. An empty label selector matches all objects. A null + label selector matches no objects. + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + namespaces: + items: + type: string + type: array + topologyKey: + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object + bootloader: + default: BIOS + description: |- + The BootloaderType defines bootloader for VM. + * BIOS - use legacy BIOS. + * EFI - use Unified Extensible Firmware (EFI/UEFI). + * EFIWithSecureBoot - use UEFI/EFI with SecureBoot support. + enum: + - BIOS + - EFI + - EFIWithSecureBoot + type: string + cpu: + description: CPUSpec specifies the CPU settings for the VM. + properties: + coreFraction: + description: |- + Guaranteed share of CPU that will be allocated to the VM. Specified as a percentage. + The range of available values is defined in the VirtualMachineClass sizing policy. + If not specified, the default value from the VirtualMachineClass will be used. + pattern: ^(100|[1-9][0-9]?|[1-9])%$ + type: string + cores: + description: Specifies the number of cores inside the VM. The value must be greater or equal 1. + format: int32 + minimum: 1 + type: integer + required: + - cores + type: object + disruptions: + default: + restartApprovalMode: Manual + description: |- + Disruptions describes the policy for applying changes that require rebooting the VM + Changes to some VM configuration settings require a reboot of the VM to apply them. This policy allows you to specify the behavior of how the VM will respond to such changes. + properties: + restartApprovalMode: + description: "RestartApprovalMode defines a restart approving mode: Manual or Automatic." + enum: + - Manual + - Automatic + type: string + type: object + enableParavirtualization: + default: true + description: |- + Use the `virtio` bus to connect virtual devices of the VM. Set false to disable `virtio` for this VM. + Note: To use paravirtualization mode, some operating systems require the appropriate drivers to be installed. + type: boolean + liveMigrationPolicy: + description: Live migration policy type. + enum: + - Manual + - Never + - AlwaysSafe + - PreferSafe + - AlwaysForced + - PreferForced + type: string + memory: + description: MemorySpec specifies the memory settings for the VM. + properties: + size: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - size + type: object + networks: + items: + properties: + id: + type: integer + name: + type: string + type: + type: string + virtualMachineMACAddressName: + type: string + required: + - type + type: object + type: array + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector must match a node's labels for the VM to be scheduled on that node. + [The same](https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes//) as in the pods `spec.nodeSelector` parameter in Kubernetes. + type: object + osType: + default: Generic + description: |- + The OsType parameter allows you to select the type of used OS, for which a VM with an optimal set of required virtual devices and parameters will be created. + + * Windows - for Microsoft Windows family operating systems. + * Generic - for other types of OS. + enum: + - Windows + - Generic + type: string + priorityClassName: + description: PriorityClassName [The same](https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/) as in the pods `spec.priorityClassName` parameter in Kubernetes. + type: string + provisioning: + description: Provisioning is a block allows you to configure the provisioning script for the VM. + properties: + sysprepRef: + description: |- + SysprepRef is reference to an existing Windows sysprep automation. + Resource structure for the SysprepRef type: + * `.data.autounattend.xml`. + * `.data.unattend.xml`. + properties: + kind: + default: Secret + description: |- + The kind of existing Windows sysprep automation resource. + The following options are supported: + - Secret + enum: + - Secret + type: string + name: + type: string + required: + - name + type: object + type: + description: |- + ProvisioningType parameter defines the type of provisioning script: + + Parameters supported for using the provisioning script: + * UserData - use the cloud-init in the .spec.provisioning.UserData section. + * UserDataRef - use a cloud-init script that resides in a different resource. + * SysprepRef - Use a Windows Automation script that resides in a different resource. + More information: https://cloudinit.readthedocs.io/en/latest/reference/examples.html + type: string + userData: + description: Inline cloud-init userdata script. + type: string + userDataRef: + description: |- + UserDataRef is reference to an existing resource with a cloud-init script. + Resource structure for userDataRef type: + * `.data.userData`. + properties: + kind: + default: Secret + description: |- + The kind of existing cloud-init automation resource. + The following options are supported: + - Secret + enum: + - Secret + type: string + name: + type: string + required: + - name + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: UserData cannot have userDataRef or sysprepRef. + rule: "self.type == 'UserData' ? has(self.userData) && !has(self.userDataRef) && !has(self.sysprepRef) : true" + - message: UserDataRef cannot have userData or sysprepRef. + rule: "self.type == 'UserDataRef' ? has(self.userDataRef) && !has(self.userData) && !has(self.sysprepRef) : true" + - message: SysprepRef cannot have userData or userDataRef. + rule: "self.type == 'SysprepRef' ? has(self.sysprepRef) && !has(self.userData) && !has(self.userDataRef) : true" + runPolicy: + default: AlwaysOnUnlessStoppedManually + description: |- + RunPolicy parameter defines the VM startup policy + * `AlwaysOn` - after creation the VM is always in a running state, even in case of its shutdown by OS means. + * `AlwaysOff` - after creation the VM is always in the off state. + * `Manual` - after creation the VM is switched off, the VM state (switching on/off) is controlled via sub-resources or OS means. + * `AlwaysOnUnlessStoppedManually` - after creation the VM is always in a running state. The VM can be shutdown by means of the OS or use the d8 utility: `d8 v stop `. + enum: + - AlwaysOn + - AlwaysOff + - Manual + - AlwaysOnUnlessStoppedManually + type: string + terminationGracePeriodSeconds: + default: 60 + description: Grace period observed after signalling a VM to stop after which the VM is force terminated. + format: int64 + type: integer + tolerations: + description: |- + Tolerations define rules to tolerate node taints. + The same](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) as in the pods `spec.tolerations` parameter in Kubernetes. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + topologySpreadConstraints: + items: + description: TopologySpreadConstraint specifies how to spread matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + usbDevices: + description: |- + List of USB devices to attach to the virtual machine. + Devices are referenced by name of USBDevice resource in the same namespace. + items: + description: USBDeviceSpecRef references a USB device by name. + properties: + name: + description: The name of USBDevice resource in the same namespace. + type: string + required: + - name + type: object + maxItems: 8 + type: array + virtualMachineClassName: + description: Name of the `VirtualMachineClass` resource describing the requirements for a virtual CPU, memory and the resource allocation policy and node placement policies for virtual machines. + type: string + virtualMachineIPAddressName: + description: |- + Name for the associated `virtualMachineIPAddress` resource. + Specified when it is necessary to use a previously created IP address of the VM. + If not explicitly specified, by default a `virtualMachineIPAddress` resource is created for the VM with a name similar to the VM resource (`.metadata.name`). + type: string + required: + - cpu + - liveMigrationPolicy + - memory + - virtualMachineClassName + type: object + type: object + required: + - scaleDownPolicy + - virtualDiskTemplates + - virtualMachineTemplate + type: object + status: + description: VirtualMachinePoolStatus is the observed state of a VirtualMachinePool. + properties: + conditions: + description: Conditions describe the current state of the pool. + items: + description: Condition contains details for one aspect of the current state of this API Resource. + 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. + 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 + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + desiredTemplateHash: + description: |- + DesiredTemplateHash is the hash of the current virtualMachineTemplate — the + revision the controller is converging replicas to (cf. updateRevision on a + StatefulSet). + type: string + observedGeneration: + description: ObservedGeneration is the generation of the spec the controller has processed. + format: int64 + type: integer + readyReplicas: + description: ReadyReplicas is the number of members ready to serve (Terminating excluded). + format: int32 + type: integer + replicas: + description: |- + Replicas is the number of existing members, including those in Terminating: + such a machine still occupies resources, so it is real capacity, not a phantom. + format: int32 + type: integer + restartPendingReplicas: + description: |- + RestartPendingReplicas is the number of replicas patched to the new template + whose disruptive part still awaits a restart. + format: int32 + type: integer + selector: + description: |- + Selector is the label selector the controller publishes for the `scale` + subresource; HPA/KEDA read it themselves. + type: string + updatedReplicas: + description: |- + UpdatedReplicas is the number of replicas effectively on DesiredTemplateHash + (fully synced). + format: int32 + type: integer + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + scale: + labelSelectorPath: .status.selector + specReplicasPath: .spec.replicas + statusReplicasPath: .status.replicas + status: {} diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md index ae584d9d62..8c73c276f3 100644 --- a/docs/USER_GUIDE.md +++ b/docs/USER_GUIDE.md @@ -3003,6 +3003,187 @@ The command cannot output data directly to the terminal. You must redirect the o After executing the command, you will receive a `debug-info.tar.gz` archive that contains all collected data in YAML format (for resources) and text files (for logs). This archive can be sent to technical support for problem analysis. +## Virtual machine pools + +{{< alert level="warning" >}} +Available in the EE and SE+ editions. Requires the `VirtualMachinePool` feature gate. +{{< /alert >}} + +A `VirtualMachinePool` maintains a requested number of identical virtual machines and lets you scale them with `kubectl scale`, an HPA, or KEDA. Its `virtualMachineTemplate.spec` is an ordinary `VirtualMachineSpec`, so a replica is no different from a manually created virtual machine. + +This functionality is disabled by default. To enable it, add `VirtualMachinePool` to the `.spec.settings.featureGates` array in the ModuleConfig `virtualization`: + +```yaml +kind: ModuleConfig +metadata: + name: virtualization +spec: + settings: + featureGates: + - VirtualMachinePool +``` + +Create a pool with the desired number of replicas and a template. Per-replica disks are declared in `virtualDiskTemplates`; their order is the replica's device (boot) order — the first template is the boot disk. The pool template has no `blockDeviceRefs` field: the controller gives each replica its own copy of every template and wires them up, so you only describe the disks once. + +```yaml +apiVersion: virtualization.deckhouse.io/v1alpha2 +kind: VirtualMachinePool +metadata: + name: runners + namespace: ci +spec: + replicas: 3 + scaleDownPolicy: NewestFirst + virtualMachineTemplate: + spec: + runPolicy: AlwaysOn + virtualMachineClassName: generic + cpu: + cores: 2 + memory: + size: 4Gi + # cloud-init: every replica self-configures on first boot (same for all). + provisioning: + type: UserData + userData: | + #cloud-config + users: + - name: cloud + sudo: ALL=(ALL) NOPASSWD:ALL + ssh_authorized_keys: + - ssh-ed25519 AAAAC3Nz... user@example + # Per-replica disks, in device order — the first (root) is the boot disk. + virtualDiskTemplates: + # Writable root disk: one per replica, cloned from an image, removed with the replica. + - name: root + reclaim: + onScaleDown: Delete + spec: + persistentVolumeClaim: + size: 30Gi + dataSource: + type: ObjectRef + objectRef: + kind: VirtualImage + name: ubuntu + # Reusable cache: survives scale-down and is reattached on scale-up. + - name: cache + reclaim: + onScaleDown: Retain + keep: 5 + ttl: 30m + spec: + persistentVolumeClaim: + size: 50Gi +``` + +Replicas are named `-`; their disks follow the same scheme — a per-replica (`Delete`) disk is `-