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 38b2cdc05..8daf0d42d 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 @@ -37,6 +37,8 @@ type VirtualizationV1alpha2Interface interface { VirtualMachineClassesGetter VirtualMachineIPAddressesGetter VirtualMachineIPAddressLeasesGetter + VirtualMachineMACAddressesGetter + VirtualMachineMACAddressLeasesGetter VirtualMachineOperationsGetter VirtualMachineRestoresGetter VirtualMachineSnapshotsGetter @@ -83,6 +85,14 @@ func (c *VirtualizationV1alpha2Client) VirtualMachineIPAddressLeases() VirtualMa return newVirtualMachineIPAddressLeases(c) } +func (c *VirtualizationV1alpha2Client) VirtualMachineMACAddresses(namespace string) VirtualMachineMACAddressInterface { + return newVirtualMachineMACAddresses(c, namespace) +} + +func (c *VirtualizationV1alpha2Client) VirtualMachineMACAddressLeases() VirtualMachineMACAddressLeaseInterface { + return newVirtualMachineMACAddressLeases(c) +} + func (c *VirtualizationV1alpha2Client) VirtualMachineOperations(namespace string) VirtualMachineOperationInterface { return newVirtualMachineOperations(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 203ed5377..ca0360fbb 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 @@ -64,6 +64,14 @@ func (c *FakeVirtualizationV1alpha2) VirtualMachineIPAddressLeases() v1alpha2.Vi return &FakeVirtualMachineIPAddressLeases{c} } +func (c *FakeVirtualizationV1alpha2) VirtualMachineMACAddresses(namespace string) v1alpha2.VirtualMachineMACAddressInterface { + return &FakeVirtualMachineMACAddresses{c, namespace} +} + +func (c *FakeVirtualizationV1alpha2) VirtualMachineMACAddressLeases() v1alpha2.VirtualMachineMACAddressLeaseInterface { + return &FakeVirtualMachineMACAddressLeases{c} +} + func (c *FakeVirtualizationV1alpha2) VirtualMachineOperations(namespace string) v1alpha2.VirtualMachineOperationInterface { return &FakeVirtualMachineOperations{c, namespace} } diff --git a/api/client/generated/clientset/versioned/typed/core/v1alpha2/fake/fake_virtualmachinemacaddress.go b/api/client/generated/clientset/versioned/typed/core/v1alpha2/fake/fake_virtualmachinemacaddress.go new file mode 100644 index 000000000..767d7b078 --- /dev/null +++ b/api/client/generated/clientset/versioned/typed/core/v1alpha2/fake/fake_virtualmachinemacaddress.go @@ -0,0 +1,141 @@ +/* +Copyright Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeVirtualMachineMACAddresses implements VirtualMachineMACAddressInterface +type FakeVirtualMachineMACAddresses struct { + Fake *FakeVirtualizationV1alpha2 + ns string +} + +var virtualmachinemacaddressesResource = v1alpha2.SchemeGroupVersion.WithResource("virtualmachinemacaddresses") + +var virtualmachinemacaddressesKind = v1alpha2.SchemeGroupVersion.WithKind("VirtualMachineMACAddress") + +// Get takes name of the virtualMachineMACAddress, and returns the corresponding virtualMachineMACAddress object, and an error if there is any. +func (c *FakeVirtualMachineMACAddresses) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha2.VirtualMachineMACAddress, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(virtualmachinemacaddressesResource, c.ns, name), &v1alpha2.VirtualMachineMACAddress{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.VirtualMachineMACAddress), err +} + +// List takes label and field selectors, and returns the list of VirtualMachineMACAddresses that match those selectors. +func (c *FakeVirtualMachineMACAddresses) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha2.VirtualMachineMACAddressList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(virtualmachinemacaddressesResource, virtualmachinemacaddressesKind, c.ns, opts), &v1alpha2.VirtualMachineMACAddressList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha2.VirtualMachineMACAddressList{ListMeta: obj.(*v1alpha2.VirtualMachineMACAddressList).ListMeta} + for _, item := range obj.(*v1alpha2.VirtualMachineMACAddressList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested virtualMachineMACAddresses. +func (c *FakeVirtualMachineMACAddresses) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(virtualmachinemacaddressesResource, c.ns, opts)) + +} + +// Create takes the representation of a virtualMachineMACAddress and creates it. Returns the server's representation of the virtualMachineMACAddress, and an error, if there is any. +func (c *FakeVirtualMachineMACAddresses) Create(ctx context.Context, virtualMachineMACAddress *v1alpha2.VirtualMachineMACAddress, opts v1.CreateOptions) (result *v1alpha2.VirtualMachineMACAddress, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(virtualmachinemacaddressesResource, c.ns, virtualMachineMACAddress), &v1alpha2.VirtualMachineMACAddress{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.VirtualMachineMACAddress), err +} + +// Update takes the representation of a virtualMachineMACAddress and updates it. Returns the server's representation of the virtualMachineMACAddress, and an error, if there is any. +func (c *FakeVirtualMachineMACAddresses) Update(ctx context.Context, virtualMachineMACAddress *v1alpha2.VirtualMachineMACAddress, opts v1.UpdateOptions) (result *v1alpha2.VirtualMachineMACAddress, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(virtualmachinemacaddressesResource, c.ns, virtualMachineMACAddress), &v1alpha2.VirtualMachineMACAddress{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.VirtualMachineMACAddress), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeVirtualMachineMACAddresses) UpdateStatus(ctx context.Context, virtualMachineMACAddress *v1alpha2.VirtualMachineMACAddress, opts v1.UpdateOptions) (*v1alpha2.VirtualMachineMACAddress, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(virtualmachinemacaddressesResource, "status", c.ns, virtualMachineMACAddress), &v1alpha2.VirtualMachineMACAddress{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.VirtualMachineMACAddress), err +} + +// Delete takes name of the virtualMachineMACAddress and deletes it. Returns an error if one occurs. +func (c *FakeVirtualMachineMACAddresses) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(virtualmachinemacaddressesResource, c.ns, name, opts), &v1alpha2.VirtualMachineMACAddress{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeVirtualMachineMACAddresses) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(virtualmachinemacaddressesResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha2.VirtualMachineMACAddressList{}) + return err +} + +// Patch applies the patch and returns the patched virtualMachineMACAddress. +func (c *FakeVirtualMachineMACAddresses) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.VirtualMachineMACAddress, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(virtualmachinemacaddressesResource, c.ns, name, pt, data, subresources...), &v1alpha2.VirtualMachineMACAddress{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.VirtualMachineMACAddress), err +} diff --git a/api/client/generated/clientset/versioned/typed/core/v1alpha2/fake/fake_virtualmachinemacaddresslease.go b/api/client/generated/clientset/versioned/typed/core/v1alpha2/fake/fake_virtualmachinemacaddresslease.go new file mode 100644 index 000000000..856c77ebc --- /dev/null +++ b/api/client/generated/clientset/versioned/typed/core/v1alpha2/fake/fake_virtualmachinemacaddresslease.go @@ -0,0 +1,132 @@ +/* +Copyright Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + v1alpha2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeVirtualMachineMACAddressLeases implements VirtualMachineMACAddressLeaseInterface +type FakeVirtualMachineMACAddressLeases struct { + Fake *FakeVirtualizationV1alpha2 +} + +var virtualmachinemacaddressleasesResource = v1alpha2.SchemeGroupVersion.WithResource("virtualmachinemacaddressleases") + +var virtualmachinemacaddressleasesKind = v1alpha2.SchemeGroupVersion.WithKind("VirtualMachineMACAddressLease") + +// Get takes name of the virtualMachineMACAddressLease, and returns the corresponding virtualMachineMACAddressLease object, and an error if there is any. +func (c *FakeVirtualMachineMACAddressLeases) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha2.VirtualMachineMACAddressLease, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootGetAction(virtualmachinemacaddressleasesResource, name), &v1alpha2.VirtualMachineMACAddressLease{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.VirtualMachineMACAddressLease), err +} + +// List takes label and field selectors, and returns the list of VirtualMachineMACAddressLeases that match those selectors. +func (c *FakeVirtualMachineMACAddressLeases) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha2.VirtualMachineMACAddressLeaseList, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootListAction(virtualmachinemacaddressleasesResource, virtualmachinemacaddressleasesKind, opts), &v1alpha2.VirtualMachineMACAddressLeaseList{}) + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v1alpha2.VirtualMachineMACAddressLeaseList{ListMeta: obj.(*v1alpha2.VirtualMachineMACAddressLeaseList).ListMeta} + for _, item := range obj.(*v1alpha2.VirtualMachineMACAddressLeaseList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested virtualMachineMACAddressLeases. +func (c *FakeVirtualMachineMACAddressLeases) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewRootWatchAction(virtualmachinemacaddressleasesResource, opts)) +} + +// Create takes the representation of a virtualMachineMACAddressLease and creates it. Returns the server's representation of the virtualMachineMACAddressLease, and an error, if there is any. +func (c *FakeVirtualMachineMACAddressLeases) Create(ctx context.Context, virtualMachineMACAddressLease *v1alpha2.VirtualMachineMACAddressLease, opts v1.CreateOptions) (result *v1alpha2.VirtualMachineMACAddressLease, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootCreateAction(virtualmachinemacaddressleasesResource, virtualMachineMACAddressLease), &v1alpha2.VirtualMachineMACAddressLease{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.VirtualMachineMACAddressLease), err +} + +// Update takes the representation of a virtualMachineMACAddressLease and updates it. Returns the server's representation of the virtualMachineMACAddressLease, and an error, if there is any. +func (c *FakeVirtualMachineMACAddressLeases) Update(ctx context.Context, virtualMachineMACAddressLease *v1alpha2.VirtualMachineMACAddressLease, opts v1.UpdateOptions) (result *v1alpha2.VirtualMachineMACAddressLease, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateAction(virtualmachinemacaddressleasesResource, virtualMachineMACAddressLease), &v1alpha2.VirtualMachineMACAddressLease{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.VirtualMachineMACAddressLease), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeVirtualMachineMACAddressLeases) UpdateStatus(ctx context.Context, virtualMachineMACAddressLease *v1alpha2.VirtualMachineMACAddressLease, opts v1.UpdateOptions) (*v1alpha2.VirtualMachineMACAddressLease, error) { + obj, err := c.Fake. + Invokes(testing.NewRootUpdateSubresourceAction(virtualmachinemacaddressleasesResource, "status", virtualMachineMACAddressLease), &v1alpha2.VirtualMachineMACAddressLease{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.VirtualMachineMACAddressLease), err +} + +// Delete takes name of the virtualMachineMACAddressLease and deletes it. Returns an error if one occurs. +func (c *FakeVirtualMachineMACAddressLeases) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewRootDeleteActionWithOptions(virtualmachinemacaddressleasesResource, name, opts), &v1alpha2.VirtualMachineMACAddressLease{}) + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeVirtualMachineMACAddressLeases) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewRootDeleteCollectionAction(virtualmachinemacaddressleasesResource, listOpts) + + _, err := c.Fake.Invokes(action, &v1alpha2.VirtualMachineMACAddressLeaseList{}) + return err +} + +// Patch applies the patch and returns the patched virtualMachineMACAddressLease. +func (c *FakeVirtualMachineMACAddressLeases) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.VirtualMachineMACAddressLease, err error) { + obj, err := c.Fake. + Invokes(testing.NewRootPatchSubresourceAction(virtualmachinemacaddressleasesResource, name, pt, data, subresources...), &v1alpha2.VirtualMachineMACAddressLease{}) + if obj == nil { + return nil, err + } + return obj.(*v1alpha2.VirtualMachineMACAddressLease), err +} diff --git a/api/client/generated/clientset/versioned/typed/core/v1alpha2/generated_expansion.go b/api/client/generated/clientset/versioned/typed/core/v1alpha2/generated_expansion.go index a52a7ada9..eb86d1575 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 @@ -36,6 +36,10 @@ type VirtualMachineIPAddressExpansion interface{} type VirtualMachineIPAddressLeaseExpansion interface{} +type VirtualMachineMACAddressExpansion interface{} + +type VirtualMachineMACAddressLeaseExpansion interface{} + type VirtualMachineOperationExpansion interface{} type VirtualMachineRestoreExpansion interface{} diff --git a/api/client/generated/clientset/versioned/typed/core/v1alpha2/virtualmachinemacaddress.go b/api/client/generated/clientset/versioned/typed/core/v1alpha2/virtualmachinemacaddress.go new file mode 100644 index 000000000..ac4a90242 --- /dev/null +++ b/api/client/generated/clientset/versioned/typed/core/v1alpha2/virtualmachinemacaddress.go @@ -0,0 +1,195 @@ +/* +Copyright Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + "context" + "time" + + scheme "github.com/deckhouse/virtualization/api/client/generated/clientset/versioned/scheme" + v1alpha2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// VirtualMachineMACAddressesGetter has a method to return a VirtualMachineMACAddressInterface. +// A group's client should implement this interface. +type VirtualMachineMACAddressesGetter interface { + VirtualMachineMACAddresses(namespace string) VirtualMachineMACAddressInterface +} + +// VirtualMachineMACAddressInterface has methods to work with VirtualMachineMACAddress resources. +type VirtualMachineMACAddressInterface interface { + Create(ctx context.Context, virtualMachineMACAddress *v1alpha2.VirtualMachineMACAddress, opts v1.CreateOptions) (*v1alpha2.VirtualMachineMACAddress, error) + Update(ctx context.Context, virtualMachineMACAddress *v1alpha2.VirtualMachineMACAddress, opts v1.UpdateOptions) (*v1alpha2.VirtualMachineMACAddress, error) + UpdateStatus(ctx context.Context, virtualMachineMACAddress *v1alpha2.VirtualMachineMACAddress, opts v1.UpdateOptions) (*v1alpha2.VirtualMachineMACAddress, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha2.VirtualMachineMACAddress, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha2.VirtualMachineMACAddressList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.VirtualMachineMACAddress, err error) + VirtualMachineMACAddressExpansion +} + +// virtualMachineMACAddresses implements VirtualMachineMACAddressInterface +type virtualMachineMACAddresses struct { + client rest.Interface + ns string +} + +// newVirtualMachineMACAddresses returns a VirtualMachineMACAddresses +func newVirtualMachineMACAddresses(c *VirtualizationV1alpha2Client, namespace string) *virtualMachineMACAddresses { + return &virtualMachineMACAddresses{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the virtualMachineMACAddress, and returns the corresponding virtualMachineMACAddress object, and an error if there is any. +func (c *virtualMachineMACAddresses) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha2.VirtualMachineMACAddress, err error) { + result = &v1alpha2.VirtualMachineMACAddress{} + err = c.client.Get(). + Namespace(c.ns). + Resource("virtualmachinemacaddresses"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of VirtualMachineMACAddresses that match those selectors. +func (c *virtualMachineMACAddresses) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha2.VirtualMachineMACAddressList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha2.VirtualMachineMACAddressList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("virtualmachinemacaddresses"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested virtualMachineMACAddresses. +func (c *virtualMachineMACAddresses) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("virtualmachinemacaddresses"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a virtualMachineMACAddress and creates it. Returns the server's representation of the virtualMachineMACAddress, and an error, if there is any. +func (c *virtualMachineMACAddresses) Create(ctx context.Context, virtualMachineMACAddress *v1alpha2.VirtualMachineMACAddress, opts v1.CreateOptions) (result *v1alpha2.VirtualMachineMACAddress, err error) { + result = &v1alpha2.VirtualMachineMACAddress{} + err = c.client.Post(). + Namespace(c.ns). + Resource("virtualmachinemacaddresses"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(virtualMachineMACAddress). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a virtualMachineMACAddress and updates it. Returns the server's representation of the virtualMachineMACAddress, and an error, if there is any. +func (c *virtualMachineMACAddresses) Update(ctx context.Context, virtualMachineMACAddress *v1alpha2.VirtualMachineMACAddress, opts v1.UpdateOptions) (result *v1alpha2.VirtualMachineMACAddress, err error) { + result = &v1alpha2.VirtualMachineMACAddress{} + err = c.client.Put(). + Namespace(c.ns). + Resource("virtualmachinemacaddresses"). + Name(virtualMachineMACAddress.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(virtualMachineMACAddress). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *virtualMachineMACAddresses) UpdateStatus(ctx context.Context, virtualMachineMACAddress *v1alpha2.VirtualMachineMACAddress, opts v1.UpdateOptions) (result *v1alpha2.VirtualMachineMACAddress, err error) { + result = &v1alpha2.VirtualMachineMACAddress{} + err = c.client.Put(). + Namespace(c.ns). + Resource("virtualmachinemacaddresses"). + Name(virtualMachineMACAddress.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(virtualMachineMACAddress). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the virtualMachineMACAddress and deletes it. Returns an error if one occurs. +func (c *virtualMachineMACAddresses) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("virtualmachinemacaddresses"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *virtualMachineMACAddresses) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("virtualmachinemacaddresses"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched virtualMachineMACAddress. +func (c *virtualMachineMACAddresses) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.VirtualMachineMACAddress, err error) { + result = &v1alpha2.VirtualMachineMACAddress{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("virtualmachinemacaddresses"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/api/client/generated/clientset/versioned/typed/core/v1alpha2/virtualmachinemacaddresslease.go b/api/client/generated/clientset/versioned/typed/core/v1alpha2/virtualmachinemacaddresslease.go new file mode 100644 index 000000000..1ca6505de --- /dev/null +++ b/api/client/generated/clientset/versioned/typed/core/v1alpha2/virtualmachinemacaddresslease.go @@ -0,0 +1,184 @@ +/* +Copyright Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + "context" + "time" + + scheme "github.com/deckhouse/virtualization/api/client/generated/clientset/versioned/scheme" + v1alpha2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// VirtualMachineMACAddressLeasesGetter has a method to return a VirtualMachineMACAddressLeaseInterface. +// A group's client should implement this interface. +type VirtualMachineMACAddressLeasesGetter interface { + VirtualMachineMACAddressLeases() VirtualMachineMACAddressLeaseInterface +} + +// VirtualMachineMACAddressLeaseInterface has methods to work with VirtualMachineMACAddressLease resources. +type VirtualMachineMACAddressLeaseInterface interface { + Create(ctx context.Context, virtualMachineMACAddressLease *v1alpha2.VirtualMachineMACAddressLease, opts v1.CreateOptions) (*v1alpha2.VirtualMachineMACAddressLease, error) + Update(ctx context.Context, virtualMachineMACAddressLease *v1alpha2.VirtualMachineMACAddressLease, opts v1.UpdateOptions) (*v1alpha2.VirtualMachineMACAddressLease, error) + UpdateStatus(ctx context.Context, virtualMachineMACAddressLease *v1alpha2.VirtualMachineMACAddressLease, opts v1.UpdateOptions) (*v1alpha2.VirtualMachineMACAddressLease, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha2.VirtualMachineMACAddressLease, error) + List(ctx context.Context, opts v1.ListOptions) (*v1alpha2.VirtualMachineMACAddressLeaseList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.VirtualMachineMACAddressLease, err error) + VirtualMachineMACAddressLeaseExpansion +} + +// virtualMachineMACAddressLeases implements VirtualMachineMACAddressLeaseInterface +type virtualMachineMACAddressLeases struct { + client rest.Interface +} + +// newVirtualMachineMACAddressLeases returns a VirtualMachineMACAddressLeases +func newVirtualMachineMACAddressLeases(c *VirtualizationV1alpha2Client) *virtualMachineMACAddressLeases { + return &virtualMachineMACAddressLeases{ + client: c.RESTClient(), + } +} + +// Get takes name of the virtualMachineMACAddressLease, and returns the corresponding virtualMachineMACAddressLease object, and an error if there is any. +func (c *virtualMachineMACAddressLeases) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha2.VirtualMachineMACAddressLease, err error) { + result = &v1alpha2.VirtualMachineMACAddressLease{} + err = c.client.Get(). + Resource("virtualmachinemacaddressleases"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of VirtualMachineMACAddressLeases that match those selectors. +func (c *virtualMachineMACAddressLeases) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha2.VirtualMachineMACAddressLeaseList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1alpha2.VirtualMachineMACAddressLeaseList{} + err = c.client.Get(). + Resource("virtualmachinemacaddressleases"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested virtualMachineMACAddressLeases. +func (c *virtualMachineMACAddressLeases) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Resource("virtualmachinemacaddressleases"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a virtualMachineMACAddressLease and creates it. Returns the server's representation of the virtualMachineMACAddressLease, and an error, if there is any. +func (c *virtualMachineMACAddressLeases) Create(ctx context.Context, virtualMachineMACAddressLease *v1alpha2.VirtualMachineMACAddressLease, opts v1.CreateOptions) (result *v1alpha2.VirtualMachineMACAddressLease, err error) { + result = &v1alpha2.VirtualMachineMACAddressLease{} + err = c.client.Post(). + Resource("virtualmachinemacaddressleases"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(virtualMachineMACAddressLease). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a virtualMachineMACAddressLease and updates it. Returns the server's representation of the virtualMachineMACAddressLease, and an error, if there is any. +func (c *virtualMachineMACAddressLeases) Update(ctx context.Context, virtualMachineMACAddressLease *v1alpha2.VirtualMachineMACAddressLease, opts v1.UpdateOptions) (result *v1alpha2.VirtualMachineMACAddressLease, err error) { + result = &v1alpha2.VirtualMachineMACAddressLease{} + err = c.client.Put(). + Resource("virtualmachinemacaddressleases"). + Name(virtualMachineMACAddressLease.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(virtualMachineMACAddressLease). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *virtualMachineMACAddressLeases) UpdateStatus(ctx context.Context, virtualMachineMACAddressLease *v1alpha2.VirtualMachineMACAddressLease, opts v1.UpdateOptions) (result *v1alpha2.VirtualMachineMACAddressLease, err error) { + result = &v1alpha2.VirtualMachineMACAddressLease{} + err = c.client.Put(). + Resource("virtualmachinemacaddressleases"). + Name(virtualMachineMACAddressLease.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(virtualMachineMACAddressLease). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the virtualMachineMACAddressLease and deletes it. Returns an error if one occurs. +func (c *virtualMachineMACAddressLeases) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Resource("virtualmachinemacaddressleases"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *virtualMachineMACAddressLeases) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Resource("virtualmachinemacaddressleases"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched virtualMachineMACAddressLease. +func (c *virtualMachineMACAddressLeases) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha2.VirtualMachineMACAddressLease, err error) { + result = &v1alpha2.VirtualMachineMACAddressLease{} + err = c.client.Patch(pt). + Resource("virtualmachinemacaddressleases"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/api/client/generated/informers/externalversions/core/v1alpha2/interface.go b/api/client/generated/informers/externalversions/core/v1alpha2/interface.go index 867f35fd0..f8dc88f22 100644 --- a/api/client/generated/informers/externalversions/core/v1alpha2/interface.go +++ b/api/client/generated/informers/externalversions/core/v1alpha2/interface.go @@ -42,6 +42,10 @@ type Interface interface { VirtualMachineIPAddresses() VirtualMachineIPAddressInformer // VirtualMachineIPAddressLeases returns a VirtualMachineIPAddressLeaseInformer. VirtualMachineIPAddressLeases() VirtualMachineIPAddressLeaseInformer + // VirtualMachineMACAddresses returns a VirtualMachineMACAddressInformer. + VirtualMachineMACAddresses() VirtualMachineMACAddressInformer + // VirtualMachineMACAddressLeases returns a VirtualMachineMACAddressLeaseInformer. + VirtualMachineMACAddressLeases() VirtualMachineMACAddressLeaseInformer // VirtualMachineOperations returns a VirtualMachineOperationInformer. VirtualMachineOperations() VirtualMachineOperationInformer // VirtualMachineRestores returns a VirtualMachineRestoreInformer. @@ -106,6 +110,16 @@ func (v *version) VirtualMachineIPAddressLeases() VirtualMachineIPAddressLeaseIn return &virtualMachineIPAddressLeaseInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} } +// VirtualMachineMACAddresses returns a VirtualMachineMACAddressInformer. +func (v *version) VirtualMachineMACAddresses() VirtualMachineMACAddressInformer { + return &virtualMachineMACAddressInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + +// VirtualMachineMACAddressLeases returns a VirtualMachineMACAddressLeaseInformer. +func (v *version) VirtualMachineMACAddressLeases() VirtualMachineMACAddressLeaseInformer { + return &virtualMachineMACAddressLeaseInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} +} + // VirtualMachineOperations returns a VirtualMachineOperationInformer. func (v *version) VirtualMachineOperations() VirtualMachineOperationInformer { return &virtualMachineOperationInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/api/client/generated/informers/externalversions/core/v1alpha2/virtualmachinemacaddress.go b/api/client/generated/informers/externalversions/core/v1alpha2/virtualmachinemacaddress.go new file mode 100644 index 000000000..0b0352be3 --- /dev/null +++ b/api/client/generated/informers/externalversions/core/v1alpha2/virtualmachinemacaddress.go @@ -0,0 +1,90 @@ +/* +Copyright Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + "context" + time "time" + + versioned "github.com/deckhouse/virtualization/api/client/generated/clientset/versioned" + internalinterfaces "github.com/deckhouse/virtualization/api/client/generated/informers/externalversions/internalinterfaces" + v1alpha2 "github.com/deckhouse/virtualization/api/client/generated/listers/core/v1alpha2" + corev1alpha2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// VirtualMachineMACAddressInformer provides access to a shared informer and lister for +// VirtualMachineMACAddresses. +type VirtualMachineMACAddressInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha2.VirtualMachineMACAddressLister +} + +type virtualMachineMACAddressInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewVirtualMachineMACAddressInformer constructs a new informer for VirtualMachineMACAddress 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 NewVirtualMachineMACAddressInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredVirtualMachineMACAddressInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredVirtualMachineMACAddressInformer constructs a new informer for VirtualMachineMACAddress 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 NewFilteredVirtualMachineMACAddressInformer(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().VirtualMachineMACAddresses(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.VirtualizationV1alpha2().VirtualMachineMACAddresses(namespace).Watch(context.TODO(), options) + }, + }, + &corev1alpha2.VirtualMachineMACAddress{}, + resyncPeriod, + indexers, + ) +} + +func (f *virtualMachineMACAddressInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredVirtualMachineMACAddressInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *virtualMachineMACAddressInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&corev1alpha2.VirtualMachineMACAddress{}, f.defaultInformer) +} + +func (f *virtualMachineMACAddressInformer) Lister() v1alpha2.VirtualMachineMACAddressLister { + return v1alpha2.NewVirtualMachineMACAddressLister(f.Informer().GetIndexer()) +} diff --git a/api/client/generated/informers/externalversions/core/v1alpha2/virtualmachinemacaddresslease.go b/api/client/generated/informers/externalversions/core/v1alpha2/virtualmachinemacaddresslease.go new file mode 100644 index 000000000..069c45f89 --- /dev/null +++ b/api/client/generated/informers/externalversions/core/v1alpha2/virtualmachinemacaddresslease.go @@ -0,0 +1,89 @@ +/* +Copyright Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + "context" + time "time" + + versioned "github.com/deckhouse/virtualization/api/client/generated/clientset/versioned" + internalinterfaces "github.com/deckhouse/virtualization/api/client/generated/informers/externalversions/internalinterfaces" + v1alpha2 "github.com/deckhouse/virtualization/api/client/generated/listers/core/v1alpha2" + corev1alpha2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// VirtualMachineMACAddressLeaseInformer provides access to a shared informer and lister for +// VirtualMachineMACAddressLeases. +type VirtualMachineMACAddressLeaseInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1alpha2.VirtualMachineMACAddressLeaseLister +} + +type virtualMachineMACAddressLeaseInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// NewVirtualMachineMACAddressLeaseInformer constructs a new informer for VirtualMachineMACAddressLease 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 NewVirtualMachineMACAddressLeaseInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredVirtualMachineMACAddressLeaseInformer(client, resyncPeriod, indexers, nil) +} + +// NewFilteredVirtualMachineMACAddressLeaseInformer constructs a new informer for VirtualMachineMACAddressLease 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 NewFilteredVirtualMachineMACAddressLeaseInformer(client versioned.Interface, 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().VirtualMachineMACAddressLeases().List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.VirtualizationV1alpha2().VirtualMachineMACAddressLeases().Watch(context.TODO(), options) + }, + }, + &corev1alpha2.VirtualMachineMACAddressLease{}, + resyncPeriod, + indexers, + ) +} + +func (f *virtualMachineMACAddressLeaseInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredVirtualMachineMACAddressLeaseInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *virtualMachineMACAddressLeaseInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&corev1alpha2.VirtualMachineMACAddressLease{}, f.defaultInformer) +} + +func (f *virtualMachineMACAddressLeaseInformer) Lister() v1alpha2.VirtualMachineMACAddressLeaseLister { + return v1alpha2.NewVirtualMachineMACAddressLeaseLister(f.Informer().GetIndexer()) +} diff --git a/api/client/generated/informers/externalversions/generic.go b/api/client/generated/informers/externalversions/generic.go index 77f8f2188..b45147102 100644 --- a/api/client/generated/informers/externalversions/generic.go +++ b/api/client/generated/informers/externalversions/generic.go @@ -71,6 +71,10 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource return &genericInformer{resource: resource.GroupResource(), informer: f.Virtualization().V1alpha2().VirtualMachineIPAddresses().Informer()}, nil case v1alpha2.SchemeGroupVersion.WithResource("virtualmachineipaddressleases"): return &genericInformer{resource: resource.GroupResource(), informer: f.Virtualization().V1alpha2().VirtualMachineIPAddressLeases().Informer()}, nil + case v1alpha2.SchemeGroupVersion.WithResource("virtualmachinemacaddresses"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Virtualization().V1alpha2().VirtualMachineMACAddresses().Informer()}, nil + case v1alpha2.SchemeGroupVersion.WithResource("virtualmachinemacaddressleases"): + 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("virtualmachinerestores"): diff --git a/api/client/generated/listers/core/v1alpha2/expansion_generated.go b/api/client/generated/listers/core/v1alpha2/expansion_generated.go index f9d606ce1..828def406 100644 --- a/api/client/generated/listers/core/v1alpha2/expansion_generated.go +++ b/api/client/generated/listers/core/v1alpha2/expansion_generated.go @@ -78,6 +78,18 @@ type VirtualMachineIPAddressNamespaceListerExpansion interface{} // VirtualMachineIPAddressLeaseLister. type VirtualMachineIPAddressLeaseListerExpansion interface{} +// VirtualMachineMACAddressListerExpansion allows custom methods to be added to +// VirtualMachineMACAddressLister. +type VirtualMachineMACAddressListerExpansion interface{} + +// VirtualMachineMACAddressNamespaceListerExpansion allows custom methods to be added to +// VirtualMachineMACAddressNamespaceLister. +type VirtualMachineMACAddressNamespaceListerExpansion interface{} + +// VirtualMachineMACAddressLeaseListerExpansion allows custom methods to be added to +// VirtualMachineMACAddressLeaseLister. +type VirtualMachineMACAddressLeaseListerExpansion interface{} + // VirtualMachineOperationListerExpansion allows custom methods to be added to // VirtualMachineOperationLister. type VirtualMachineOperationListerExpansion interface{} diff --git a/api/client/generated/listers/core/v1alpha2/virtualmachinemacaddress.go b/api/client/generated/listers/core/v1alpha2/virtualmachinemacaddress.go new file mode 100644 index 000000000..2fa8f4486 --- /dev/null +++ b/api/client/generated/listers/core/v1alpha2/virtualmachinemacaddress.go @@ -0,0 +1,99 @@ +/* +Copyright Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + v1alpha2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// VirtualMachineMACAddressLister helps list VirtualMachineMACAddresses. +// All objects returned here must be treated as read-only. +type VirtualMachineMACAddressLister interface { + // List lists all VirtualMachineMACAddresses in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha2.VirtualMachineMACAddress, err error) + // VirtualMachineMACAddresses returns an object that can list and get VirtualMachineMACAddresses. + VirtualMachineMACAddresses(namespace string) VirtualMachineMACAddressNamespaceLister + VirtualMachineMACAddressListerExpansion +} + +// virtualMachineMACAddressLister implements the VirtualMachineMACAddressLister interface. +type virtualMachineMACAddressLister struct { + indexer cache.Indexer +} + +// NewVirtualMachineMACAddressLister returns a new VirtualMachineMACAddressLister. +func NewVirtualMachineMACAddressLister(indexer cache.Indexer) VirtualMachineMACAddressLister { + return &virtualMachineMACAddressLister{indexer: indexer} +} + +// List lists all VirtualMachineMACAddresses in the indexer. +func (s *virtualMachineMACAddressLister) List(selector labels.Selector) (ret []*v1alpha2.VirtualMachineMACAddress, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha2.VirtualMachineMACAddress)) + }) + return ret, err +} + +// VirtualMachineMACAddresses returns an object that can list and get VirtualMachineMACAddresses. +func (s *virtualMachineMACAddressLister) VirtualMachineMACAddresses(namespace string) VirtualMachineMACAddressNamespaceLister { + return virtualMachineMACAddressNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// VirtualMachineMACAddressNamespaceLister helps list and get VirtualMachineMACAddresses. +// All objects returned here must be treated as read-only. +type VirtualMachineMACAddressNamespaceLister interface { + // List lists all VirtualMachineMACAddresses in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha2.VirtualMachineMACAddress, err error) + // Get retrieves the VirtualMachineMACAddress from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha2.VirtualMachineMACAddress, error) + VirtualMachineMACAddressNamespaceListerExpansion +} + +// virtualMachineMACAddressNamespaceLister implements the VirtualMachineMACAddressNamespaceLister +// interface. +type virtualMachineMACAddressNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all VirtualMachineMACAddresses in the indexer for a given namespace. +func (s virtualMachineMACAddressNamespaceLister) List(selector labels.Selector) (ret []*v1alpha2.VirtualMachineMACAddress, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha2.VirtualMachineMACAddress)) + }) + return ret, err +} + +// Get retrieves the VirtualMachineMACAddress from the indexer for a given namespace and name. +func (s virtualMachineMACAddressNamespaceLister) Get(name string) (*v1alpha2.VirtualMachineMACAddress, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha2.Resource("virtualmachinemacaddress"), name) + } + return obj.(*v1alpha2.VirtualMachineMACAddress), nil +} diff --git a/api/client/generated/listers/core/v1alpha2/virtualmachinemacaddresslease.go b/api/client/generated/listers/core/v1alpha2/virtualmachinemacaddresslease.go new file mode 100644 index 000000000..13f46c47c --- /dev/null +++ b/api/client/generated/listers/core/v1alpha2/virtualmachinemacaddresslease.go @@ -0,0 +1,68 @@ +/* +Copyright Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha2 + +import ( + v1alpha2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// VirtualMachineMACAddressLeaseLister helps list VirtualMachineMACAddressLeases. +// All objects returned here must be treated as read-only. +type VirtualMachineMACAddressLeaseLister interface { + // List lists all VirtualMachineMACAddressLeases in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1alpha2.VirtualMachineMACAddressLease, err error) + // Get retrieves the VirtualMachineMACAddressLease from the index for a given name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1alpha2.VirtualMachineMACAddressLease, error) + VirtualMachineMACAddressLeaseListerExpansion +} + +// virtualMachineMACAddressLeaseLister implements the VirtualMachineMACAddressLeaseLister interface. +type virtualMachineMACAddressLeaseLister struct { + indexer cache.Indexer +} + +// NewVirtualMachineMACAddressLeaseLister returns a new VirtualMachineMACAddressLeaseLister. +func NewVirtualMachineMACAddressLeaseLister(indexer cache.Indexer) VirtualMachineMACAddressLeaseLister { + return &virtualMachineMACAddressLeaseLister{indexer: indexer} +} + +// List lists all VirtualMachineMACAddressLeases in the indexer. +func (s *virtualMachineMACAddressLeaseLister) List(selector labels.Selector) (ret []*v1alpha2.VirtualMachineMACAddressLease, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1alpha2.VirtualMachineMACAddressLease)) + }) + return ret, err +} + +// Get retrieves the VirtualMachineMACAddressLease from the index for a given name. +func (s *virtualMachineMACAddressLeaseLister) Get(name string) (*v1alpha2.VirtualMachineMACAddressLease, error) { + obj, exists, err := s.indexer.GetByKey(name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1alpha2.Resource("virtualmachinemacaddresslease"), name) + } + return obj.(*v1alpha2.VirtualMachineMACAddressLease), nil +} diff --git a/api/core/v1alpha2/finalizers.go b/api/core/v1alpha2/finalizers.go index a25a34ed1..3fd0d99fb 100644 --- a/api/core/v1alpha2/finalizers.go +++ b/api/core/v1alpha2/finalizers.go @@ -22,18 +22,21 @@ const ( FinalizerVDProtection = "virtualization.deckhouse.io/vd-protection" FinalizerKVVMProtection = "virtualization.deckhouse.io/kvvm-protection" FinalizerIPAddressProtection = "virtualization.deckhouse.io/vmip-protection" + FinalizerMACAddressProtection = "virtualization.deckhouse.io/vmmac-protection" FinalizerPodProtection = "virtualization.deckhouse.io/pod-protection" FinalizerVDSnapshotProtection = "virtualization.deckhouse.io/vdsnapshot-protection" FinalizerVMSnapshotProtection = "virtualization.deckhouse.io/vmsnapshot-protection" - FinalizerCVICleanup = "virtualization.deckhouse.io/cvi-cleanup" - FinalizerVDCleanup = "virtualization.deckhouse.io/vd-cleanup" - FinalizerVICleanup = "virtualization.deckhouse.io/vi-cleanup" - FinalizerVMCleanup = "virtualization.deckhouse.io/vm-cleanup" - FinalizerIPAddressCleanup = "virtualization.deckhouse.io/vmip-cleanup" - FinalizerIPAddressLeaseCleanup = "virtualization.deckhouse.io/vmipl-cleanup" - FinalizerVDSnapshotCleanup = "virtualization.deckhouse.io/vdsnapshot-cleanup" - FinalizerVMOPCleanup = "virtualization.deckhouse.io/vmop-cleanup" - FinalizerVMClassCleanup = "virtualization.deckhouse.io/vmclass-cleanup" - FinalizerVMBDACleanup = "virtualization.deckhouse.io/vmbda-cleanup" + FinalizerCVICleanup = "virtualization.deckhouse.io/cvi-cleanup" + FinalizerVDCleanup = "virtualization.deckhouse.io/vd-cleanup" + FinalizerVICleanup = "virtualization.deckhouse.io/vi-cleanup" + FinalizerVMCleanup = "virtualization.deckhouse.io/vm-cleanup" + FinalizerIPAddressCleanup = "virtualization.deckhouse.io/vmip-cleanup" + FinalizerIPAddressLeaseCleanup = "virtualization.deckhouse.io/vmipl-cleanup" + FinalizerMACAddressCleanup = "virtualization.deckhouse.io/vmmac-cleanup" + FinalizerMACAddressLeaseCleanup = "virtualization.deckhouse.io/vmmacl-cleanup" + FinalizerVDSnapshotCleanup = "virtualization.deckhouse.io/vdsnapshot-cleanup" + FinalizerVMOPCleanup = "virtualization.deckhouse.io/vmop-cleanup" + FinalizerVMClassCleanup = "virtualization.deckhouse.io/vmclass-cleanup" + FinalizerVMBDACleanup = "virtualization.deckhouse.io/vmbda-cleanup" ) diff --git a/api/core/v1alpha2/register.go b/api/core/v1alpha2/register.go index 62c73482b..06e1c7e9b 100644 --- a/api/core/v1alpha2/register.go +++ b/api/core/v1alpha2/register.go @@ -86,6 +86,10 @@ func addKnownTypes(scheme *runtime.Scheme) error { &VirtualMachineSnapshotList{}, &VirtualMachineRestore{}, &VirtualMachineRestoreList{}, + &VirtualMachineMACAddress{}, + &VirtualMachineMACAddressList{}, + &VirtualMachineMACAddressLease{}, + &VirtualMachineMACAddressLeaseList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil diff --git a/api/core/v1alpha2/virtual_machine.go b/api/core/v1alpha2/virtual_machine.go index 20c814d13..706b3e763 100644 --- a/api/core/v1alpha2/virtual_machine.go +++ b/api/core/v1alpha2/virtual_machine.go @@ -69,6 +69,8 @@ type VirtualMachineSpec struct { // If not explicitly specified, by default a `virtualMachineIPAddress` resource is created for the VM with a name similar to the VM resource (`.metadata.name`). VirtualMachineIPAddress string `json:"virtualMachineIPAddressName,omitempty"` + VirtualMachineMACAddress string `json:"virtualMachineMACAddressName,omitempty"` + TopologySpreadConstraints []corev1.TopologySpreadConstraint `json:"topologySpreadConstraints,omitempty"` Affinity *VMAffinity `json:"affinity,omitempty"` @@ -244,8 +246,12 @@ type VirtualMachineStatus struct { Node string `json:"nodeName"` // Name of `virtualMachineIPAddressName` holding the ip address of the VirtualMachine. VirtualMachineIPAddress string `json:"virtualMachineIPAddressName"` + // Name of `virtualMachineMACAddressName` holding the MAC address of the VirtualMachine. + VirtualMachineMACAddress string `json:"virtualMachineMACAddressName"` // IP address of VM. IPAddress string `json:"ipAddress"` + // MAC address of VM. + MACAddress string `json:"macAddress"` // The list of attached block device attachments. BlockDeviceRefs []BlockDeviceStatusRef `json:"blockDeviceRefs,omitempty"` GuestOSInfo virtv1.VirtualMachineInstanceGuestOSInfo `json:"guestOSInfo,omitempty"` diff --git a/api/core/v1alpha2/virtual_machine_mac_address.go b/api/core/v1alpha2/virtual_machine_mac_address.go new file mode 100644 index 000000000..279a78d6b --- /dev/null +++ b/api/core/v1alpha2/virtual_machine_mac_address.go @@ -0,0 +1,81 @@ +/* +Copyright 2024 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha2 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + VirtualMachineMACAddressKind = "VirtualMachineMACAddress" + VirtualMachineMACAddressResource = "virtualmachinemacaddresses" +) + +// VirtualMachineMACAddress defines MAC address for virtual machine. +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type VirtualMachineMACAddress struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec VirtualMachineMACAddressSpec `json:"spec,omitempty"` + Status VirtualMachineMACAddressStatus `json:"status,omitempty"` +} + +// VirtualMachineMACAddressList contains a list of VirtualMachineMACAddress +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type VirtualMachineMACAddressList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []VirtualMachineMACAddress `json:"items"` +} + +// VirtualMachineMACAddressSpec is the desired state of `VirtualMachineMACAddress`. +type VirtualMachineMACAddressSpec struct { + // MACAddress is the requested MAC address. If omitted - random generate. + Address string `json:"address,omitempty"` +} + +// VirtualMachineMACAddressStatus is the observed state of `VirtualMachineMACAddress`. +type VirtualMachineMACAddressStatus struct { + // VirtualMachine represents the virtual machine that currently uses this MAC address. + // It's the name of the virtual machine instance. + VirtualMachine string `json:"virtualMachineName,omitempty"` + + // Address is the assigned MAC address allocated to the virtual machine. + Address string `json:"address,omitempty"` + + // Phase represents the current state of the MAC address. + // It could indicate whether the MAC address is in use, available, or in any other defined state. + Phase VirtualMachineMACAddressPhase `json:"phase,omitempty"` + + // ObservedGeneration is the most recent generation observed by the controller. + // This is used to identify changes that have been recently observed and handled. + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + + // Conditions represents the latest available observations of the object's state. + // They provide detailed status and information, such as whether the MAC address allocation was successful, in progress, etc. + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +type VirtualMachineMACAddressPhase string + +const ( + VirtualMachineMACAddressPhasePending VirtualMachineMACAddressPhase = "Pending" + VirtualMachineMACAddressPhaseBound VirtualMachineMACAddressPhase = "Bound" + VirtualMachineMACAddressPhaseAttached VirtualMachineMACAddressPhase = "Attached" +) diff --git a/api/core/v1alpha2/virtual_machine_mac_address_lease.go b/api/core/v1alpha2/virtual_machine_mac_address_lease.go new file mode 100644 index 000000000..222586fda --- /dev/null +++ b/api/core/v1alpha2/virtual_machine_mac_address_lease.go @@ -0,0 +1,72 @@ +/* +Copyright 2024 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha2 + +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +const ( + VirtualMachineMACAddressLeaseKind = "VirtualMachineMACAddressLease" + VirtualMachineMACAddressLeaseResource = "virtualmachinemacaddressleases" +) + +// VirtualMachineMACAddressLease defines fact of issued lease for `VirtualMachineMACAddress`. +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type VirtualMachineMACAddressLease struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec VirtualMachineMACAddressLeaseSpec `json:"spec,omitempty"` + Status VirtualMachineMACAddressLeaseStatus `json:"status,omiMACmpty"` +} + +// VirtualMachineMACAddressLeaseList contains a list of VirtualMachineMACAddressLease +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type VirtualMachineMACAddressLeaseList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []VirtualMachineMACAddressLease `json:"items"` +} + +// VirtualMachineMACAddressLeaseSpec is the desired state of `VirtualMachineMACAddressLease`. +type VirtualMachineMACAddressLeaseSpec struct { + // The link to existing `VirtualMachineMACAddress`. + VirtualMachineMACAddressRef *VirtualMachineMACAddressLeaseMACAddressRef `json:"virtualMachineMACAddressRef,omitempty"` +} + +type VirtualMachineMACAddressLeaseMACAddressRef struct { + // The Namespace of the referenced `VirtualMachineMACAddress`. + Namespace string `json:"namespace"` + // The name of the referenced `VirtualMachineMACAddress`. + Name string `json:"name"` +} + +// VirtualMachineMACAddressLeaseStatus is the observed state of `VirtualMachineMACAddressLease`. +type VirtualMachineMACAddressLeaseStatus struct { + // Represents the current state of issued MAC address lease. + Phase VirtualMachineMACAddressLeasePhase `json:"phase,omitempty"` + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +type VirtualMachineMACAddressLeasePhase string + +const ( + VirtualMachineMACAddressLeasePhaseBound VirtualMachineMACAddressLeasePhase = "Bound" + VirtualMachineMACAddressLeasePhaseReleased VirtualMachineMACAddressLeasePhase = "Released" +) diff --git a/api/core/v1alpha2/vmcondition/condition.go b/api/core/v1alpha2/vmcondition/condition.go index 7ff2fb56a..4864ecca3 100644 --- a/api/core/v1alpha2/vmcondition/condition.go +++ b/api/core/v1alpha2/vmcondition/condition.go @@ -24,6 +24,7 @@ func (t Type) String() string { const ( TypeIPAddressReady Type = "VirtualMachineIPAddressReady" + TypeMACAddressReady Type = "VirtualMachineMACAddressReady" TypeClassReady Type = "VirtualMachineClassReady" TypeBlockDevicesReady Type = "BlockDevicesReady" TypeRunning Type = "Running" @@ -62,6 +63,11 @@ const ( ReasonIPAddressNotAssigned Reason = "VirtualMachineIPAddressNotAssigned" ReasonIPAddressNotAvailable Reason = "VirtualMachineIPAddressNotAvailable" + ReasonMACAddressReady Reason = "VirtualMachineMACAddressReady" + ReasonMACAddressNotReady Reason = "VirtualMachineMACAddressNotReady" + ReasonMACAddressNotAssigned Reason = "VirtualMachineMACAddressNotAssigned" + ReasonMACAddressNotAvailable Reason = "VirtualMachineMACAddressNotAvailable" + ReasonBlockDevicesReady Reason = "BlockDevicesReady" ReasonWaitingForProvisioningToPVC Reason = "WaitingForTheProvisioningToPersistentVolumeClaim" ReasonBlockDevicesNotReady Reason = "BlockDevicesNotReady" diff --git a/api/core/v1alpha2/vmmaccondition/condition.go b/api/core/v1alpha2/vmmaccondition/condition.go new file mode 100644 index 000000000..f2e8f696a --- /dev/null +++ b/api/core/v1alpha2/vmmaccondition/condition.go @@ -0,0 +1,73 @@ +/* +Copyright 2024 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package vmmaccondition + +type Type string + +const ( + // BoundType represents the condition type when a Virtual Machine MAC address is bound. + BoundType Type = "Bound" + + // AttachedType represents the condition type when a Virtual Machine MAC address is attached. + AttachedType Type = "Attached" +) + +func (t Type) String() string { + return string(t) +} + +type ( + // BoundReason represents specific reasons for the 'Bound' condition type. + BoundReason string + + // AttachedReason represents specific reasons for the 'Attached' condition type. + AttachedReason string +) + +func (r BoundReason) String() string { + return string(r) +} + +func (r AttachedReason) String() string { + return string(r) +} + +const ( + // VirtualMachineMACAddressIsOutOfTheValidRange is a BoundReason indicating when specified MAC address is out of the range in controller settings. + VirtualMachineMACAddressIsOutOfTheValidRange BoundReason = "VirtualMachineMACAddressIsOutOfTheValidRange" + + // VirtualMachineMACAddressLeaseAlreadyExists is a BoundReason indicating the MAC address lease already exists. + VirtualMachineMACAddressLeaseAlreadyExists BoundReason = "VirtualMachineMACAddressLeaseAlreadyExists" + + // VirtualMachineMACAddressLeaseLost is a BoundReason indicating the MAC address lease was lost. + VirtualMachineMACAddressLeaseLost BoundReason = "VirtualMachineMACAddressLeaseLost" + + // VirtualMachineMACAddressLeaseNotFound is a BoundReason indicating the MAC address lease was not found. + VirtualMachineMACAddressLeaseNotFound BoundReason = "VirtualMachineMACAddressLeaseNotFound" + + // VirtualMachineMACAddressLeaseNotReady is a BoundReason indicating the MAC address lease was not ready. + VirtualMachineMACAddressLeaseNotReady BoundReason = "VirtualMachineMACAddressLeaseNotReady" + + // Bound is a BoundReason indicating the MAC address lease is successfully bound. + Bound BoundReason = "Bound" + + // VirtualMachineNotFound is an AttachedReason indicating the Virtual Machine was not found. + VirtualMachineNotFound AttachedReason = "VirtualMachineNotFound" + + // Attached is an AttachedReason indicating the MAC address was successfully attached to the Virtual Machine. + Attached AttachedReason = "Attached" +) diff --git a/api/core/v1alpha2/vmmaclcondition/condition.go b/api/core/v1alpha2/vmmaclcondition/condition.go new file mode 100644 index 000000000..f80faea99 --- /dev/null +++ b/api/core/v1alpha2/vmmaclcondition/condition.go @@ -0,0 +1,43 @@ +/* +Copyright 2024 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package vmmaclcondition + +type Type string + +const ( + // BoundType represents the condition type when a Virtual Machine MAC is bound. + BoundType Type = "Bound" +) + +func (t Type) String() string { + return string(t) +} + +// BoundReason represents specific reasons for the 'Bound' condition type. +type BoundReason string + +func (r BoundReason) String() string { + return string(r) +} + +const ( + // Released is a BoundReason indicating the MAC address lease has been released. + Released BoundReason = "Released" + + // Bound is a BoundReason indicating the MAC address lease is successfully bound. + Bound BoundReason = "Bound" +) diff --git a/api/core/v1alpha2/zz_generated.deepcopy.go b/api/core/v1alpha2/zz_generated.deepcopy.go index 137e594a6..33ffbcda8 100644 --- a/api/core/v1alpha2/zz_generated.deepcopy.go +++ b/api/core/v1alpha2/zz_generated.deepcopy.go @@ -2121,6 +2121,227 @@ func (in *VirtualMachineLocation) DeepCopy() *VirtualMachineLocation { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VirtualMachineMACAddress) DeepCopyInto(out *VirtualMachineMACAddress) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualMachineMACAddress. +func (in *VirtualMachineMACAddress) DeepCopy() *VirtualMachineMACAddress { + if in == nil { + return nil + } + out := new(VirtualMachineMACAddress) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VirtualMachineMACAddress) 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 *VirtualMachineMACAddressLease) DeepCopyInto(out *VirtualMachineMACAddressLease) { + *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 VirtualMachineMACAddressLease. +func (in *VirtualMachineMACAddressLease) DeepCopy() *VirtualMachineMACAddressLease { + if in == nil { + return nil + } + out := new(VirtualMachineMACAddressLease) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VirtualMachineMACAddressLease) 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 *VirtualMachineMACAddressLeaseList) DeepCopyInto(out *VirtualMachineMACAddressLeaseList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]VirtualMachineMACAddressLease, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualMachineMACAddressLeaseList. +func (in *VirtualMachineMACAddressLeaseList) DeepCopy() *VirtualMachineMACAddressLeaseList { + if in == nil { + return nil + } + out := new(VirtualMachineMACAddressLeaseList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VirtualMachineMACAddressLeaseList) 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 *VirtualMachineMACAddressLeaseMACAddressRef) DeepCopyInto(out *VirtualMachineMACAddressLeaseMACAddressRef) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualMachineMACAddressLeaseMACAddressRef. +func (in *VirtualMachineMACAddressLeaseMACAddressRef) DeepCopy() *VirtualMachineMACAddressLeaseMACAddressRef { + if in == nil { + return nil + } + out := new(VirtualMachineMACAddressLeaseMACAddressRef) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VirtualMachineMACAddressLeaseSpec) DeepCopyInto(out *VirtualMachineMACAddressLeaseSpec) { + *out = *in + if in.VirtualMachineMACAddressRef != nil { + in, out := &in.VirtualMachineMACAddressRef, &out.VirtualMachineMACAddressRef + *out = new(VirtualMachineMACAddressLeaseMACAddressRef) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualMachineMACAddressLeaseSpec. +func (in *VirtualMachineMACAddressLeaseSpec) DeepCopy() *VirtualMachineMACAddressLeaseSpec { + if in == nil { + return nil + } + out := new(VirtualMachineMACAddressLeaseSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VirtualMachineMACAddressLeaseStatus) DeepCopyInto(out *VirtualMachineMACAddressLeaseStatus) { + *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 VirtualMachineMACAddressLeaseStatus. +func (in *VirtualMachineMACAddressLeaseStatus) DeepCopy() *VirtualMachineMACAddressLeaseStatus { + if in == nil { + return nil + } + out := new(VirtualMachineMACAddressLeaseStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VirtualMachineMACAddressList) DeepCopyInto(out *VirtualMachineMACAddressList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]VirtualMachineMACAddress, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualMachineMACAddressList. +func (in *VirtualMachineMACAddressList) DeepCopy() *VirtualMachineMACAddressList { + if in == nil { + return nil + } + out := new(VirtualMachineMACAddressList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *VirtualMachineMACAddressList) 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 *VirtualMachineMACAddressSpec) DeepCopyInto(out *VirtualMachineMACAddressSpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VirtualMachineMACAddressSpec. +func (in *VirtualMachineMACAddressSpec) DeepCopy() *VirtualMachineMACAddressSpec { + if in == nil { + return nil + } + out := new(VirtualMachineMACAddressSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VirtualMachineMACAddressStatus) DeepCopyInto(out *VirtualMachineMACAddressStatus) { + *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 VirtualMachineMACAddressStatus. +func (in *VirtualMachineMACAddressStatus) DeepCopy() *VirtualMachineMACAddressStatus { + if in == nil { + return nil + } + out := new(VirtualMachineMACAddressStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VirtualMachineMigrationState) DeepCopyInto(out *VirtualMachineMigrationState) { *out = *in diff --git a/crds/doc-ru-virtualmachinemacaddresses.yaml b/crds/doc-ru-virtualmachinemacaddresses.yaml new file mode 100644 index 000000000..db8f096a2 --- /dev/null +++ b/crds/doc-ru-virtualmachinemacaddresses.yaml @@ -0,0 +1,55 @@ +spec: + versions: + - name: v1alpha2 + schema: + openAPIV3Schema: + description: | + Ресурс, определяющий MAC-адрес для виртуальной машины. + properties: + spec: + description: | + Настройки `VirtualMachineMACAddress`. + properties: + address: + description: | + Запрашиваемый MAC-адрес, который должен быть присвоен виртуальной машине. + status: + properties: + conditions: + description: | + Последнее подтвержденное состояние данного ресурса. + items: + description: | + condition содержит подробные сведения об одном аспекте текущего состояния этого ресурса API. + properties: + lastTransitionTime: + description: Время перехода условия из одного состояния в другое. + message: + description: Удобочитаемое сообщение с подробной информацией о последнем переходе. + observedGeneration: + description: | + observedGeneration представляет собой .metadata.generation, на основе которого было установлено условие. + Например, если .metadata.generation в настоящее время имеет значение 12, а .status.conditions[x].observedgeneration имеет значение 9, то условие устарело. + reason: + description: Краткая причина последнего перехода состояния. + status: + description: | + Статус условия. Возможные значения: `True`, `False`, `Unknown`. + type: + description: Тип условия. + address: + description: | + Назначенный MAC-адрес. + phase: + description: | + Представляет текущее состояние ресурса `VirtualMachineMACAddress`. + + * Pending - создание ресурса находится в процессе выполнения. + * Bound - ресурс `VirtualMachineMACAddress` привязан к ресурсу `VirtualMachineMACAddressLease`. + * Attached - ресурс `VirtualMachineMACAddress` подключен к ресурсу `VirtualMachine`. + virtualMachineName: + description: | + Имя виртуальной машины, которая в настоящее время использует этот MAC-адрес. + observedGeneration: + description: | + Поколение ресурса, которое в последний раз обрабатывалось контроллером. diff --git a/crds/doc-ru-virtualmachinemacaddressleases.yaml b/crds/doc-ru-virtualmachinemacaddressleases.yaml new file mode 100644 index 000000000..2136df6ad --- /dev/null +++ b/crds/doc-ru-virtualmachinemacaddressleases.yaml @@ -0,0 +1,55 @@ +spec: + versions: + - name: v1alpha2 + schema: + openAPIV3Schema: + description: | + Ресурс, определяющий факт выданной аренды для `VirtualMachineMACAddress`. + properties: + spec: + description: | + Параметры конфигурации `VirtualMachineMACAddressLease`. + properties: + virtualMachineMACAddressRef: + description: | + Ссылка на существующие `VirtualMachineMACAddress`. + properties: + name: + description: | + Имя ссылающегося `VirtualMachineMACAddress`. + namespace: + description: | + Пространство имен ссылающегося `VirtualMachineMACAddress`. + status: + properties: + conditions: + description: | + Последнее подтвержденное состояние данного ресурса. + items: + description: | + condition содержит подробные сведения об одном аспекте текущего состояния этого ресурса API. + properties: + lastTransitionTime: + description: Время перехода условия из одного состояния в другое. + message: + description: Удобочитаемое сообщение с подробной информацией о последнем переходе. + observedGeneration: + description: | + observedGeneration представляет собой .metadata.generation, на основе которого было установлено условие. + Например, если .metadata.generation в настоящее время имеет значение 12, а .status.conditions[x].observedgeneration имеет значение 9, то условие устарело. + reason: + description: Краткая причина последнего перехода состояния. + status: + description: | + Статус условия. Возможные значения: `True`, `False`, `Unknown`. + type: + description: Тип условия. + phase: + description: | + Представляет текущее состояние ресурса `VirtualMachineMACAddressLease`. + + * Bound - ресурс `VirtualMachineMACAddressLease` привязан к ресурсу `VirtualMachineMACAddress`. + * Released - ресурс `VirtualMachineMACAddressLease` доступен для связки с новым ресурсом `VirtualMachineMACAddress`. + observedGeneration: + description: | + Поколение ресурса, которое в последний раз обрабатывалось контроллером. diff --git a/crds/doc-ru-virtualmachines.yaml b/crds/doc-ru-virtualmachines.yaml index b641e4334..3b1e49e08 100644 --- a/crds/doc-ru-virtualmachines.yaml +++ b/crds/doc-ru-virtualmachines.yaml @@ -532,6 +532,13 @@ spec: Указывается при необходимости использования ранее созданного IP-адреса ВМ. Если не указано явно, по умолчанию для ВМ создается ресурс `virtualMachineIPAddress` с именем аналогичным ресурсу ВМ (`.metadata.name`). + virtualMachineMACAddressName: + description: | + Имя для связанного ресурса `virtualMachineMACAddress`. + + Указывается при необходимости использования ранее созданного MAC-адреса ВМ. + + Если не указано явно, по умолчанию для ВМ создается ресурс `virtualMachineMACAddress` с именем аналогичным ресурсу ВМ (`.metadata.name`). status: properties: blockDeviceRefs: @@ -591,6 +598,9 @@ spec: ipAddress: description: | IP-адрес ВМ. + macAddress: + description: | + MAC-адрес ВМ. nodeName: description: | Имя узла, на котором в данный момент запущена ВМ. @@ -610,6 +620,9 @@ spec: virtualMachineIPAddressName: description: | Имя `virtualMachineIPAddressName`, содержащее IP-адрес виртуальной машины. + virtualMachineMACAddressName: + description: | + Имя `virtualMachineMACAddressName`, содержащее MAC-адрес виртуальной машины. migrationState: description: | Информация о миграции Виртуальной машины. diff --git a/crds/virtualmachinemacaddresses.yaml b/crds/virtualmachinemacaddresses.yaml new file mode 100644 index 000000000..07c62254f --- /dev/null +++ b/crds/virtualmachinemacaddresses.yaml @@ -0,0 +1,159 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: virtualmachinemacaddresses.virtualization.deckhouse.io + labels: + heritage: deckhouse + module: virtualization +spec: + group: virtualization.deckhouse.io + scope: Namespaced + names: + categories: + - all + - virtualization + plural: virtualmachinemacaddresses + singular: virtualmachinemacaddress + kind: VirtualMachineMACAddress + shortNames: + - vmmac + - vmmacs + versions: + - name: v1alpha2 + schema: + openAPIV3Schema: + description: | + The resource that defines MAC address for virtual machine. + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + description: | + The desired state of `VirtualMachineMACAddress`. + properties: + address: + description: | + The requested MAC address that should be assigned to the virtual machine. + type: string + type: object + status: + properties: + conditions: + description: | + The latest available observations of an object's current state. + items: + description: + "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + address: + description: | + The assigned MAC address. + type: string + virtualMachineName: + description: | + Represents the virtual machine that currently uses this MAC address. + type: string + phase: + type: string + enum: + - "Pending" + - "Bound" + - "Attached" + description: | + Represents the current state of MAC address. + + * Pending - the process of creating is in progress. + * Bound - the MAC address is bound to MAC address lease. + * Attached - the MAC address is attached to VirtualMachine. + observedGeneration: + type: integer + description: | + The generation last processed by the controller. + type: object + type: object + served: true + storage: true + subresources: + status: {} + additionalPrinterColumns: + - jsonPath: .status.address + name: Address + type: string + - jsonPath: .status.phase + name: Status + type: string + - jsonPath: .status.virtualMachineName + name: VM + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date diff --git a/crds/virtualmachinemacaddressleases.yaml b/crds/virtualmachinemacaddressleases.yaml new file mode 100644 index 000000000..15ad74632 --- /dev/null +++ b/crds/virtualmachinemacaddressleases.yaml @@ -0,0 +1,159 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: virtualmachinemacaddressleases.virtualization.deckhouse.io + labels: + heritage: deckhouse + module: virtualization +spec: + group: virtualization.deckhouse.io + scope: Cluster + names: + categories: + - virtualization + plural: virtualmachinemacaddressleases + singular: virtualmachinemacaddresslease + kind: VirtualMachineMACAddressLease + shortNames: + - vmmacl + - vmmacls + - vmmaclease + - vmmacleases + versions: + - name: v1alpha2 + schema: + openAPIV3Schema: + description: | + The resource that defines fact of issued lease for `VirtualMachineMACAddress`. + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + description: | + The desired state of `VirtualMachineMACAddressLease`. + properties: + virtualMachineMACAddressRef: + description: | + The link to existing `VirtualMachineMACAddress`. + properties: + name: + description: | + The name of the referenced `VirtualMachineMACAddress`. + type: string + namespace: + description: | + The Namespace of the referenced `VirtualMachineMACAddress`. + type: string + required: + - name + - namespace + type: object + type: object + status: + type: object + properties: + conditions: + description: | + The latest available observations of an object's current state. + items: + description: + "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + phase: + type: string + enum: + - "Bound" + - "Released" + description: | + Represents the current state of issued MAC address lease. + + * Bound - the MAC address lease is bound to MAC address. + * Released - the IP address lease is available for binding. + observedGeneration: + type: integer + description: | + The generation last processed by the controller. + type: object + served: true + storage: true + subresources: + status: {} + additionalPrinterColumns: + - jsonPath: .spec.virtualMachineMACAddressRef + name: VirtualMachineMACAddress + type: string + - jsonPath: .status.phase + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date diff --git a/crds/virtualmachines.yaml b/crds/virtualmachines.yaml index cb2c40924..e32ecea51 100644 --- a/crds/virtualmachines.yaml +++ b/crds/virtualmachines.yaml @@ -173,6 +173,15 @@ spec: If not explicitly specified, by default a `virtualMachineIPAddress` resource is created for the VM with a name similar to the VM resource (`.metadata.name`). + virtualMachineMACAddressName: + type: string + description: | + Name for the associated `virtualMachineMACAddress` resource. + + Specified when it is necessary to use a previously created MAC address of the VM. + + If not explicitly specified, by default a `virtualMachineMACAddress` resource is created for the VM with a name similar to the VM resource (`.metadata.name`). + topologySpreadConstraints: description: | [The same](https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/) as in the pods `spec.topologySpreadConstraints` parameter in Kubernetes; @@ -976,6 +985,14 @@ spec: type: string description: | IP address of VM. + virtualMachineMACAddressName: + type: string + description: | + Name of `virtualMachineMACAddressName` holding the MAC address of the VirtualMachine. + macAddress: + type: string + description: | + MAC address of VM. blockDeviceRefs: type: array description: | @@ -1290,6 +1307,10 @@ spec: jsonPath: .status.ipAddress name: IPAddress type: string + - description: The MAC address of the virtual machine. + jsonPath: .status.macAddress + name: MACAddress + type: string - description: Time of creation resource. jsonPath: .metadata.creationTimestamp name: Age diff --git a/images/virtualization-artifact/cmd/virtualization-controller/main.go b/images/virtualization-artifact/cmd/virtualization-controller/main.go index b5cf24b01..a56eea99b 100644 --- a/images/virtualization-artifact/cmd/virtualization-controller/main.go +++ b/images/virtualization-artifact/cmd/virtualization-controller/main.go @@ -50,6 +50,8 @@ import ( "github.com/deckhouse/virtualization-controller/pkg/controller/vmclass" "github.com/deckhouse/virtualization-controller/pkg/controller/vmip" "github.com/deckhouse/virtualization-controller/pkg/controller/vmiplease" + "github.com/deckhouse/virtualization-controller/pkg/controller/vmmac" + "github.com/deckhouse/virtualization-controller/pkg/controller/vmmaclease" "github.com/deckhouse/virtualization-controller/pkg/controller/vmop" "github.com/deckhouse/virtualization-controller/pkg/controller/vmrestore" "github.com/deckhouse/virtualization-controller/pkg/controller/vmsnapshot" @@ -66,11 +68,14 @@ const ( logLevelEnv = "LOG_LEVEL" logOutputEnv = "LOG_OUTPUT" - metricsBindAddrEnv = "METRICS_BIND_ADDRESS" - podNamespaceEnv = "POD_NAMESPACE" - pprofBindAddrEnv = "PPROF_BIND_ADDRESS" - virtualMachineCIDRsEnv = "VIRTUAL_MACHINE_CIDRS" - virtualMachineIPLeasesRetentionDurationEnv = "VIRTUAL_MACHINE_IP_LEASES_RETENTION_DURATION" + metricsBindAddrEnv = "METRICS_BIND_ADDRESS" + podNamespaceEnv = "POD_NAMESPACE" + pprofBindAddrEnv = "PPROF_BIND_ADDRESS" + virtualMachineCIDRsEnv = "VIRTUAL_MACHINE_CIDRS" + virtualMachineIPLeasesRetentionDurationEnv = "VIRTUAL_MACHINE_IP_LEASES_RETENTION_DURATION" + virtualMachineMACLeasesRetentionDurationEnv = "VIRTUAL_MACHINE_MAC_LEASES_RETENTION_DURATION" + + virtualMachineMACAddressPrefixEnv = "VIRTUAL_MACHINE_MAC_ADDRESS_PREFIX" ) func main() { @@ -201,10 +206,21 @@ func main() { virtualMachineIPLeasesRetentionDuration := os.Getenv(virtualMachineIPLeasesRetentionDurationEnv) if virtualMachineIPLeasesRetentionDuration == "" { - log.Info("virtualMachineIPLeasesRetentionDuration not found -> set default value '10m'") + log.Info("virtualMachineIPLeasesRetentionDuration not found - set default value '10m'") virtualMachineIPLeasesRetentionDuration = "10m" } + virtualMachineMACLeasesRetentionDuration := os.Getenv(virtualMachineMACLeasesRetentionDurationEnv) + if virtualMachineMACLeasesRetentionDuration == "" { + log.Info("virtualMachineMACLeasesRetentionDuration not found - set default value 1 day") + virtualMachineMACLeasesRetentionDuration = "24h" + } + + virtualMachineMACAddressPrefix := os.Getenv(virtualMachineMACAddressPrefixEnv) + if virtualMachineMACAddressPrefix == "" { + log.Info("virtualMachineMACAddressPrefixEnv not found - the MAC address prefix will be generated from the cluster UID") + } + // Create a new Manager to provide shared dependencies and start components mgr, err := manager.New(cfg, managerOpts) if err != nil { @@ -308,6 +324,18 @@ func main() { os.Exit(1) } + vmmacLogger := logger.NewControllerLogger(vmmac.ControllerName, logLevel, logOutput, logDebugVerbosity, logDebugControllerList) + if _, err = vmmac.NewController(ctx, mgr, vmmacLogger, virtualMachineMACAddressPrefix); err != nil { + log.Error(err.Error()) + os.Exit(1) + } + + vmmacleaseLogger := logger.NewControllerLogger(vmmaclease.ControllerName, logLevel, logOutput, logDebugVerbosity, logDebugControllerList) + if _, err = vmmaclease.NewController(ctx, mgr, vmmacleaseLogger, virtualMachineMACLeasesRetentionDuration); err != nil { + log.Error(err.Error()) + os.Exit(1) + } + if err = mc.SetupWebhookWithManager(mgr); err != nil { log.Error(err.Error()) os.Exit(1) diff --git a/images/virtualization-artifact/pkg/common/mac/mac.go b/images/virtualization-artifact/pkg/common/mac/mac.go new file mode 100644 index 000000000..5e3b420ac --- /dev/null +++ b/images/virtualization-artifact/pkg/common/mac/mac.go @@ -0,0 +1,48 @@ +/* +Copyright 2024 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mac + +import ( + "regexp" + "strings" + + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +const macPrefix = "mac-" + +type AllocatedMACs map[string]*virtv2.VirtualMachineMACAddressLease + +// AddressToLeaseName generate the Virtual Machine MAC Address Lease's name from the MAC address +func AddressToLeaseName(address string) string { + return macPrefix + strings.ReplaceAll(address, ":", "-") +} + +// LeaseNameToAddress generate the MAC address from the Virtual Machine MAC Address Lease's name +func LeaseNameToAddress(leaseName string) string { + if strings.HasPrefix(leaseName, macPrefix) && len(leaseName) > len(macPrefix) { + return strings.ReplaceAll(leaseName[len(macPrefix):], "-", ":") + } + + return "" +} + +func IsValidAddressFormat(inputAddress string) bool { + inputAddress = strings.TrimSpace(inputAddress) + re := regexp.MustCompile(`^([0-9A-Fa-f]{2}([-:])){5}([0-9A-Fa-f]{2})$`) + return re.MatchString(inputAddress) +} diff --git a/images/virtualization-artifact/pkg/common/mac/mac_test.go b/images/virtualization-artifact/pkg/common/mac/mac_test.go new file mode 100644 index 000000000..73298c9e0 --- /dev/null +++ b/images/virtualization-artifact/pkg/common/mac/mac_test.go @@ -0,0 +1,76 @@ +/* +Copyright 2025 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 mac + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestMACAddressService(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "MAC Utilities Suite") +} + +var _ = Describe("MAC Utilities", func() { + Context("AddressToLeaseName", func() { + It("should convert MAC address to Lease Name correctly", func() { + address := "00:1A:2B:3C:4D:5E" + expectedLeaseName := "mac-00-1A-2B-3C-4D-5E" + Expect(AddressToLeaseName(address)).To(Equal(expectedLeaseName)) + }) + }) + + Context("LeaseNameToAddress", func() { + It("should convert Lease Name back to MAC address correctly", func() { + leaseName := "mac-00-1A-2B-3C-4D-5E" + expectedAddress := "00:1A:2B:3C:4D:5E" + Expect(LeaseNameToAddress(leaseName)).To(Equal(expectedAddress)) + }) + + It("should return an empty string for invalid Lease Name", func() { + leaseName := "invalid-mac-name" + Expect(LeaseNameToAddress(leaseName)).To(Equal("")) + }) + }) + + Context("IsValidAddressFormat", func() { + It("should return true for a valid MAC address", func() { + address := "00:1A:2B:3C:4D:5E" + Expect(IsValidAddressFormat(address)).To(BeTrue()) + }) + + It("should return false for an invalid MAC address", func() { + address := "00:1G:2B:3C:4D:5E" // Invalid because 'G' is not a valid hex character + Expect(IsValidAddressFormat(address)).To(BeFalse()) + }) + + It("should return false for a MAC address with incorrect length", func() { + addressShort := "00:1A:2B:3C:4D" + addressLong := "00:1A:2B:3C:4D:5E:6F" + Expect(IsValidAddressFormat(addressShort)).To(BeFalse()) + Expect(IsValidAddressFormat(addressLong)).To(BeFalse()) + }) + + It("should return false for a MAC address with special characters", func() { + address := "00:1A:2B:3C:4D:5E!" + Expect(IsValidAddressFormat(address)).To(BeFalse()) + }) + }) +}) diff --git a/images/virtualization-artifact/pkg/controller/indexer/indexer.go b/images/virtualization-artifact/pkg/controller/indexer/indexer.go index 915331238..fe6cabd12 100644 --- a/images/virtualization-artifact/pkg/controller/indexer/indexer.go +++ b/images/virtualization-artifact/pkg/controller/indexer/indexer.go @@ -35,7 +35,8 @@ const ( IndexFieldVMByVI = "spec.blockDeviceRefs.VirtualImage" IndexFieldVMByCVI = "spec.blockDeviceRefs.ClusterVirtualImage" - IndexFieldVMIPLeaseByVMIP = "spec.virtualMachineIPAddressRef.Name" + IndexFieldVMIPLeaseByVMIP = "spec.virtualMachineIPAddressRef.Name" + IndexFieldVMMACLeaseByVMMAC = "spec.virtualMachineMACAddressRef.Name" IndexFieldVDByVDSnapshot = "spec.DataSource.ObjectRef.Name,.Kind=VirtualDiskSnapshot" @@ -47,7 +48,8 @@ const ( IndexFieldVMRestoreByVMSnapshot = "spec.virtualMachineSnapshotName" - IndexFieldVMIPByVM = "status.virtualMachine" + IndexFieldVMIPByVM = "status.virtualMachine,Kind=VirtualMachineIPAddress" + IndexFieldVMMACByVM = "status.virtualMachine,Kind=VirtualMachineMACAddress" IndexFieldVMIPByAddress = "spec.staticIP|status.address" IndexFieldVMBDAByVM = "spec.virtualMachineName" @@ -62,11 +64,13 @@ func IndexALL(ctx context.Context, mgr manager.Manager) error { IndexVMByVI, IndexVMByCVI, IndexVMIPLeaseByVMIP, + IndexVMMACLeaseByVMMAC, IndexVDByVDSnapshot, IndexVMSnapshotByVM, IndexVMSnapshotByVDSnapshot, IndexVMRestoreByVMSnapshot, IndexVMIPByVM, + IndexVMMACByVM, IndexVDByStorageClass, IndexVIByStorageClass, IndexVMIPByAddress, @@ -121,13 +125,3 @@ func getBlockDeviceNamesByKind(obj client.Object, kind virtv2.BlockDeviceKind) [ } return res } - -func IndexVMIPLeaseByVMIP(ctx context.Context, mgr manager.Manager) error { - return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.VirtualMachineIPAddressLease{}, IndexFieldVMIPLeaseByVMIP, func(object client.Object) []string { - lease, ok := object.(*virtv2.VirtualMachineIPAddressLease) - if !ok || lease == nil { - return nil - } - return []string{lease.Spec.VirtualMachineIPAddressRef.Name} - }) -} diff --git a/images/virtualization-artifact/pkg/controller/indexer/lease_indexer.go b/images/virtualization-artifact/pkg/controller/indexer/lease_indexer.go new file mode 100644 index 000000000..5a41cb10a --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/indexer/lease_indexer.go @@ -0,0 +1,46 @@ +/* +Copyright 2024 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package indexer + +import ( + "context" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" + + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +func IndexVMIPLeaseByVMIP(ctx context.Context, mgr manager.Manager) error { + return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.VirtualMachineIPAddressLease{}, IndexFieldVMIPLeaseByVMIP, func(object client.Object) []string { + lease, ok := object.(*virtv2.VirtualMachineIPAddressLease) + if !ok || lease == nil { + return nil + } + return []string{lease.Spec.VirtualMachineIPAddressRef.Name} + }) +} + +func IndexVMMACLeaseByVMMAC(ctx context.Context, mgr manager.Manager) error { + return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.VirtualMachineMACAddressLease{}, IndexFieldVMMACLeaseByVMMAC, func(object client.Object) []string { + lease, ok := object.(*virtv2.VirtualMachineMACAddressLease) + if !ok || lease == nil { + return nil + } + return []string{lease.Spec.VirtualMachineMACAddressRef.Name} + }) +} diff --git a/images/virtualization-artifact/pkg/controller/indexer/vmmac_indexer.go b/images/virtualization-artifact/pkg/controller/indexer/vmmac_indexer.go new file mode 100644 index 000000000..ab1d28ff4 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/indexer/vmmac_indexer.go @@ -0,0 +1,36 @@ +/* +Copyright 2024 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package indexer + +import ( + "context" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" + + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +func IndexVMMACByVM(ctx context.Context, mgr manager.Manager) error { + return mgr.GetFieldIndexer().IndexField(ctx, &virtv2.VirtualMachineMACAddress{}, IndexFieldVMMACByVM, func(object client.Object) []string { + vmmac, ok := object.(*virtv2.VirtualMachineMACAddress) + if !ok || vmmac == nil { + return nil + } + return []string{vmmac.Status.VirtualMachine} + }) +} diff --git a/images/virtualization-artifact/pkg/controller/kvbuilder/kvvm_utils.go b/images/virtualization-artifact/pkg/controller/kvbuilder/kvvm_utils.go index ee8798b32..34825676b 100644 --- a/images/virtualization-artifact/pkg/controller/kvbuilder/kvvm_utils.go +++ b/images/virtualization-artifact/pkg/controller/kvbuilder/kvvm_utils.go @@ -26,7 +26,7 @@ import ( "github.com/deckhouse/virtualization-controller/pkg/common" "github.com/deckhouse/virtualization-controller/pkg/common/imageformat" "github.com/deckhouse/virtualization-controller/pkg/common/pointer" - "github.com/deckhouse/virtualization-controller/pkg/controller/ipam" + "github.com/deckhouse/virtualization-controller/pkg/controller/netmanager" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" ) @@ -216,7 +216,7 @@ func ApplyVirtualMachineSpec( kvvm.AddFinalizer(virtv2.FinalizerKVVMProtection) // Set ip address cni request annotation. - kvvm.SetKVVMIAnnotation(ipam.AnnoIPAddressCNIRequest, ipAddress) + kvvm.SetKVVMIAnnotation(netmanager.AnnoIPAddressCNIRequest, ipAddress) // Set live migration annotation. kvvm.SetKVVMIAnnotation(virtv1.AllowPodBridgeNetworkLiveMigrationAnnotation, "true") diff --git a/images/virtualization-artifact/pkg/controller/ipam/ipam.go b/images/virtualization-artifact/pkg/controller/netmanager/ipam.go similarity index 98% rename from images/virtualization-artifact/pkg/controller/ipam/ipam.go rename to images/virtualization-artifact/pkg/controller/netmanager/ipam.go index a2199fd3d..1aba295ac 100644 --- a/images/virtualization-artifact/pkg/controller/ipam/ipam.go +++ b/images/virtualization-artifact/pkg/controller/netmanager/ipam.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package ipam +package netmanager import ( "context" @@ -30,7 +30,7 @@ import ( const AnnoIPAddressCNIRequest = "cni.cilium.io/ipAddress" -func New() *IPAM { +func NewIPAM() *IPAM { return &IPAM{} } diff --git a/images/virtualization-artifact/pkg/controller/netmanager/macam.go b/images/virtualization-artifact/pkg/controller/netmanager/macam.go new file mode 100644 index 000000000..e92e051ad --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/netmanager/macam.go @@ -0,0 +1,88 @@ +/* +Copyright 2024 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package netmanager + +import ( + "context" + "errors" + "strings" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/deckhouse/virtualization-controller/pkg/common/annotations" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +func NewMACAM() *MACAM { + return &MACAM{} +} + +type MACAM struct{} + +func (m MACAM) IsBound(vmName string, vmmac *virtv2.VirtualMachineMACAddress) bool { + if vmmac == nil { + return false + } + + if vmmac.Status.Phase != virtv2.VirtualMachineMACAddressPhaseBound && vmmac.Status.Phase != virtv2.VirtualMachineMACAddressPhaseAttached { + return false + } + + return vmmac.Status.VirtualMachine == vmName +} + +func (m MACAM) CheckMACAddressAvailableForBinding(vmmac *virtv2.VirtualMachineMACAddress) error { + if vmmac == nil { + return errors.New("cannot to bind with empty MAC address") + } + + return nil +} + +func (m MACAM) CreateMACAddress(ctx context.Context, vm *virtv2.VirtualMachine, client client.Client) error { + ownerRef := metav1.NewControllerRef(vm, vm.GroupVersionKind()) + return client.Create(ctx, &virtv2.VirtualMachineMACAddress{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + annotations.LabelVirtualMachineUID: string(vm.GetUID()), + }, + GenerateName: GenerateName(vm), + Namespace: vm.Namespace, + OwnerReferences: []metav1.OwnerReference{*ownerRef}, + }, + Spec: virtv2.VirtualMachineMACAddressSpec{}, + }) +} + +func GetVirtualMachineNameFromVMMAC(vmmac *virtv2.VirtualMachineMACAddress) string { + if vmmac == nil { + return "" + } + if gn := vmmac.GenerateName; gn != "" { + return strings.TrimSuffix(vmmac.GenerateName, generateNameSuffix) + } + + name := vmmac.GetName() + for _, ow := range vmmac.GetOwnerReferences() { + if ow.Kind == virtv2.VirtualMachineKind { + name = ow.Name + break + } + } + return name +} diff --git a/images/virtualization-artifact/pkg/controller/service/errors.go b/images/virtualization-artifact/pkg/controller/service/errors.go index b4dc97129..62625eb0c 100644 --- a/images/virtualization-artifact/pkg/controller/service/errors.go +++ b/images/virtualization-artifact/pkg/controller/service/errors.go @@ -35,6 +35,11 @@ var ( ErrIPAddressOutOfRange = errors.New("the IP address is out of range") ) +var ( + ErrMACAddressAlreadyExist = errors.New("the MAC address is already allocated") + ErrMACAddressOutOfRange = errors.New("the MAC address is out of range") +) + type VirtualDiskUsedByImageError struct { vdName string } diff --git a/images/virtualization-artifact/pkg/controller/service/ip_address_service_test.go b/images/virtualization-artifact/pkg/controller/service/ip_address_service_test.go index d22bd4ce8..3d545c70a 100644 --- a/images/virtualization-artifact/pkg/controller/service/ip_address_service_test.go +++ b/images/virtualization-artifact/pkg/controller/service/ip_address_service_test.go @@ -23,6 +23,7 @@ import ( . "github.com/onsi/gomega" "github.com/deckhouse/deckhouse/pkg/log" + "github.com/deckhouse/virtualization-controller/pkg/common/ip" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" ) @@ -84,7 +85,7 @@ var _ = Describe("IpAddressService", func() { }) }) - Describe("AllocateNewIP", func() { + Describe("AllocateNewAddress", func() { Context("when there are available IP addresses in the range", func() { It("should allocate a new IP address", func() { ip, err := ipService.AllocateNewIP(allocatedIPs) diff --git a/images/virtualization-artifact/pkg/controller/service/mac_address_service.go b/images/virtualization-artifact/pkg/controller/service/mac_address_service.go new file mode 100644 index 000000000..765e9688f --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/service/mac_address_service.go @@ -0,0 +1,100 @@ +/* +Copyright 2024 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "errors" + "fmt" + "math/rand" + "regexp" + "strings" + "time" + + "github.com/deckhouse/virtualization-controller/pkg/common/mac" +) + +const MaxCount int = 65536 + +type MACAddressService struct { + prefix string +} + +func NewMACAddressService( + prefix string, +) *MACAddressService { + if prefix == "" { + //todo dlopatin add generate from cluster uid + prefix = "f6:e1:74:94" + } + + return &MACAddressService{ + prefix: prefix, + } +} + +func (s MACAddressService) IsAvailableAddress(address string, allocatedMACs mac.AllocatedMACs) error { + if !mac.IsValidAddressFormat(address) { + return errors.New("invalid MAC address format") + } + + if _, ok := allocatedMACs[address]; ok { + // already exists + return ErrMACAddressAlreadyExist + } + + if address[:11] == s.prefix { + return nil + } + + return ErrMACAddressOutOfRange +} + +func formatPrefix(prefix string) (string, error) { + prefix = strings.TrimSpace(prefix) + + re := regexp.MustCompile(`(?i)([0-9A-Fa-f]{2})`) + matches := re.FindAllString(prefix, -1) + + if len(matches) != 4 { + return "", fmt.Errorf("wrong format MAC address prefix") + } + + return fmt.Sprintf("%s:%s:%s:%s", matches[0], matches[1], matches[2], matches[3]), nil +} + +func (s MACAddressService) AllocateNewAddress(allocatedMACs mac.AllocatedMACs) (string, error) { + prefix, err := formatPrefix(s.prefix) + if err != nil { + return "", err + } + + r := rand.New(rand.NewSource(time.Now().UnixNano())) + retry := 0 + maxRetries := MaxCount - len(allocatedMACs) + + for retry < maxRetries { + genAddress := fmt.Sprintf("%s:%02X:%02X", prefix, r.Intn(256), r.Intn(256)) + + if _, ok := allocatedMACs[genAddress]; !ok { + return genAddress, nil + } + + retry++ + } + + return "", errors.New("no remaining MAC addresses") +} diff --git a/images/virtualization-artifact/pkg/controller/service/mac_address_service_test.go b/images/virtualization-artifact/pkg/controller/service/mac_address_service_test.go new file mode 100644 index 000000000..8d3d2053a --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/service/mac_address_service_test.go @@ -0,0 +1,132 @@ +/* +Copyright 2024 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "fmt" + + "github.com/deckhouse/virtualization-controller/pkg/common/mac" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("MACAddressService", func() { + var ( + service *MACAddressService + allocatedMACs mac.AllocatedMACs + ) + + BeforeEach(func() { + allocatedMACs = make(mac.AllocatedMACs) + service = NewMACAddressService("f6:e1:74:94") + }) + + Context("IsAvailableAddress", func() { + It("should return error for an invalid MAC address format", func() { + err := service.IsAvailableAddress("invalid-mac", allocatedMACs) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal("invalid MAC address format")) + }) + + It("should return error for a duplicate MAC address", func() { + ref := virtv2.VirtualMachineMACAddressLeaseMACAddressRef{ + Name: "test", + Namespace: "test", + } + + spec := virtv2.VirtualMachineMACAddressLeaseSpec{ + VirtualMachineMACAddressRef: &ref, + } + + lease := &virtv2.VirtualMachineMACAddressLease{ + Spec: spec, + } + + allocatedMACs["f6:e1:74:94:AB:CD"] = lease + err := service.IsAvailableAddress("f6:e1:74:94:AB:CD", allocatedMACs) + Expect(err).To(HaveOccurred()) + Expect(err).To(Equal(ErrMACAddressAlreadyExist)) + }) + + It("should return error for a MAC address out of prefix range", func() { + err := service.IsAvailableAddress("00:11:22:33:44:55", allocatedMACs) + Expect(err).To(HaveOccurred()) + Expect(err).To(Equal(ErrMACAddressOutOfRange)) + }) + + It("should return nil for a valid MAC address", func() { + err := service.IsAvailableAddress("f6:e1:74:94:12:34", allocatedMACs) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Context("AllocateNewAddress", func() { + It("should allocate a new unique MAC address with format prefix xx:xx:xx:xx", func() { + address, err := service.AllocateNewAddress(allocatedMACs) + Expect(err).NotTo(HaveOccurred()) + Expect(address).To(HavePrefix("f6:e1:74:94")) + }) + + It("should allocate a new unique MAC address with format prefix xx-xx-xx-xx", func() { + service := NewMACAddressService("f6-e1-74-94") + address, err := service.AllocateNewAddress(allocatedMACs) + Expect(err).NotTo(HaveOccurred()) + Expect(address).To(HavePrefix("f6:e1:74:94")) + }) + + It("should allocate a new unique MAC address with format prefix xxxxxxxx", func() { + service := NewMACAddressService("f6e17494") + address, err := service.AllocateNewAddress(allocatedMACs) + Expect(err).NotTo(HaveOccurred()) + Expect(address).To(HavePrefix("f6:e1:74:94")) + }) + + It("should return an error when MAC addresses prefix wrong", func() { + service := NewMACAddressService("f6e1749") + _, err := service.AllocateNewAddress(allocatedMACs) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal("wrong format MAC address prefix")) + }) + + It("should return an error when no MAC addresses are available", func() { + for i := 0; i < MaxCount; i++ { + testRefName := fmt.Sprintf("test-%d", i) + ref := virtv2.VirtualMachineMACAddressLeaseMACAddressRef{ + Name: testRefName, + Namespace: testRefName, + } + + spec := virtv2.VirtualMachineMACAddressLeaseSpec{ + VirtualMachineMACAddressRef: &ref, + } + + lease := &virtv2.VirtualMachineMACAddressLease{ + Spec: spec, + } + + allocatedMACs[fmt.Sprintf("f6:e1:74:94:%02X:%02X", i/256, i%256)] = lease + } + + address, err := service.AllocateNewAddress(allocatedMACs) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal("no remaining MAC addresses")) + Expect(address).To(BeEmpty()) + }) + }) +}) diff --git a/images/virtualization-artifact/pkg/controller/vm/internal/macam.go b/images/virtualization-artifact/pkg/controller/vm/internal/macam.go new file mode 100644 index 000000000..e08cb8f17 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vm/internal/macam.go @@ -0,0 +1,160 @@ +/* +Copyright 2024 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package internal + +import ( + "context" + "fmt" + "log/slog" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" + "github.com/deckhouse/virtualization-controller/pkg/controller/service" + "github.com/deckhouse/virtualization-controller/pkg/controller/vm/internal/state" + "github.com/deckhouse/virtualization-controller/pkg/eventrecord" + "github.com/deckhouse/virtualization-controller/pkg/logger" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vmcondition" +) + +const nameMACAMHandler = "MACAMHandler" + +type MACAM interface { + IsBound(vmName string, vmmac *virtv2.VirtualMachineMACAddress) bool + CheckMACAddressAvailableForBinding(vmmac *virtv2.VirtualMachineMACAddress) error + CreateMACAddress(ctx context.Context, vm *virtv2.VirtualMachine, client client.Client) error +} + +func NewMACAMHandler(macam MACAM, cl client.Client, recorder eventrecord.EventRecorderLogger) *MACAMHandler { + return &MACAMHandler{ + macam: macam, + client: cl, + recorder: recorder, + protection: service.NewProtectionService(cl, virtv2.FinalizerMACAddressProtection), + } +} + +type MACAMHandler struct { + macam MACAM + client client.Client + recorder eventrecord.EventRecorderLogger + protection *service.ProtectionService +} + +func (h *MACAMHandler) Handle(ctx context.Context, s state.VirtualMachineState) (reconcile.Result, error) { + log := logger.FromContext(ctx).With(logger.SlogHandler(nameMACAMHandler)) + + if s.VirtualMachine().IsEmpty() { + return reconcile.Result{}, nil + } + current := s.VirtualMachine().Current() + changed := s.VirtualMachine().Changed() + + if update := addAllUnknown(changed, vmcondition.TypeMACAddressReady); update { + return reconcile.Result{Requeue: true}, nil + } + + //nolint:staticcheck + mgr := conditions.NewManager(changed.Status.Conditions) + cb := conditions.NewConditionBuilder(vmcondition.TypeMACAddressReady). + Generation(current.GetGeneration()) + + macAddress, err := s.MACAddress(ctx) + if err != nil { + return reconcile.Result{}, err + } + + if isDeletion(current) { + return reconcile.Result{}, h.protection.RemoveProtection(ctx, macAddress) + } + err = h.protection.AddProtection(ctx, macAddress) + if err != nil { + return reconcile.Result{}, err + } + + // 1. OK: already bound. + if h.macam.IsBound(current.GetName(), macAddress) { + mgr.Update(cb.Status(metav1.ConditionTrue). + Reason(vmcondition.ReasonMACAddressReady). + Condition()) + changed.Status.VirtualMachineMACAddress = macAddress.GetName() + if changed.Status.Phase != virtv2.MachineRunning && changed.Status.Phase != virtv2.MachineStopping { + changed.Status.MACAddress = macAddress.Status.Address + } + + // todo dlopatin add check mac address on kvvmi and update condition status + + changed.Status.Conditions = mgr.Generate() + return reconcile.Result{}, nil + } + + cb.Status(metav1.ConditionFalse) + + // 2. MAC address not found: create if possible or wait for the MAC address. + if macAddress == nil { + cb.Reason(vmcondition.ReasonMACAddressNotReady) + if current.Spec.VirtualMachineMACAddress != "" { + log.Info(fmt.Sprintf("The requested MAC address (%s) for the virtual machine not found: waiting for the MAC address", current.Spec.VirtualMachineMACAddress)) + cb.Message(fmt.Sprintf("The requested MAC address (%s) for the virtual machine not found: waiting for the MAC address", current.Spec.VirtualMachineMACAddress)) + mgr.Update(cb.Condition()) + changed.Status.Conditions = mgr.Generate() + return reconcile.Result{}, nil + } + log.Info("VirtualMachineMACAddress not found: create the new one", slog.String("vmmacName", current.GetName())) + cb.Message(fmt.Sprintf("VirtualMachineMACAddress %q not found: it may be in the process of being created", current.GetName())) + mgr.Update(cb.Condition()) + changed.Status.Conditions = mgr.Generate() + return reconcile.Result{}, h.macam.CreateMACAddress(ctx, changed, h.client) + } + + // 3. Check if possible to bind virtual machine with the found MAC address. + err = h.macam.CheckMACAddressAvailableForBinding(macAddress) + if err != nil { + log.Info("MAC address is not available to be bound", "err", err, "vmmacName", current.Spec.VirtualMachineMACAddress) + reason := vmcondition.ReasonMACAddressNotAvailable + mgr.Update(cb.Reason(reason).Message(err.Error()).Condition()) + changed.Status.Conditions = mgr.Generate() + h.recorder.Event(changed, corev1.EventTypeWarning, reason.String(), err.Error()) + return reconcile.Result{}, nil + } + + // 4. MAC address exist and attached to another VirtualMachine + if macAddress.Status.VirtualMachine != "" && macAddress.Status.VirtualMachine != changed.Name { + msg := fmt.Sprintf("The requested MAC address (%s) attached to VirtualMachine '%s': waiting for the MAC address", current.Spec.VirtualMachineMACAddress, macAddress.Status.VirtualMachine) + log.Info(msg) + mgr.Update(cb.Reason(vmcondition.ReasonMACAddressNotReady). + Message(msg).Condition()) + changed.Status.Conditions = mgr.Generate() + return reconcile.Result{}, nil + } + + // 5. MAC address exists and available for binding with virtual machine: waiting for the MAC address. + log.Info("Waiting for the MAC address to be bound to VM", "vmmacName", current.Spec.VirtualMachineMACAddress) + mgr.Update(cb.Reason(vmcondition.ReasonMACAddressNotReady). + Message("MAC address not bound: waiting for the MAC address").Condition()) + changed.Status.Conditions = mgr.Generate() + + return reconcile.Result{}, nil +} + +func (h *MACAMHandler) Name() string { + return nameMACAMHandler +} diff --git a/images/virtualization-artifact/pkg/controller/vm/internal/state/state.go b/images/virtualization-artifact/pkg/controller/vm/internal/state/state.go index b14d41fdd..f665f1fe1 100644 --- a/images/virtualization-artifact/pkg/controller/vm/internal/state/state.go +++ b/images/virtualization-artifact/pkg/controller/vm/internal/state/state.go @@ -48,6 +48,7 @@ type VirtualMachineState interface { VirtualImagesByName(ctx context.Context) (map[string]*virtv2.VirtualImage, error) ClusterVirtualImagesByName(ctx context.Context) (map[string]*virtv2.ClusterVirtualImage, error) IPAddress(ctx context.Context) (*virtv2.VirtualMachineIPAddress, error) + MACAddress(ctx context.Context) (*virtv2.VirtualMachineMACAddress, error) Class(ctx context.Context) (*virtv2.VirtualMachineClass, error) Shared(fn func(s *Shared)) } @@ -57,19 +58,20 @@ func New(c client.Client, vm *service.Resource[*virtv2.VirtualMachine, virtv2.Vi } type state struct { - client client.Client - mu sync.RWMutex - vm *service.Resource[*virtv2.VirtualMachine, virtv2.VirtualMachineStatus] - kvvm *virtv1.VirtualMachine - kvvmi *virtv1.VirtualMachineInstance - pods *corev1.PodList - pod *corev1.Pod - vdByName map[string]*virtv2.VirtualDisk - viByName map[string]*virtv2.VirtualImage - cviByName map[string]*virtv2.ClusterVirtualImage - ipAddress *virtv2.VirtualMachineIPAddress - vmClass *virtv2.VirtualMachineClass - shared Shared + client client.Client + mu sync.RWMutex + vm *service.Resource[*virtv2.VirtualMachine, virtv2.VirtualMachineStatus] + kvvm *virtv1.VirtualMachine + kvvmi *virtv1.VirtualMachineInstance + pods *corev1.PodList + pod *corev1.Pod + vdByName map[string]*virtv2.VirtualDisk + viByName map[string]*virtv2.VirtualImage + cviByName map[string]*virtv2.ClusterVirtualImage + ipAddress *virtv2.VirtualMachineIPAddress + macAddress *virtv2.VirtualMachineMACAddress + vmClass *virtv2.VirtualMachineClass + shared Shared } type Shared struct { @@ -289,6 +291,48 @@ func (s *state) ClusterVirtualImagesByName(ctx context.Context) (map[string]*vir return cviByName, nil } +func (s *state) MACAddress(ctx context.Context) (*virtv2.VirtualMachineMACAddress, error) { + if s.vm == nil { + return nil, nil + } + + if s.macAddress != nil { + return s.macAddress, nil + } + s.mu.Lock() + defer s.mu.Unlock() + + vmmacName := s.vm.Current().Spec.VirtualMachineMACAddress + if vmmacName == "" { + vmmacList := &virtv2.VirtualMachineMACAddressList{} + + err := s.client.List(ctx, vmmacList, &client.ListOptions{ + Namespace: s.vm.Current().GetNamespace(), + LabelSelector: labels.SelectorFromSet(map[string]string{annotations.LabelVirtualMachineUID: string(s.vm.Current().GetUID())}), + }) + if err != nil { + return nil, fmt.Errorf("failed to list VirtualMachineMACAddress: %w", err) + } + + if len(vmmacList.Items) == 0 { + // TODO add search for resource by owner ref + return nil, nil + } + + s.macAddress = &vmmacList.Items[0] + } else { + vmmacKey := types.NamespacedName{Name: vmmacName, Namespace: s.vm.Current().GetNamespace()} + + macAddress, err := object.FetchObject(ctx, vmmacKey, s.client, &virtv2.VirtualMachineMACAddress{}) + if err != nil { + return nil, fmt.Errorf("failed to fetch VirtualMachineMACAddress: %w", err) + } + s.macAddress = macAddress + } + + return s.macAddress, nil +} + func (s *state) IPAddress(ctx context.Context) (*virtv2.VirtualMachineIPAddress, error) { if s.vm == nil { return nil, nil diff --git a/images/virtualization-artifact/pkg/controller/vm/vm_controller.go b/images/virtualization-artifact/pkg/controller/vm/vm_controller.go index 88b7b813a..aeaae7aa4 100644 --- a/images/virtualization-artifact/pkg/controller/vm/vm_controller.go +++ b/images/virtualization-artifact/pkg/controller/vm/vm_controller.go @@ -27,7 +27,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/metrics" "github.com/deckhouse/deckhouse/pkg/log" - "github.com/deckhouse/virtualization-controller/pkg/controller/ipam" + + "github.com/deckhouse/virtualization-controller/pkg/controller/netmanager" "github.com/deckhouse/virtualization-controller/pkg/controller/service" "github.com/deckhouse/virtualization-controller/pkg/controller/vm/internal" "github.com/deckhouse/virtualization-controller/pkg/dvcr" @@ -54,7 +55,8 @@ func SetupController( handlers := []Handler{ internal.NewDeletionHandler(client), internal.NewClassHandler(client, recorder), - internal.NewIPAMHandler(ipam.New(), client, recorder), + internal.NewIPAMHandler(netmanager.NewIPAM(), client, recorder), + internal.NewMACAMHandler(netmanager.NewMACAM(), client, recorder), internal.NewBlockDeviceHandler(client, recorder), internal.NewBlockDeviceLimiterHandler(blockDeviceService), internal.NewProvisioningHandler(client), @@ -87,7 +89,7 @@ func SetupController( if err = builder.WebhookManagedBy(mgr). For(&v1alpha2.VirtualMachine{}). - WithValidator(NewValidator(ipam.New(), client, blockDeviceService, log)). + WithValidator(NewValidator(netmanager.NewIPAM(), client, blockDeviceService, log)). Complete(); err != nil { return err } diff --git a/images/virtualization-artifact/pkg/controller/vm/vm_reconciler.go b/images/virtualization-artifact/pkg/controller/vm/vm_reconciler.go index b6e55b9cd..c46029e12 100644 --- a/images/virtualization-artifact/pkg/controller/vm/vm_reconciler.go +++ b/images/virtualization-artifact/pkg/controller/vm/vm_reconciler.go @@ -185,6 +185,41 @@ func (r *Reconciler) SetupController(_ context.Context, mgr manager.Manager, ctr return fmt.Errorf("error setting watch on VirtualMachineIpAddress: %w", err) } + // Subscribe on VirtualMachineMACAddress. + if err := ctr.Watch( + source.Kind(mgr.GetCache(), &virtv2.VirtualMachineMACAddress{}), + handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request { + vmmac, ok := obj.(*virtv2.VirtualMachineMACAddress) + if !ok { + return nil + } + name := vmmac.Status.VirtualMachine + if name == "" { + return nil + } + return []reconcile.Request{ + { + NamespacedName: types.NamespacedName{ + Name: name, + Namespace: vmmac.GetNamespace(), + }, + }, + } + }), + predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { return true }, + DeleteFunc: func(e event.DeleteEvent) bool { return true }, + UpdateFunc: func(e event.UpdateEvent) bool { + oldVmmac := e.ObjectOld.(*virtv2.VirtualMachineMACAddress) + newVmmac := e.ObjectNew.(*virtv2.VirtualMachineMACAddress) + return oldVmmac.Status.Phase != newVmmac.Status.Phase || + oldVmmac.Status.VirtualMachine != newVmmac.Status.VirtualMachine + }, + }, + ); err != nil { + return fmt.Errorf("error setting watch on VirtualMachineMACAddress: %w", err) + } + // Subscribe on VirtualImage. if err := ctr.Watch( source.Kind(mgr.GetCache(), &virtv2.VirtualImage{}), diff --git a/images/virtualization-artifact/pkg/controller/vmip/internal/lifecycle_handler.go b/images/virtualization-artifact/pkg/controller/vmip/internal/lifecycle_handler.go index 3bcd8e066..d4936feeb 100644 --- a/images/virtualization-artifact/pkg/controller/vmip/internal/lifecycle_handler.go +++ b/images/virtualization-artifact/pkg/controller/vmip/internal/lifecycle_handler.go @@ -82,66 +82,54 @@ func (h *LifecycleHandler) Handle(ctx context.Context, state state.VMIPState) (r needRequeue := false switch { case lease == nil && vmipStatus.Address != "": - if vmipStatus.Phase != virtv2.VirtualMachineIPAddressPhasePending { - vmipStatus.Phase = virtv2.VirtualMachineIPAddressPhasePending - conditionBound.Status(metav1.ConditionFalse). - Reason(vmipcondition.VirtualMachineIPAddressLeaseLost). - Message(fmt.Sprintf("VirtualMachineIPAddressLease %s doesn't exist", - ip.IpToLeaseName(vmipStatus.Address))) - } + vmipStatus.Phase = virtv2.VirtualMachineIPAddressPhasePending + conditionBound.Status(metav1.ConditionFalse). + Reason(vmipcondition.VirtualMachineIPAddressLeaseLost). + Message(fmt.Sprintf("VirtualMachineIPAddressLease %s doesn't exist", + ip.IpToLeaseName(vmipStatus.Address))) case lease == nil: - if vmipStatus.Phase != virtv2.VirtualMachineIPAddressPhasePending { - vmipStatus.Phase = virtv2.VirtualMachineIPAddressPhasePending - conditionBound.Status(metav1.ConditionFalse). - Reason(vmipcondition.VirtualMachineIPAddressLeaseNotFound). - Message("VirtualMachineIPAddressLease is not found") - } + vmipStatus.Phase = virtv2.VirtualMachineIPAddressPhasePending + conditionBound.Status(metav1.ConditionFalse). + Reason(vmipcondition.VirtualMachineIPAddressLeaseNotFound). + Message("VirtualMachineIPAddressLease is not found") case vm != nil && vm.GetDeletionTimestamp().IsZero(): - if vmipStatus.Phase != virtv2.VirtualMachineIPAddressPhaseAttached { - vmipStatus.Phase = virtv2.VirtualMachineIPAddressPhaseAttached - vmipStatus.VirtualMachine = vm.Name - conditionAttach.Status(metav1.ConditionTrue). - Reason(vmipcondition.Attached) - } + vmipStatus.Phase = virtv2.VirtualMachineIPAddressPhaseAttached + vmipStatus.VirtualMachine = vm.Name + conditionAttach.Status(metav1.ConditionTrue). + Reason(vmipcondition.Attached) case util.IsBoundLease(lease, vmip): - if vmipStatus.Phase != virtv2.VirtualMachineIPAddressPhaseBound { - vmipStatus.Phase = virtv2.VirtualMachineIPAddressPhaseBound - vmipStatus.Address = ip.LeaseNameToIP(lease.Name) - conditionBound.Status(metav1.ConditionTrue). - Reason(vmipcondition.Bound) - } + vmipStatus.Phase = virtv2.VirtualMachineIPAddressPhaseBound + vmipStatus.Address = ip.LeaseNameToIP(lease.Name) + conditionBound.Status(metav1.ConditionTrue). + Reason(vmipcondition.Bound) case lease.Status.Phase == virtv2.VirtualMachineIPAddressLeasePhaseBound: - if vmipStatus.Phase != virtv2.VirtualMachineIPAddressPhasePending { - vmipStatus.Phase = virtv2.VirtualMachineIPAddressPhasePending - log.Warn(fmt.Sprintf("VirtualMachineIPAddressLease %s is bound to another VirtualMachineIPAddress resource: %s/%s", - lease.Name, lease.Spec.VirtualMachineIPAddressRef.Name, lease.Spec.VirtualMachineIPAddressRef.Namespace)) - conditionBound.Status(metav1.ConditionFalse). - Reason(vmipcondition.VirtualMachineIPAddressLeaseAlreadyExists). - Message(fmt.Sprintf("VirtualMachineIPAddressLease %s is bound to another VirtualMachineIPAddress resource", - lease.Name)) - } + vmipStatus.Phase = virtv2.VirtualMachineIPAddressPhasePending + log.Warn(fmt.Sprintf("VirtualMachineIPAddressLease %s is bound to another VirtualMachineIPAddress resource: %s/%s", + lease.Name, lease.Spec.VirtualMachineIPAddressRef.Name, lease.Spec.VirtualMachineIPAddressRef.Namespace)) + conditionBound.Status(metav1.ConditionFalse). + Reason(vmipcondition.VirtualMachineIPAddressLeaseAlreadyExists). + Message(fmt.Sprintf("VirtualMachineIPAddressLease %s is bound to another VirtualMachineIPAddress resource", + lease.Name)) case lease.Spec.VirtualMachineIPAddressRef.Namespace != vmip.Namespace: - if vmipStatus.Phase != virtv2.VirtualMachineIPAddressPhasePending { - vmipStatus.Phase = virtv2.VirtualMachineIPAddressPhasePending - conditionBound.Status(metav1.ConditionFalse). - Reason(vmipcondition.VirtualMachineIPAddressLeaseAlreadyExists). - Message(fmt.Sprintf("The VirtualMachineIPLease %s belongs to a different namespace", lease.Name)) - } + vmipStatus.Phase = virtv2.VirtualMachineIPAddressPhasePending + conditionBound.Status(metav1.ConditionFalse). + Reason(vmipcondition.VirtualMachineIPAddressLeaseAlreadyExists). + Message(fmt.Sprintf("The VirtualMachineIPLease %s belongs to a different namespace", lease.Name)) + needRequeue = true default: - if vmipStatus.Phase != virtv2.VirtualMachineIPAddressPhasePending { - vmipStatus.Phase = virtv2.VirtualMachineIPAddressPhasePending - conditionBound.Status(metav1.ConditionFalse). - Reason(vmipcondition.VirtualMachineIPAddressLeaseNotReady). - Message(fmt.Sprintf("VirtualMachineIPAddressLease %s is not ready", - lease.Name)) - } + vmipStatus.Phase = virtv2.VirtualMachineIPAddressPhasePending + conditionBound.Status(metav1.ConditionFalse). + Reason(vmipcondition.VirtualMachineIPAddressLeaseNotReady). + Message(fmt.Sprintf("VirtualMachineIPAddressLease %s is not ready", + lease.Name)) + } log.Debug("Set VirtualMachineIP phase", "phase", vmipStatus.Phase) diff --git a/images/virtualization-artifact/pkg/controller/vmip/internal/state/state.go b/images/virtualization-artifact/pkg/controller/vmip/internal/state/state.go index 2304c938b..e17450ccb 100644 --- a/images/virtualization-artifact/pkg/controller/vmip/internal/state/state.go +++ b/images/virtualization-artifact/pkg/controller/vmip/internal/state/state.go @@ -28,7 +28,7 @@ import ( "github.com/deckhouse/virtualization-controller/pkg/common/object" "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" "github.com/deckhouse/virtualization-controller/pkg/controller/indexer" - "github.com/deckhouse/virtualization-controller/pkg/controller/ipam" + "github.com/deckhouse/virtualization-controller/pkg/controller/netmanager" "github.com/deckhouse/virtualization-controller/pkg/controller/vmip/internal/util" virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/api/core/v1alpha2/vmiplcondition" @@ -137,7 +137,7 @@ func (s *state) VirtualMachine(ctx context.Context) (*virtv2.VirtualMachine, err } for i, vm := range vms.Items { - if vm.Spec.VirtualMachineIPAddress == s.vmip.Name || vm.Spec.VirtualMachineIPAddress == "" && vm.Name == ipam.GetVirtualMachineName(s.vmip) { + if vm.Spec.VirtualMachineIPAddress == s.vmip.Name || vm.Spec.VirtualMachineIPAddress == "" && vm.Name == netmanager.GetVirtualMachineName(s.vmip) { s.vm = &vms.Items[i] break } diff --git a/images/virtualization-artifact/pkg/controller/vmiplease/internal/lifecycle_handler.go b/images/virtualization-artifact/pkg/controller/vmiplease/internal/lifecycle_handler.go index 18ef101d1..ca4de15a5 100644 --- a/images/virtualization-artifact/pkg/controller/vmiplease/internal/lifecycle_handler.go +++ b/images/virtualization-artifact/pkg/controller/vmiplease/internal/lifecycle_handler.go @@ -56,20 +56,17 @@ func (h *LifecycleHandler) Handle(ctx context.Context, state state.VMIPLeaseStat } if vmip != nil { - if leaseStatus.Phase != virtv2.VirtualMachineIPAddressLeasePhaseBound { - leaseStatus.Phase = virtv2.VirtualMachineIPAddressLeasePhaseBound - cb.Status(metav1.ConditionTrue). - Reason(vmiplcondition.Bound) - conditions.SetCondition(cb, &leaseStatus.Conditions) - } + leaseStatus.Phase = virtv2.VirtualMachineIPAddressLeasePhaseBound + cb.Status(metav1.ConditionTrue). + Reason(vmiplcondition.Bound) + conditions.SetCondition(cb, &leaseStatus.Conditions) + } else { - if leaseStatus.Phase != virtv2.VirtualMachineIPAddressLeasePhaseReleased { - leaseStatus.Phase = virtv2.VirtualMachineIPAddressLeasePhaseReleased - cb.Status(metav1.ConditionFalse). - Reason(vmiplcondition.Released). - Message("VirtualMachineIPAddress lease is not used by any VirtualMachineIPAddress") - conditions.SetCondition(cb, &leaseStatus.Conditions) - } + leaseStatus.Phase = virtv2.VirtualMachineIPAddressLeasePhaseReleased + cb.Status(metav1.ConditionFalse). + Reason(vmiplcondition.Released). + Message("VirtualMachineIPAddress lease is not used by any VirtualMachineIPAddress") + conditions.SetCondition(cb, &leaseStatus.Conditions) } leaseStatus.ObservedGeneration = lease.GetGeneration() diff --git a/images/virtualization-artifact/pkg/controller/vmiplease/internal/protection_handler.go b/images/virtualization-artifact/pkg/controller/vmiplease/internal/protection_handler.go index deaa6fef7..37cfa10ab 100644 --- a/images/virtualization-artifact/pkg/controller/vmiplease/internal/protection_handler.go +++ b/images/virtualization-artifact/pkg/controller/vmiplease/internal/protection_handler.go @@ -47,7 +47,7 @@ func (h *ProtectionHandler) Handle(ctx context.Context, state state.VMIPLeaseSta if vmip != nil { controllerutil.AddFinalizer(lease, virtv2.FinalizerIPAddressLeaseCleanup) } else if lease.GetDeletionTimestamp() == nil { - log.Info("Deletion observed: remove cleanup finalizer from VirtualMachineIPAddressLease") + log.Info("Deletion observed: remove cleanup finalizer from VirtualMachineMACAddressLease") controllerutil.RemoveFinalizer(lease, virtv2.FinalizerIPAddressLeaseCleanup) } diff --git a/images/virtualization-artifact/pkg/controller/vmiplease/vmiplease_webhook.go b/images/virtualization-artifact/pkg/controller/vmiplease/vmiplease_webhook.go index 66db25ff7..c5a3d74ed 100644 --- a/images/virtualization-artifact/pkg/controller/vmiplease/vmiplease_webhook.go +++ b/images/virtualization-artifact/pkg/controller/vmiplease/vmiplease_webhook.go @@ -25,6 +25,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" "github.com/deckhouse/deckhouse/pkg/log" + "github.com/deckhouse/virtualization-controller/pkg/common/ip" "github.com/deckhouse/virtualization/api/core/v1alpha2" ) @@ -40,7 +41,7 @@ type Validator struct { func (v *Validator) ValidateCreate(_ context.Context, obj runtime.Object) (admission.Warnings, error) { lease, ok := obj.(*v1alpha2.VirtualMachineIPAddressLease) if !ok { - return nil, fmt.Errorf("expected a new VirtualMachineIPAddressLease but got a %T", obj) + return nil, fmt.Errorf("expected a new VirtualMachineMACAddressLease but got a %T", obj) } v.log.Info("Validate VirtualMachineIpAddressLease creating", "name", lease.Name) diff --git a/images/virtualization-artifact/pkg/controller/vmmac/internal/lifecycle_handler.go b/images/virtualization-artifact/pkg/controller/vmmac/internal/lifecycle_handler.go new file mode 100644 index 000000000..7fac7202b --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmmac/internal/lifecycle_handler.go @@ -0,0 +1,149 @@ +/* +Copyright 2024 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package internal + +import ( + "context" + "fmt" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/deckhouse/virtualization-controller/pkg/common/ip" + "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" + "github.com/deckhouse/virtualization-controller/pkg/controller/vmmac/internal/state" + "github.com/deckhouse/virtualization-controller/pkg/controller/vmmac/internal/util" + "github.com/deckhouse/virtualization-controller/pkg/logger" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vmmaccondition" +) + +const LifecycleHandlerName = "LifecycleHandler" + +type LifecycleHandler struct{} + +func NewLifecycleHandler() *LifecycleHandler { + return &LifecycleHandler{} +} + +func (h LifecycleHandler) Handle(ctx context.Context, state state.VMMACState) (reconcile.Result, error) { + log := logger.FromContext(ctx).With(logger.SlogHandler(LifecycleHandlerName)) + + mac := state.VirtualMachineMAC() + macStatus := &mac.Status + + vm, err := state.VirtualMachine(ctx) + if err != nil { + return reconcile.Result{}, err + } + + conditionBound := conditions.NewConditionBuilder(vmmaccondition.BoundType). + Generation(mac.GetGeneration()). + Reason(conditions.ReasonUnknown). + Status(metav1.ConditionUnknown) + + conditionAttach := conditions.NewConditionBuilder(vmmaccondition.AttachedType). + Generation(mac.GetGeneration()). + Reason(conditions.ReasonUnknown). + Status(metav1.ConditionUnknown) + + defer func() { + conditions.SetCondition(conditionBound, &macStatus.Conditions) + conditions.SetCondition(conditionAttach, &macStatus.Conditions) + }() + + if vm == nil || vm.DeletionTimestamp != nil { + macStatus.VirtualMachine = "" + conditionAttach.Status(metav1.ConditionFalse). + Reason(vmmaccondition.VirtualMachineNotFound). + Message("Virtual machine not found") + } + + lease, err := state.VirtualMachineMACLease(ctx) + if err != nil { + return reconcile.Result{}, err + } + + needRequeue := false + switch { + case lease == nil && macStatus.Address != "": + macStatus.Phase = virtv2.VirtualMachineMACAddressPhasePending + conditionBound.Status(metav1.ConditionFalse). + Reason(vmmaccondition.VirtualMachineMACAddressLeaseLost). + Message(fmt.Sprintf("VirtualMachineMACAddressLease %s doesn't exist", + ip.IpToLeaseName(macStatus.Address))) + + case lease == nil: + macStatus.Phase = virtv2.VirtualMachineMACAddressPhasePending + conditionBound.Status(metav1.ConditionFalse). + Reason(vmmaccondition.VirtualMachineMACAddressLeaseNotFound). + Message("VirtualMachineMACAddressLease is not found") + + case vm != nil && vm.GetDeletionTimestamp().IsZero(): + macStatus.Phase = virtv2.VirtualMachineMACAddressPhaseAttached + macStatus.VirtualMachine = vm.Name + conditionAttach.Status(metav1.ConditionTrue). + Reason(vmmaccondition.Attached) + + case util.IsBoundLease(lease, mac): + macStatus.Phase = virtv2.VirtualMachineMACAddressPhaseBound + macStatus.Address = ip.LeaseNameToIP(lease.Name) + conditionBound.Status(metav1.ConditionTrue). + Reason(vmmaccondition.Bound) + + case lease.Status.Phase == virtv2.VirtualMachineMACAddressLeasePhaseBound: + macStatus.Phase = virtv2.VirtualMachineMACAddressPhasePending + log.Warn(fmt.Sprintf("VirtualMachineMACAddressLease %s is bound to another VirtualMachineMACAddress resource: %s/%s", + lease.Name, lease.Spec.VirtualMachineMACAddressRef.Name, lease.Spec.VirtualMachineMACAddressRef.Namespace)) + conditionBound.Status(metav1.ConditionFalse). + Reason(vmmaccondition.VirtualMachineMACAddressLeaseAlreadyExists). + Message(fmt.Sprintf("VirtualMachineMACAddressLease %s is bound to another VirtualMachineMACAddress resource", + lease.Name)) + + case lease.Spec.VirtualMachineMACAddressRef.Namespace != mac.Namespace: + macStatus.Phase = virtv2.VirtualMachineMACAddressPhasePending + conditionBound.Status(metav1.ConditionFalse). + Reason(vmmaccondition.VirtualMachineMACAddressLeaseAlreadyExists). + Message(fmt.Sprintf("The VirtualMachineIPLease %s belongs to a different namespace", lease.Name)) + + needRequeue = true + + default: + if macStatus.Phase != virtv2.VirtualMachineMACAddressPhasePending { + macStatus.Phase = virtv2.VirtualMachineMACAddressPhasePending + conditionBound.Status(metav1.ConditionFalse). + Reason(vmmaccondition.VirtualMachineMACAddressLeaseNotReady). + Message(fmt.Sprintf("VirtualMachineMACAddressLease %s is not ready", + lease.Name)) + } + } + + log.Debug("Set VirtualMachineMAC phase", "phase", macStatus.Phase) + + macStatus.ObservedGeneration = mac.GetGeneration() + if !needRequeue { + return reconcile.Result{}, nil + } else { + // TODO add requeue with with exponential BackOff time interval using condition Bound -> probeTime + return reconcile.Result{RequeueAfter: 30 * time.Second}, nil + } +} + +func (h LifecycleHandler) Name() string { + return LifecycleHandlerName +} diff --git a/images/virtualization-artifact/pkg/controller/vmmac/internal/maclease_handler.go b/images/virtualization-artifact/pkg/controller/vmmac/internal/maclease_handler.go new file mode 100644 index 000000000..912184992 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmmac/internal/maclease_handler.go @@ -0,0 +1,188 @@ +/* +Copyright 2024 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package internal + +import ( + "context" + "errors" + "fmt" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/deckhouse/virtualization-controller/pkg/common/mac" + "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" + "github.com/deckhouse/virtualization-controller/pkg/controller/service" + "github.com/deckhouse/virtualization-controller/pkg/controller/vmmac/internal/state" + "github.com/deckhouse/virtualization-controller/pkg/controller/vmmac/internal/util" + "github.com/deckhouse/virtualization-controller/pkg/logger" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vmmaccondition" +) + +const MACLeaseHandlerName = "MACLeaseHandler" + +type MACLeaseHandler struct { + client client.Client + macService *service.MACAddressService + recorder record.EventRecorder +} + +func NewMACLeaseHandler(client client.Client, macAddressService *service.MACAddressService, recorder record.EventRecorder) *MACLeaseHandler { + return &MACLeaseHandler{ + client: client, + macService: macAddressService, + recorder: recorder, + } +} + +func (h MACLeaseHandler) Handle(ctx context.Context, state state.VMMACState) (reconcile.Result, error) { + log, ctx := logger.GetHandlerContext(ctx, MACLeaseHandlerName) + + vmmac := state.VirtualMachineMAC() + macStatus := &vmmac.Status + + lease, err := state.VirtualMachineMACLease(ctx) + if err != nil { + return reconcile.Result{}, err + } + condition, _ := conditions.GetCondition(vmmaccondition.BoundType, macStatus.Conditions) + + switch { + case lease == nil && macStatus.Address != "" && condition.Reason != vmmaccondition.VirtualMachineMACAddressLeaseAlreadyExists.String(): + log.Info("Lease by name not found: waiting for the lease to be available") + return reconcile.Result{}, nil + + case lease == nil: + log.Info("No Lease for VirtualMachineMAC: create the new one", "address", vmmac.Spec.Address) + return h.createNewLease(ctx, state) + + case lease.Status.Phase == "": + log.Info("Lease is not ready: waiting for the lease") + return reconcile.Result{}, nil + + case util.IsBoundLease(lease, vmmac): + log.Info("Lease already exists, VirtualMachineMAC ref is valid") + return reconcile.Result{}, nil + + case lease.Status.Phase == virtv2.VirtualMachineMACAddressLeasePhaseBound: + log.Info("Lease is bounded to another VirtualMachineMAC: recreate VirtualMachineMAC when the lease is released") + return reconcile.Result{}, nil + + default: + log.Info("Lease is released: set binding") + + if lease.Spec.VirtualMachineMACAddressRef.Namespace != vmmac.Namespace { + log.Warn(fmt.Sprintf("The VirtualMachineMACLease belongs to a different namespace: %s", lease.Spec.VirtualMachineMACAddressRef.Namespace)) + h.recorder.Event(vmmac, corev1.EventTypeWarning, vmmaccondition.VirtualMachineMACAddressLeaseAlreadyExists.String(), "The VirtualMachineMACLease belongs to a different namespace") + + return reconcile.Result{}, nil + } + + lease.Spec.VirtualMachineMACAddressRef = &virtv2.VirtualMachineMACAddressLeaseMACAddressRef{ + Name: vmmac.Name, + Namespace: vmmac.Namespace, + } + + err := h.client.Update(ctx, lease) + if err != nil { + return reconcile.Result{}, err + } + + macStatus.Address = mac.LeaseNameToAddress(lease.Name) + return reconcile.Result{}, nil + } +} + +func (h MACLeaseHandler) createNewLease(ctx context.Context, state state.VMMACState) (reconcile.Result, error) { + log := logger.FromContext(ctx) + + vmmac := state.VirtualMachineMAC() + macStatus := &vmmac.Status + + if vmmac.Spec.Address == "" { + log.Info("allocate the new VirtualMachineMAC address") + var err error + macStatus.Address, err = h.macService.AllocateNewAddress(state.AllocatedMACs()) + if err != nil { + return reconcile.Result{}, err + } + } else { + macStatus.Address = vmmac.Spec.Address + } + + err := h.macService.IsAvailableAddress(macStatus.Address, state.AllocatedMACs()) + if err != nil { + macStatus.Address = "" + msg := fmt.Sprintf("the VirtualMachineMAC cannot be created: %s", err.Error()) + log.Info(msg) + + conditionBound := conditions.NewConditionBuilder(vmmaccondition.BoundType). + Generation(vmmac.GetGeneration()) + + switch { + case errors.Is(err, service.ErrMACAddressOutOfRange): + macStatus.Phase = virtv2.VirtualMachineMACAddressPhasePending + conditionBound.Status(metav1.ConditionFalse). + Reason(vmmaccondition.VirtualMachineMACAddressIsOutOfTheValidRange). + Message(fmt.Sprintf("The requested MAC address %s is out of the valid range", + vmmac.Spec.Address)) + h.recorder.Event(vmmac, corev1.EventTypeWarning, vmmaccondition.VirtualMachineMACAddressIsOutOfTheValidRange.String(), msg) + case errors.Is(err, service.ErrMACAddressAlreadyExist): + macStatus.Phase = virtv2.VirtualMachineMACAddressPhasePending + conditionBound.Status(metav1.ConditionFalse). + Reason(vmmaccondition.VirtualMachineMACAddressLeaseAlreadyExists). + Message(fmt.Sprintf("VirtualMachineMACAddressLease %s is bound to another VirtualMachineMACAddress", + mac.AddressToLeaseName(macStatus.Address))) + h.recorder.Event(vmmac, corev1.EventTypeWarning, vmmaccondition.VirtualMachineMACAddressLeaseAlreadyExists.String(), msg) + } + conditions.SetCondition(conditionBound, &macStatus.Conditions) + return reconcile.Result{}, nil + } + + leaseName := mac.AddressToLeaseName(macStatus.Address) + + log.Info("Create lease", + "leaseName", leaseName, + "refName", vmmac.Name, + "refNamespace", vmmac.Namespace, + ) + + err = h.client.Create(ctx, &virtv2.VirtualMachineMACAddressLease{ + ObjectMeta: metav1.ObjectMeta{ + Name: leaseName, + }, + Spec: virtv2.VirtualMachineMACAddressLeaseSpec{ + VirtualMachineMACAddressRef: &virtv2.VirtualMachineMACAddressLeaseMACAddressRef{ + Name: vmmac.Name, + Namespace: vmmac.Namespace, + }, + }, + }) + if err != nil { + return reconcile.Result{}, err + } + + return reconcile.Result{}, nil +} + +func (h MACLeaseHandler) Name() string { + return MACLeaseHandlerName +} diff --git a/images/virtualization-artifact/pkg/controller/vmmac/internal/protection_handler.go b/images/virtualization-artifact/pkg/controller/vmmac/internal/protection_handler.go new file mode 100644 index 000000000..a863645ba --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmmac/internal/protection_handler.go @@ -0,0 +1,101 @@ +/* +Copyright 2024 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package internal + +import ( + "context" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/deckhouse/virtualization-controller/pkg/controller/vmmac/internal/state" + "github.com/deckhouse/virtualization-controller/pkg/logger" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +const ProtectionHandlerName = "ProtectionHandler" + +type ProtectionHandler struct { + client client.Client +} + +func NewProtectionHandler(client client.Client) *ProtectionHandler { + return &ProtectionHandler{ + client: client, + } +} + +func (h *ProtectionHandler) Handle(ctx context.Context, state state.VMMACState) (reconcile.Result, error) { + log := logger.FromContext(ctx).With(logger.SlogHandler(ProtectionHandlerName)) + + mac := state.VirtualMachineMAC() + + vm, err := state.VirtualMachine(ctx) + if err != nil { + return reconcile.Result{}, err + } + + configuredVms, err := h.getConfiguredVM(ctx, mac) + if err != nil { + return reconcile.Result{}, err + } + + switch { + case len(configuredVms) == 0: + log.Debug("Allow VirtualMachineMACAddress deletion: remove protection finalizer") + controllerutil.RemoveFinalizer(mac, virtv2.FinalizerMACAddressProtection) + case mac.DeletionTimestamp == nil: + log.Debug("Protect VirtualMachineMACAddress from deletion") + controllerutil.AddFinalizer(mac, virtv2.FinalizerMACAddressProtection) + default: + log.Debug("VirtualMachineMACAddress deletion is delayed: it's protected by virtual machines") + } + + if vm == nil || vm.DeletionTimestamp != nil { + log.Info("VirtualMachineMAC is no longer attached to any VM: remove cleanup finalizer", "VirtualMachineMACName", mac.Name) + controllerutil.RemoveFinalizer(mac, virtv2.FinalizerMACAddressCleanup) + } else if mac.GetDeletionTimestamp() == nil { + controllerutil.AddFinalizer(mac, virtv2.FinalizerMACAddressCleanup) + log.Info("VirtualMachineMAC is still attached, finalizer added", "VirtualMachineMACName", mac.Name) + } + + return reconcile.Result{}, nil +} + +func (h *ProtectionHandler) Name() string { + return ProtectionHandlerName +} + +func (h *ProtectionHandler) getConfiguredVM(ctx context.Context, vmmac *virtv2.VirtualMachineMACAddress) ([]virtv2.VirtualMachine, error) { + var vms virtv2.VirtualMachineList + err := h.client.List(ctx, &vms, &client.ListOptions{ + Namespace: vmmac.Namespace, + }) + if err != nil { + return nil, err + } + + var configuredVms []virtv2.VirtualMachine + for _, vm := range vms.Items { + if vm.Spec.VirtualMachineMACAddress == vmmac.Name && vm.Status.Phase != virtv2.MachineTerminating { + configuredVms = append(configuredVms, vm) + } + } + + return configuredVms, nil +} diff --git a/images/virtualization-artifact/pkg/controller/vmmac/internal/state/state.go b/images/virtualization-artifact/pkg/controller/vmmac/internal/state/state.go new file mode 100644 index 000000000..bad58f346 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmmac/internal/state/state.go @@ -0,0 +1,152 @@ +/* +Copyright 2024 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package state + +import ( + "context" + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/deckhouse/virtualization-controller/pkg/common/mac" + "github.com/deckhouse/virtualization-controller/pkg/common/object" + "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" + "github.com/deckhouse/virtualization-controller/pkg/controller/indexer" + "github.com/deckhouse/virtualization-controller/pkg/controller/netmanager" + "github.com/deckhouse/virtualization-controller/pkg/controller/vmmac/internal/util" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vmiplcondition" +) + +type VMMACState interface { + VirtualMachineMAC() *virtv2.VirtualMachineMACAddress + VirtualMachineMACLease(ctx context.Context) (*virtv2.VirtualMachineMACAddressLease, error) + VirtualMachine(ctx context.Context) (*virtv2.VirtualMachine, error) + + AllocatedMACs() mac.AllocatedMACs +} + +type state struct { + client client.Client + mac *virtv2.VirtualMachineMACAddress + lease *virtv2.VirtualMachineMACAddressLease + vm *virtv2.VirtualMachine + allocatedMACs mac.AllocatedMACs +} + +func New(c client.Client, vmmac *virtv2.VirtualMachineMACAddress) VMMACState { + return &state{client: c, mac: vmmac} +} + +func (s *state) VirtualMachineMAC() *virtv2.VirtualMachineMACAddress { + return s.mac +} + +func (s *state) VirtualMachineMACLease(ctx context.Context) (*virtv2.VirtualMachineMACAddressLease, error) { + if s.lease != nil { + return s.lease, nil + } + + var err error + + leaseName := mac.AddressToLeaseName(s.mac.Status.Address) + + if leaseName != "" { + leaseKey := types.NamespacedName{Name: leaseName} + s.lease, err = object.FetchObject(ctx, leaseKey, s.client, &virtv2.VirtualMachineMACAddressLease{}) + if err != nil { + return nil, fmt.Errorf("unable to get mac lease %s: %w", leaseKey, err) + } + } + + if s.lease == nil { + var leases virtv2.VirtualMachineMACAddressLeaseList + err = s.client.List(ctx, &leases, + client.InNamespace(s.mac.Namespace), + &client.MatchingFields{ + indexer.IndexFieldVMMACLeaseByVMMAC: s.mac.Name, + }) + if err != nil { + return nil, err + } + + for i, lease := range leases.Items { + boundCondition, exist := conditions.GetCondition(vmiplcondition.BoundType, lease.Status.Conditions) + if exist && boundCondition.Status == metav1.ConditionTrue { + s.lease = &leases.Items[i] + break + } + } + } + + if s.lease == nil { + s.allocatedMACs, err = util.GetAllocatedMACs(ctx, s.client) + if err != nil { + return nil, err + } + } + + return s.lease, nil +} + +func (s *state) VirtualMachine(ctx context.Context) (*virtv2.VirtualMachine, error) { + if s.vm != nil { + return s.vm, nil + } + + var err error + if s.mac.Status.VirtualMachine != "" { + vmKey := types.NamespacedName{Name: s.mac.Status.VirtualMachine, Namespace: s.mac.Namespace} + vm, err := object.FetchObject(ctx, vmKey, s.client, &virtv2.VirtualMachine{}) + if err != nil { + return nil, fmt.Errorf("unable to get VM %s: %w", vmKey, err) + } + + if vm == nil { + return s.vm, nil + } + + if vm.Status.VirtualMachineIPAddress == s.mac.Name && vm.Status.MACAddress == s.mac.Status.Address { + s.vm = vm + } + } + + if s.vm == nil { + var vms virtv2.VirtualMachineList + err = s.client.List(ctx, &vms, &client.ListOptions{ + Namespace: s.mac.Namespace, + }) + if err != nil { + return nil, err + } + + for i, vm := range vms.Items { + if vm.Spec.VirtualMachineIPAddress == s.mac.Name || vm.Spec.VirtualMachineIPAddress == "" && vm.Name == netmanager.GetVirtualMachineNameFromVMMAC(s.mac) { + s.vm = &vms.Items[i] + break + } + } + } + + return s.vm, nil +} + +func (s *state) AllocatedMACs() mac.AllocatedMACs { + return s.allocatedMACs +} diff --git a/images/virtualization-artifact/pkg/controller/vmmac/internal/util/util.go b/images/virtualization-artifact/pkg/controller/vmmac/internal/util/util.go new file mode 100644 index 000000000..cf7ec2c25 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmmac/internal/util/util.go @@ -0,0 +1,61 @@ +/* +Copyright 2024 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "context" + "fmt" + + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/deckhouse/virtualization-controller/pkg/common/ip" + "github.com/deckhouse/virtualization-controller/pkg/common/mac" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +func GetAllocatedMACs(ctx context.Context, apiClient client.Client) (mac.AllocatedMACs, error) { + var leases virtv2.VirtualMachineMACAddressLeaseList + + err := apiClient.List(ctx, &leases) + if err != nil { + return nil, fmt.Errorf("error getting leases: %w", err) + } + + allocatedIPs := make(mac.AllocatedMACs, len(leases.Items)) + for _, lease := range leases.Items { + l := lease + allocatedIPs[ip.LeaseNameToIP(lease.Name)] = &l + } + + return allocatedIPs, nil +} + +func IsBoundLease(lease *virtv2.VirtualMachineMACAddressLease, vmmac *virtv2.VirtualMachineMACAddress) bool { + if lease.Status.Phase != virtv2.VirtualMachineMACAddressLeasePhaseBound { + return false + } + + if lease.Spec.VirtualMachineMACAddressRef == nil { + return false + } + + if lease.Spec.VirtualMachineMACAddressRef.Namespace != vmmac.Namespace || lease.Spec.VirtualMachineMACAddressRef.Name != vmmac.Name { + return false + } + + return true +} diff --git a/images/virtualization-artifact/pkg/controller/vmmac/vmmac_controller.go b/images/virtualization-artifact/pkg/controller/vmmac/vmmac_controller.go new file mode 100644 index 000000000..317b4186b --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmmac/vmmac_controller.go @@ -0,0 +1,83 @@ +/* +Copyright 2024 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package vmmac + +import ( + "context" + "time" + + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/manager" + + "github.com/deckhouse/deckhouse/pkg/log" + + "github.com/deckhouse/virtualization-controller/pkg/controller/service" + "github.com/deckhouse/virtualization-controller/pkg/controller/vmmac/internal" + "github.com/deckhouse/virtualization-controller/pkg/logger" + "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +const ( + ControllerName = "vmmac-controller" +) + +func NewController( + ctx context.Context, + mgr manager.Manager, + log *log.Logger, + macAddressPrefix string, +) (controller.Controller, error) { + recorder := mgr.GetEventRecorderFor(ControllerName) + macService := service.NewMACAddressService(macAddressPrefix) + + handlers := []Handler{ + internal.NewProtectionHandler(mgr.GetClient()), + internal.NewMACLeaseHandler(mgr.GetClient(), macService, recorder), + internal.NewLifecycleHandler(), + } + + r, err := NewReconciler(mgr.GetClient(), handlers...) + if err != nil { + return nil, err + } + + c, err := controller.New(ControllerName, mgr, controller.Options{ + Reconciler: r, + RecoverPanic: ptr.To(true), + LogConstructor: logger.NewConstructor(log), + CacheSyncTimeout: 10 * time.Minute, + }) + if err != nil { + return nil, err + } + + if err = r.SetupController(ctx, mgr, c); err != nil { + return nil, err + } + + if err = builder.WebhookManagedBy(mgr). + For(&v1alpha2.VirtualMachineIPAddress{}). + WithValidator(NewValidator(log, mgr.GetClient(), macService)). + Complete(); err != nil { + return nil, err + } + + log.Info("Initialized VirtualMachineMAC controller") + return c, nil +} diff --git a/images/virtualization-artifact/pkg/controller/vmmac/vmmac_reconciler.go b/images/virtualization-artifact/pkg/controller/vmmac/vmmac_reconciler.go new file mode 100644 index 000000000..43e236a01 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmmac/vmmac_reconciler.go @@ -0,0 +1,196 @@ +/* +Copyright 2024 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package vmmac + +import ( + "context" + "errors" + "fmt" + + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" + + "github.com/deckhouse/virtualization-controller/pkg/controller/indexer" + "github.com/deckhouse/virtualization-controller/pkg/controller/service" + "github.com/deckhouse/virtualization-controller/pkg/controller/vmmac/internal/state" + "github.com/deckhouse/virtualization-controller/pkg/logger" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +type Handler interface { + Handle(ctx context.Context, s state.VMMACState) (reconcile.Result, error) + Name() string +} + +type Reconciler struct { + handlers []Handler + client client.Client +} + +func NewReconciler(client client.Client, handlers ...Handler) (*Reconciler, error) { + return &Reconciler{ + client: client, + handlers: handlers, + }, nil +} + +func (r *Reconciler) SetupController(_ context.Context, mgr manager.Manager, ctr controller.Controller) error { + if err := ctr.Watch( + source.Kind(mgr.GetCache(), &virtv2.VirtualMachineMACAddressLease{}), + handler.EnqueueRequestsFromMapFunc(r.enqueueRequestsFromLeases), + predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { return true }, + DeleteFunc: func(e event.DeleteEvent) bool { return true }, + UpdateFunc: func(e event.UpdateEvent) bool { return true }, + }, + ); err != nil { + return fmt.Errorf("error setting watch on leases: %w", err) + } + + if err := ctr.Watch( + source.Kind(mgr.GetCache(), &virtv2.VirtualMachine{}), + handler.EnqueueRequestsFromMapFunc(r.enqueueRequestsFromVMs), + predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { return false }, + DeleteFunc: func(e event.DeleteEvent) bool { return true }, + UpdateFunc: func(e event.UpdateEvent) bool { return true }, + }, + ); err != nil { + return fmt.Errorf("error setting watch on vms: %w", err) + } + + return ctr.Watch(source.Kind(mgr.GetCache(), &virtv2.VirtualMachineMACAddress{}), &handler.EnqueueRequestForObject{}) +} + +func (r *Reconciler) enqueueRequestsFromVMs(ctx context.Context, obj client.Object) []reconcile.Request { + vm, ok := obj.(*virtv2.VirtualMachine) + if !ok { + return nil + } + + var requests []reconcile.Request + if vm.Spec.VirtualMachineMACAddress == "" { + requests = append(requests, reconcile.Request{NamespacedName: types.NamespacedName{ + Name: vm.Name, + Namespace: vm.Namespace, + }}) + } else { + requests = append(requests, reconcile.Request{NamespacedName: types.NamespacedName{ + Namespace: vm.Namespace, + Name: vm.Spec.VirtualMachineMACAddress, + }}) + } + + vmmacs := &virtv2.VirtualMachineMACAddressList{} + err := r.client.List(ctx, vmmacs, client.InNamespace(vm.Namespace), + &client.MatchingFields{ + indexer.IndexFieldVMMACByVM: vm.Name, + }) + if err != nil { + return nil + } + + for _, vmmac := range vmmacs.Items { + requests = append(requests, reconcile.Request{NamespacedName: types.NamespacedName{ + Namespace: vmmac.Namespace, + Name: vmmac.Name, + }}) + } + + return requests +} + +func (r *Reconciler) enqueueRequestsFromLeases(_ context.Context, obj client.Object) []reconcile.Request { + lease, ok := obj.(*virtv2.VirtualMachineMACAddressLease) + if !ok { + return nil + } + + if lease.Spec.VirtualMachineMACAddressRef == nil { + return nil + } + + return []reconcile.Request{ + { + NamespacedName: types.NamespacedName{ + Namespace: lease.Spec.VirtualMachineMACAddressRef.Namespace, + Name: lease.Spec.VirtualMachineMACAddressRef.Name, + }, + }, + } +} + +func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { + log := logger.FromContext(ctx) + + vmmac := service.NewResource(req.NamespacedName, r.client, r.factory, r.statusGetter) + + err := vmmac.Fetch(ctx) + if err != nil { + return reconcile.Result{}, err + } + + if vmmac.IsEmpty() { + return reconcile.Result{}, nil + } + + log.Debug("Start reconcile VMMAC") + + s := state.New(r.client, vmmac.Changed()) + var handlerErrs []error + + var result reconcile.Result + for _, h := range r.handlers { + log.Debug("Run handler", logger.SlogHandler(h.Name())) + + var res reconcile.Result + res, err = h.Handle(ctx, s) + if err != nil { + log.Error("Failed to handle VirtualMachineMAC", logger.SlogErr(err), logger.SlogHandler(h.Name())) + handlerErrs = append(handlerErrs, err) + } + + result = service.MergeResults(result, res) + } + + err = vmmac.Update(ctx) + if err != nil { + return reconcile.Result{}, err + } + + err = errors.Join(handlerErrs...) + if err != nil { + return reconcile.Result{}, err + } + + return result, nil +} + +func (r *Reconciler) factory() *virtv2.VirtualMachineMACAddress { + return &virtv2.VirtualMachineMACAddress{} +} + +func (r *Reconciler) statusGetter(obj *virtv2.VirtualMachineMACAddress) virtv2.VirtualMachineMACAddressStatus { + return obj.Status +} diff --git a/images/virtualization-artifact/pkg/controller/vmmac/vmmac_webhook.go b/images/virtualization-artifact/pkg/controller/vmmac/vmmac_webhook.go new file mode 100644 index 000000000..ad315b2e3 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmmac/vmmac_webhook.go @@ -0,0 +1,120 @@ +/* +Copyright 2024 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package vmmac + +import ( + "context" + "errors" + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "github.com/deckhouse/deckhouse/pkg/log" + + "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" + "github.com/deckhouse/virtualization-controller/pkg/controller/service" + "github.com/deckhouse/virtualization-controller/pkg/controller/vmmac/internal/util" + "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vmmaccondition" +) + +func NewValidator( + log *log.Logger, + client client.Client, + macAddressService *service.MACAddressService, +) *Validator { + return &Validator{ + log: log.With("webhook", "validation"), + client: client, + macService: macAddressService, + } +} + +type Validator struct { + log *log.Logger + client client.Client + macService *service.MACAddressService +} + +func (v *Validator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) { + vmmac, ok := obj.(*v1alpha2.VirtualMachineMACAddress) + if !ok { + return nil, fmt.Errorf("expected a new VirtualMachineMACAddress but got a %T", obj) + } + + v.log.Info("Validate VirtualMachineMAC creating", "name", vmmac.Name, "address", vmmac.Spec.Address) + + var warnings admission.Warnings + + address := vmmac.Spec.Address + if address != "" { + allocatedMACs, err := util.GetAllocatedMACs(ctx, v.client) + if err != nil { + return nil, fmt.Errorf("error getting allocated mac addresses: %w", err) + } + + allocatedLease, ok := allocatedMACs[address] + if ok && allocatedLease.Spec.VirtualMachineMACAddressRef != nil && + (allocatedLease.Spec.VirtualMachineMACAddressRef.Namespace != vmmac.Namespace || + allocatedLease.Spec.VirtualMachineMACAddressRef.Name != vmmac.Name) { + return nil, fmt.Errorf("VirtualMachineMACAddress cannot be created: the MAC address %s has already been allocated by VirtualMachineMACAddress/%s in ns/%s", address, allocatedLease.Spec.VirtualMachineMACAddressRef.Name, allocatedLease.Spec.VirtualMachineMACAddressRef.Namespace) + } + + err = v.macService.IsAvailableAddress(address, allocatedMACs) + if err != nil { + if errors.Is(err, service.ErrIPAddressOutOfRange) { + warnings = append(warnings, fmt.Sprintf("The requested MAC address %s is out of the valid range", address)) + } + } + } + + return warnings, nil +} + +func (v *Validator) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + oldMac, ok := oldObj.(*v1alpha2.VirtualMachineMACAddress) + if !ok { + return nil, fmt.Errorf("expected an old VirtualMachineMAC but got a %T", oldObj) + } + + newMac, ok := newObj.(*v1alpha2.VirtualMachineMACAddress) + if !ok { + return nil, fmt.Errorf("expected a new VirtualMachineMAC but got a %T", newObj) + } + + v.log.Info("Validate VirtualMachineMAC updating", "name", newMac.Name, + "old.address", oldMac.Spec.Address, "new.address", newMac.Spec.Address, + ) + + boundCondition, exist := conditions.GetCondition(vmmaccondition.BoundType, oldMac.Status.Conditions) + if exist && boundCondition.Status == metav1.ConditionTrue { + if oldMac.Spec.Address != newMac.Spec.Address { + return nil, errors.New("the VirtualMachineMACAddress is in 'Bound' state -> MAC address cannot be changed") + } + } + + return nil, nil +} + +func (v *Validator) ValidateDelete(_ context.Context, _ runtime.Object) (admission.Warnings, error) { + err := fmt.Errorf("misconfigured webhook rules: delete operation not implemented") + v.log.Error("Ensure the correctness of ValidatingWebhookConfiguration", "err", err) + return nil, nil +} diff --git a/images/virtualization-artifact/pkg/controller/vmmaclease/internal/lifecycle_handler.go b/images/virtualization-artifact/pkg/controller/vmmaclease/internal/lifecycle_handler.go new file mode 100644 index 000000000..1f1b0ccf2 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmmaclease/internal/lifecycle_handler.go @@ -0,0 +1,78 @@ +/* +Copyright 2024 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package internal + +import ( + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" + "github.com/deckhouse/virtualization-controller/pkg/controller/vmmaclease/internal/state" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vmiplcondition" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vmmaclcondition" +) + +const LifecycleHandlerName = "LifecycleHandler" + +type LifecycleHandler struct{} + +func NewLifecycleHandler() *LifecycleHandler { + return &LifecycleHandler{} +} + +func (h *LifecycleHandler) Handle(ctx context.Context, state state.VMMACLeaseState) (reconcile.Result, error) { + lease := state.VirtualMachineMACAddressLease() + leaseStatus := &lease.Status + + if state.ShouldDeletion() { + return reconcile.Result{}, nil + } + + cb := conditions.NewConditionBuilder(vmmaclcondition.BoundType). + Generation(lease.GetGeneration()). + Reason(conditions.ReasonUnknown). + Status(metav1.ConditionUnknown) + + vmmac, err := state.VirtualMachineMACAddress(ctx) + if err != nil { + return reconcile.Result{}, err + } + + if vmmac != nil { + leaseStatus.Phase = virtv2.VirtualMachineMACAddressLeasePhaseBound + cb.Status(metav1.ConditionTrue). + Reason(vmmaclcondition.Bound) + conditions.SetCondition(cb, &leaseStatus.Conditions) + } else { + leaseStatus.Phase = virtv2.VirtualMachineMACAddressLeasePhaseReleased + cb.Status(metav1.ConditionFalse). + Reason(vmiplcondition.Released). + Message("VirtualMachineMACAddress lease is not used by any VirtualMachineMACAddress") + conditions.SetCondition(cb, &leaseStatus.Conditions) + } + + leaseStatus.ObservedGeneration = lease.GetGeneration() + + return reconcile.Result{}, nil +} + +func (h *LifecycleHandler) Name() string { + return LifecycleHandlerName +} diff --git a/images/virtualization-artifact/pkg/controller/vmmaclease/internal/protection_handler.go b/images/virtualization-artifact/pkg/controller/vmmaclease/internal/protection_handler.go new file mode 100644 index 000000000..f59504691 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmmaclease/internal/protection_handler.go @@ -0,0 +1,59 @@ +/* +Copyright 2024 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package internal + +import ( + "context" + + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/deckhouse/virtualization-controller/pkg/controller/vmmaclease/internal/state" + "github.com/deckhouse/virtualization-controller/pkg/logger" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +const ProtectionHandlerName = "ProtectionHandler" + +type ProtectionHandler struct{} + +func NewProtectionHandler() *ProtectionHandler { + return &ProtectionHandler{} +} + +func (h *ProtectionHandler) Handle(ctx context.Context, state state.VMMACLeaseState) (reconcile.Result, error) { + log := logger.FromContext(ctx).With(logger.SlogHandler(ProtectionHandlerName)) + lease := state.VirtualMachineMACAddressLease() + + vmmac, err := state.VirtualMachineMACAddress(ctx) + if err != nil { + return reconcile.Result{}, err + } + + if vmmac != nil { + controllerutil.AddFinalizer(lease, virtv2.FinalizerMACAddressLeaseCleanup) + } else if lease.GetDeletionTimestamp() == nil { + log.Info("Deletion observed: remove cleanup finalizer from VirtualMachineMACAddressLease") + controllerutil.RemoveFinalizer(lease, virtv2.FinalizerMACAddressLeaseCleanup) + } + + return reconcile.Result{}, nil +} + +func (h *ProtectionHandler) Name() string { + return ProtectionHandlerName +} diff --git a/images/virtualization-artifact/pkg/controller/vmmaclease/internal/retention_handler.go b/images/virtualization-artifact/pkg/controller/vmmaclease/internal/retention_handler.go new file mode 100644 index 000000000..81d7c18ad --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmmaclease/internal/retention_handler.go @@ -0,0 +1,83 @@ +/* +Copyright 2024 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package internal + +import ( + "context" + "fmt" + "time" + + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" + "github.com/deckhouse/virtualization-controller/pkg/controller/vmmaclease/internal/state" + "github.com/deckhouse/virtualization-controller/pkg/logger" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vmipcondition" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vmmaclcondition" +) + +const RetentionHandlerName = "RetentionHandler" + +type RetentionHandler struct { + retentionDuration time.Duration +} + +func NewRetentionHandler(retentionDuration time.Duration) *RetentionHandler { + return &RetentionHandler{ + retentionDuration: retentionDuration, + } +} + +func (h *RetentionHandler) Handle(ctx context.Context, state state.VMMACLeaseState) (reconcile.Result, error) { + log := logger.FromContext(ctx).With(logger.SlogHandler(RetentionHandlerName)) + + lease := state.VirtualMachineMACAddressLease() + + vmip, err := state.VirtualMachineMACAddress(ctx) + if err != nil { + return reconcile.Result{}, err + } + + if vmip == nil { + if lease.Spec.VirtualMachineMACAddressRef.Name != "" { + log.Info("VirtualMachineMAC not found: remove this ref from the spec and retain VMIPLease") + lease.Spec.VirtualMachineMACAddressRef.Name = "" + return reconcile.Result{RequeueAfter: h.retentionDuration}, nil + } + + leaseStatus := &lease.Status + boundCondition, _ := conditions.GetCondition(vmipcondition.BoundType, leaseStatus.Conditions) + if boundCondition.Reason == vmmaclcondition.Released.String() { + currentTime := time.Now().UTC() + + duration := currentTime.Sub(boundCondition.LastTransitionTime.Time) + if duration >= h.retentionDuration { + log.Info(fmt.Sprintf("Retain VMIPLease after %s of being not claimed", h.retentionDuration.String())) + state.SetDeletion(true) + return reconcile.Result{}, nil + } + + return reconcile.Result{RequeueAfter: h.retentionDuration - duration}, nil + } + } + + return reconcile.Result{}, nil +} + +func (h *RetentionHandler) Name() string { + return RetentionHandlerName +} diff --git a/images/virtualization-artifact/pkg/controller/vmmaclease/internal/state/state.go b/images/virtualization-artifact/pkg/controller/vmmaclease/internal/state/state.go new file mode 100644 index 000000000..b359d5f2c --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmmaclease/internal/state/state.go @@ -0,0 +1,76 @@ +/* +Copyright 2024 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package state + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/deckhouse/virtualization-controller/pkg/common/object" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +type VMMACLeaseState interface { + VirtualMachineMACAddressLease() *virtv2.VirtualMachineMACAddressLease + VirtualMachineMACAddress(ctx context.Context) (*virtv2.VirtualMachineMACAddress, error) + SetDeletion(value bool) + ShouldDeletion() bool +} + +type state struct { + client client.Client + lease *virtv2.VirtualMachineMACAddressLease + mac *virtv2.VirtualMachineMACAddress + isDeletion bool +} + +func New(c client.Client, lease *virtv2.VirtualMachineMACAddressLease) VMMACLeaseState { + return &state{client: c, lease: lease} +} + +func (s *state) VirtualMachineMACAddressLease() *virtv2.VirtualMachineMACAddressLease { + return s.lease +} + +func (s *state) VirtualMachineMACAddress(ctx context.Context) (*virtv2.VirtualMachineMACAddress, error) { + if s.mac != nil { + return s.mac, nil + } + + var err error + + if s.lease.Spec.VirtualMachineMACAddressRef != nil { + macKey := types.NamespacedName{Name: s.lease.Spec.VirtualMachineMACAddressRef.Name, Namespace: s.lease.Spec.VirtualMachineMACAddressRef.Namespace} + s.mac, err = object.FetchObject(ctx, macKey, s.client, &virtv2.VirtualMachineMACAddress{}) + if err != nil { + return nil, fmt.Errorf("unable to get VirtualMachineMAC %s: %w", macKey, err) + } + } + + return s.mac, nil +} + +func (s *state) SetDeletion(value bool) { + s.isDeletion = value +} + +func (s *state) ShouldDeletion() bool { + return s.isDeletion +} diff --git a/images/virtualization-artifact/pkg/controller/vmmaclease/vmmaclease_controller.go b/images/virtualization-artifact/pkg/controller/vmmaclease/vmmaclease_controller.go new file mode 100644 index 000000000..95987e4a4 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmmaclease/vmmaclease_controller.go @@ -0,0 +1,82 @@ +/* +Copyright 2024 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package vmmaclease + +import ( + "context" + "time" + + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/manager" + + "github.com/deckhouse/deckhouse/pkg/log" + + "github.com/deckhouse/virtualization-controller/pkg/controller/vmmaclease/internal" + "github.com/deckhouse/virtualization-controller/pkg/logger" + "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +const ( + ControllerName = "vmmaclease-controller" +) + +func NewController( + ctx context.Context, + mgr manager.Manager, + log *log.Logger, + retentionDurationStr string, +) (controller.Controller, error) { + retentionDuration, err := time.ParseDuration(retentionDurationStr) + if err != nil { + log.Error("Failed to parse retention duration", "err", err) + return nil, err + } + + handlers := []Handler{ + internal.NewProtectionHandler(), + internal.NewRetentionHandler(retentionDuration), + internal.NewLifecycleHandler(), + } + + r := NewReconciler(mgr.GetClient(), handlers...) + + c, err := controller.New(ControllerName, mgr, controller.Options{ + Reconciler: r, + RecoverPanic: ptr.To(true), + LogConstructor: logger.NewConstructor(log), + CacheSyncTimeout: 10 * time.Minute, + }) + if err != nil { + return nil, err + } + + if err = r.SetupController(ctx, mgr, c); err != nil { + return nil, err + } + + if err = builder.WebhookManagedBy(mgr). + For(&v1alpha2.VirtualMachineIPAddressLease{}). + WithValidator(NewValidator(log)). + Complete(); err != nil { + return nil, err + } + + log.Info("Initialized VirtualMachineMACLease controller") + return c, nil +} diff --git a/images/virtualization-artifact/pkg/controller/vmmaclease/vmmaclease_reconciler.go b/images/virtualization-artifact/pkg/controller/vmmaclease/vmmaclease_reconciler.go new file mode 100644 index 000000000..84dac3148 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmmaclease/vmmaclease_reconciler.go @@ -0,0 +1,158 @@ +/* +Copyright 2024 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package vmmaclease + +import ( + "context" + "errors" + "fmt" + "reflect" + + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" + + "github.com/deckhouse/virtualization-controller/pkg/common/mac" + "github.com/deckhouse/virtualization-controller/pkg/controller/service" + "github.com/deckhouse/virtualization-controller/pkg/controller/vmmaclease/internal/state" + "github.com/deckhouse/virtualization-controller/pkg/logger" + virtv2 "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +type Handler interface { + Handle(ctx context.Context, s state.VMMACLeaseState) (reconcile.Result, error) + Name() string +} + +type Reconciler struct { + handlers []Handler + client client.Client +} + +func NewReconciler(client client.Client, handlers ...Handler) *Reconciler { + return &Reconciler{ + client: client, + handlers: handlers, + } +} + +func (r *Reconciler) SetupController(_ context.Context, mgr manager.Manager, ctr controller.Controller) error { + if err := ctr.Watch( + source.Kind(mgr.GetCache(), &virtv2.VirtualMachineMACAddress{}), + handler.EnqueueRequestsFromMapFunc(r.enqueueRequestsFromVMMAC), + predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { return false }, + DeleteFunc: func(e event.DeleteEvent) bool { return true }, + UpdateFunc: func(e event.UpdateEvent) bool { return false }, + }, + ); err != nil { + return fmt.Errorf("error setting watch on vmmac: %w", err) + } + + return ctr.Watch(source.Kind(mgr.GetCache(), &virtv2.VirtualMachineMACAddressLease{}), &handler.EnqueueRequestForObject{}) +} + +func (r *Reconciler) enqueueRequestsFromVMMAC(_ context.Context, obj client.Object) []reconcile.Request { + vmmac, ok := obj.(*virtv2.VirtualMachineMACAddress) + if !ok { + return nil + } + + if vmmac.Status.Address == "" { + return nil + } + + return []reconcile.Request{ + { + NamespacedName: types.NamespacedName{ + Name: mac.AddressToLeaseName(vmmac.Status.Address), + }, + }, + } +} + +func (r *Reconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { + log := logger.FromContext(ctx) + + lease := service.NewResource(req.NamespacedName, r.client, r.factory, r.statusGetter) + + var err error + err = lease.Fetch(ctx) + if err != nil { + return reconcile.Result{}, err + } + + if lease.IsEmpty() { + return reconcile.Result{}, nil + } + + log.Debug("Start reconcile VMMACLease") + + s := state.New(r.client, lease.Changed()) + var handlerErrs []error + + var result reconcile.Result + for _, h := range r.handlers { + log.Debug("Run handler", logger.SlogHandler(h.Name())) + + var res reconcile.Result + res, err = h.Handle(ctx, s) + if err != nil { + log.Error("Failed to handle VMMACLease", "err", err, logger.SlogHandler(h.Name())) + handlerErrs = append(handlerErrs, err) + } + + result = service.MergeResults(result, res) + } + + if s.ShouldDeletion() { + err = r.client.Delete(ctx, lease.Changed()) + } else { + if !reflect.DeepEqual(lease.Current().Spec, lease.Changed().Spec) { + leaseStatus := lease.Changed().Status.DeepCopy() + err = r.client.Update(ctx, lease.Changed()) + if err != nil { + log.Error("Failed to update VirtualMachineMACAddressLease", "err", err) + handlerErrs = append(handlerErrs, err) + } + lease.Changed().Status = *leaseStatus + } + + err = lease.Update(ctx) + } + + err = errors.Join(handlerErrs...) + if err != nil { + return reconcile.Result{}, err + } + + return result, nil +} + +func (r *Reconciler) factory() *virtv2.VirtualMachineMACAddressLease { + return &virtv2.VirtualMachineMACAddressLease{} +} + +func (r *Reconciler) statusGetter(obj *virtv2.VirtualMachineMACAddressLease) virtv2.VirtualMachineMACAddressLeaseStatus { + return obj.Status +} diff --git a/images/virtualization-artifact/pkg/controller/vmmaclease/vmmaclease_webhook.go b/images/virtualization-artifact/pkg/controller/vmmaclease/vmmaclease_webhook.go new file mode 100644 index 000000000..720b525c7 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmmaclease/vmmaclease_webhook.go @@ -0,0 +1,65 @@ +/* +Copyright 2024 Flant JSC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package vmmaclease + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "github.com/deckhouse/deckhouse/pkg/log" + + "github.com/deckhouse/virtualization-controller/pkg/common/mac" + "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +func NewValidator(log *log.Logger) *Validator { + return &Validator{log: log.With("webhook", "validation")} +} + +type Validator struct { + log *log.Logger +} + +func (v *Validator) ValidateCreate(_ context.Context, obj runtime.Object) (admission.Warnings, error) { + lease, ok := obj.(*v1alpha2.VirtualMachineMACAddressLease) + if !ok { + return nil, fmt.Errorf("expected a new VirtualMachineMACAddressLease but got a %T", obj) + } + + v.log.Info("Validate VirtualMachineMACAddressLease creating", "name", lease.Name) + + if !mac.IsValidAddressFormat(mac.LeaseNameToAddress(lease.Name)) { + return nil, fmt.Errorf("the lease address is not a valid textual representation of an MAC address") + } + + return nil, nil +} + +func (v *Validator) ValidateUpdate(_ context.Context, _, _ runtime.Object) (admission.Warnings, error) { + err := fmt.Errorf("misconfigured webhook rules: update operation not implemented") + v.log.Error("Ensure the correctness of ValidatingWebhookConfiguration", "err", err) + return nil, nil +} + +func (v *Validator) ValidateDelete(_ context.Context, _ runtime.Object) (admission.Warnings, error) { + err := fmt.Errorf("misconfigured webhook rules: delete operation not implemented") + v.log.Error("Ensure the correctness of ValidatingWebhookConfiguration", "err", err) + return nil, nil +} diff --git a/openapi/config-values.yaml b/openapi/config-values.yaml index 9fdc774c8..989981751 100644 --- a/openapi/config-values.yaml +++ b/openapi/config-values.yaml @@ -220,6 +220,19 @@ properties: items: type: string x-examples: ["sc-1", "sc-2"] + network: + description: | + Configuring network settings for virtual machines. + properties: + macAddress: + description: | + Configuring MAC address for virtual machines. + properties: + prefix: + type: string + description: | + MAC address prefix for virtual machines. + Supported formats: xx-xx-xx-xx, xx:xx:xx:xx, xxxxxxxx. logLevel: type: string description: | diff --git a/openapi/doc-ru-config-values.yaml b/openapi/doc-ru-config-values.yaml index 0a0b726d4..befc67a7a 100644 --- a/openapi/doc-ru-config-values.yaml +++ b/openapi/doc-ru-config-values.yaml @@ -128,6 +128,19 @@ properties: type: array items: type: string + network: + description: | + Настройка сетевых параметров для виртуальных машин. + properties: + macAddress: + description: | + Настройка MAC-адреса для виртуальных машин. + properties: + prefix: + type: string + description: | + Префикс MAC-адреса для виртуальных машин. + Поддерживаемые форматы: xx-xx-xx-xx, xx:xx:xx:xx, xxxxxxx. logLevel: type: string description: | diff --git a/templates/virtualization-controller/_helpers.tpl b/templates/virtualization-controller/_helpers.tpl index 7ec2b52e3..2c8dad065 100644 --- a/templates/virtualization-controller/_helpers.tpl +++ b/templates/virtualization-controller/_helpers.tpl @@ -30,6 +30,10 @@ value: "true" - name: VIRTUAL_MACHINE_CIDRS value: {{ join "," .Values.virtualization.virtualMachineCIDRs | quote }} +{{- if and (hasKey .Values.virtualization "network") (hasKey .Values.virtualization.network "macAddress") (hasKey .Values.virtualization.network.macAddress "prefix") }} +- name: VIRTUAL_MACHINE_MAC_ADDRESS_PREFIX +value: {{ .Values.virtualization.network.macAddress.prefix }} +{{- end }} {{- if (hasKey .Values.virtualization "virtualImages") }} - name: VIRTUAL_IMAGE_STORAGE_CLASS value: {{ .Values.virtualization.virtualImages.storageClassName }} @@ -50,6 +54,8 @@ {{- end }} - name: VIRTUAL_MACHINE_IP_LEASES_RETENTION_DURATION value: "10m" +- name: VIRTUAL_MACHINE_MAC_LEASES_RETENTION_DURATION + value: "24h" - name: UPLOADER_INGRESS_HOST value: {{ include "helm_lib_module_public_domain" (list . "virtualization") }} - name: UPLOADER_INGRESS_TLS_SECRET diff --git a/templates/virtualization-controller/rbac-for-us.yaml b/templates/virtualization-controller/rbac-for-us.yaml index c873f497f..b4d7eaa8d 100644 --- a/templates/virtualization-controller/rbac-for-us.yaml +++ b/templates/virtualization-controller/rbac-for-us.yaml @@ -176,6 +176,8 @@ rules: - virtualimages - virtualmachineipaddressleases - virtualmachineipaddresses + - virtualmachinemacaddressleases + - virtualmachinemacaddresses - virtualmachineblockdeviceattachments - virtualmachines - clustervirtualimages @@ -202,6 +204,8 @@ rules: - clustervirtualimages/finalizers - virtualmachineipaddressleases/finalizers - virtualmachineipaddresses/finalizers + - virtualmachinemacaddressleases/finalizers + - virtualmachinemacaddresses/finalizers - virtualmachineoperations/finalizers - virtualmachineclasses/finalizers - virtualdisksnapshots/finalizers @@ -209,6 +213,8 @@ rules: - virtualmachinerestores/finalizers - virtualmachineipaddresses/status - virtualmachineipaddressleases/status + - virtualmachinemacaddresses/status + - virtualmachinemacaddressleases/status - virtualdisks/status - virtualimages/status - virtualmachineblockdeviceattachments/status