Skip to content
Open
4 changes: 3 additions & 1 deletion images/hooks/cmd/virtualization-module-hooks/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,18 @@ package main
import (
_ "hooks/pkg/hooks/ca-discovery"
_ "hooks/pkg/hooks/copy-custom-certificate"
_ "hooks/pkg/hooks/create-generic-vmclass"
_ "hooks/pkg/hooks/discovery-clusterip-service-for-dvcr"
_ "hooks/pkg/hooks/discovery-workload-nodes"
_ "hooks/pkg/hooks/drop-helm-labels-from-generic-vmclass"
_ "hooks/pkg/hooks/drop-openshift-labels"
_ "hooks/pkg/hooks/generate-secret-for-dvcr"
_ "hooks/pkg/hooks/migrate-delete-renamed-validation-admission-policy"
_ "hooks/pkg/hooks/migrate-virthandler-kvm-node-labels"
_ "hooks/pkg/hooks/prevent-default-vmclasses-deletion"
_ "hooks/pkg/hooks/tls-certificates-api"
_ "hooks/pkg/hooks/tls-certificates-api-proxy"
_ "hooks/pkg/hooks/tls-certificates-controller"
_ "hooks/pkg/hooks/tls-certificates-dvcr"
_ "hooks/pkg/hooks/update-module-state"
_ "hooks/pkg/hooks/validate-module-config"
)
167 changes: 167 additions & 0 deletions images/hooks/pkg/hooks/create-generic-vmclass/hook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/*
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 create_generic_vmclass

import (
"context"
"fmt"

"hooks/pkg/settings"

"github.com/deckhouse/virtualization/api/core"
"github.com/deckhouse/virtualization/api/core/v1alpha2"

"github.com/deckhouse/module-sdk/pkg"
"github.com/deckhouse/module-sdk/pkg/registry"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
)

const (
moduleStateSecretSnapshot = "module-state-secret"
vmClassSnapshot = "vmclass-generic"

moduleStateSecretName = "module-state"
genericVMClassName = "generic"

apiVersion = core.GroupName + "/" + v1alpha2.Version
)

var _ = registry.RegisterFunc(config, Reconcile)

var config = &pkg.HookConfig{
OnBeforeHelm: &pkg.OrderedConfig{Order: 5},
Kubernetes: []pkg.KubernetesConfig{
{
Name: moduleStateSecretSnapshot,
APIVersion: "v1",
Kind: "Secret",
JqFilter: `{"metadata": .metadata, "data": .data}`,
NameSelector: &pkg.NameSelector{
MatchNames: []string{moduleStateSecretName},
},
NamespaceSelector: &pkg.NamespaceSelector{
NameSelector: &pkg.NameSelector{
MatchNames: []string{settings.ModuleNamespace},
},
},
ExecuteHookOnSynchronization: ptr.To(false),
},
{
Name: vmClassSnapshot,
APIVersion: apiVersion,
Kind: v1alpha2.VirtualMachineClassKind,
JqFilter: `.metadata.name`,
NameSelector: &pkg.NameSelector{
MatchNames: []string{genericVMClassName},
},
LabelSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "virtualization-controller",
"module": settings.ModuleName,
},
},
ExecuteHookOnSynchronization: ptr.To(false),
},
},

Queue: fmt.Sprintf("modules/%s", settings.ModuleName),
}

func Reconcile(_ context.Context, input *pkg.HookInput) error {
moduleStateSecrets := input.Snapshots.Get(moduleStateSecretSnapshot)
vmClasses := input.Snapshots.Get(vmClassSnapshot)

// nothing to do if generic vmclass already exists
if len(vmClasses) > 0 {
return nil
}

// if module-state secret exists and contains generic-vmclass-was-ever-created=true, nothing to do
if len(moduleStateSecrets) > 0 {
var moduleStateSecret corev1.Secret
if err := moduleStateSecrets[0].UnmarshalTo(&moduleStateSecret); err != nil {
return err
}

if string(moduleStateSecret.Data["generic-vmclass-was-ever-created"]) == "true" {
return nil
}
}

input.Logger.Info("Creating generic VirtualMachineClass")

vmClass := &v1alpha2.VirtualMachineClass{
TypeMeta: metav1.TypeMeta{
APIVersion: apiVersion,
Kind: v1alpha2.VirtualMachineClassKind,
},
ObjectMeta: metav1.ObjectMeta{
Name: genericVMClassName,
Labels: map[string]string{
"app": "virtualization-controller",
"module": settings.ModuleName,
},
Comment on lines +116 to +119
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be add a label with some kind of "spec-version" for this vmclass?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand, but why?

},
Spec: v1alpha2.VirtualMachineClassSpec{
CPU: v1alpha2.CPU{
Type: v1alpha2.CPUTypeModel,
Model: "Nehalem",
},
SizingPolicies: []v1alpha2.SizingPolicy{
{
Cores: &v1alpha2.SizingPolicyCores{
Min: 1,
Max: 4,
},
DedicatedCores: []bool{false},
CoreFractions: []v1alpha2.CoreFractionValue{5, 10, 20, 50, 100},
},
{
Cores: &v1alpha2.SizingPolicyCores{
Min: 5,
Max: 8,
},
DedicatedCores: []bool{false},
CoreFractions: []v1alpha2.CoreFractionValue{20, 50, 100},
},
{
Cores: &v1alpha2.SizingPolicyCores{
Min: 9,
Max: 16,
},
DedicatedCores: []bool{true, false},
CoreFractions: []v1alpha2.CoreFractionValue{50, 100},
},
{
Cores: &v1alpha2.SizingPolicyCores{
Min: 17,
Max: 1024,
},
DedicatedCores: []bool{true, false},
CoreFractions: []v1alpha2.CoreFractionValue{100},
},
},
},
}

input.PatchCollector.Create(vmClass)
input.Logger.Info("VirtualMachineClass generic created")

return nil
}
201 changes: 201 additions & 0 deletions images/hooks/pkg/hooks/create-generic-vmclass/hook_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
/*
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 create_generic_vmclass

import (
"context"
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

"github.com/deckhouse/deckhouse/pkg/log"
"github.com/deckhouse/module-sdk/pkg"
"github.com/deckhouse/module-sdk/testing/mock"
"github.com/deckhouse/virtualization/api/core/v1alpha2"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestCreateGenericVMClass(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Create Generic VMClass Suite")
}

var _ = Describe("Create Generic VMClass hook", func() {
var (
snapshots *mock.SnapshotsMock
patchCollector *mock.PatchCollectorMock
)

newInput := func() *pkg.HookInput {
return &pkg.HookInput{
Snapshots: snapshots,
PatchCollector: patchCollector,
Logger: log.NewNop(),
}
}

BeforeEach(func() {
snapshots = mock.NewSnapshotsMock(GinkgoT())
patchCollector = mock.NewPatchCollectorMock(GinkgoT())
})

AfterEach(func() {
snapshots = nil
patchCollector = nil
})

Context("when module-state secret exists with generic-vmclass-was-ever-created=true", func() {
BeforeEach(func() {
moduleStateSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "module-state",
Namespace: "d8-virtualization",
},
Data: map[string][]byte{
"generic-vmclass-was-ever-created": []byte("true"),
},
}

snapshots.GetMock.When(moduleStateSecretSnapshot).Then([]pkg.Snapshot{
mock.NewSnapshotMock(GinkgoT()).UnmarshalToMock.Set(func(v any) error {
secret, ok := v.(*corev1.Secret)
Expect(ok).To(BeTrue())
*secret = *moduleStateSecret
return nil
}),
})
})

It("should not recreate generic vmclass when it doesn't exist but state says it was created (user may have deleted it intentionally)", func() {
snapshots.GetMock.When(vmClassSnapshot).Then([]pkg.Snapshot{})

patchCollector.CreateMock.Optional()

Expect(Reconcile(context.Background(), newInput())).To(Succeed())
Expect(patchCollector.CreateMock.Calls()).To(HaveLen(0))
})

})

Context("when module-state secret doesn't exist", func() {
BeforeEach(func() {
snapshots.GetMock.When(moduleStateSecretSnapshot).Then([]pkg.Snapshot{})
})

It("should create generic vmclass when it doesn't exist", func() {
snapshots.GetMock.When(vmClassSnapshot).Then([]pkg.Snapshot{})

patchCollector.CreateMock.Set(func(obj interface{}) {
vmClass, ok := obj.(*v1alpha2.VirtualMachineClass)
Expect(ok).To(BeTrue())
Expect(vmClass.Name).To(Equal("generic"))
Expect(vmClass.Labels).To(Equal(map[string]string{
"app": "virtualization-controller",
"module": "virtualization",
}))
})

Expect(Reconcile(context.Background(), newInput())).To(Succeed())
Expect(patchCollector.CreateMock.Calls()).To(HaveLen(1))
})

})

Context("when module-state secret exists but doesn't contain generic-vmclass-was-ever-created", func() {
BeforeEach(func() {
moduleStateSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "module-state",
Namespace: "d8-virtualization",
},
Data: map[string][]byte{
"other-key": []byte("other-value"),
},
}

snapshots.GetMock.When(moduleStateSecretSnapshot).Then([]pkg.Snapshot{
mock.NewSnapshotMock(GinkgoT()).UnmarshalToMock.Set(func(v any) error {
secret, ok := v.(*corev1.Secret)
Expect(ok).To(BeTrue())
*secret = *moduleStateSecret
return nil
}),
})
})

It("should create generic vmclass when it doesn't exist", func() {
snapshots.GetMock.When(vmClassSnapshot).Then([]pkg.Snapshot{})

patchCollector.CreateMock.Set(func(obj interface{}) {
vmClass, ok := obj.(*v1alpha2.VirtualMachineClass)
Expect(ok).To(BeTrue())
Expect(vmClass.Name).To(Equal("generic"))
Expect(vmClass.Labels).To(Equal(map[string]string{
"app": "virtualization-controller",
"module": "virtualization",
}))
})

Expect(Reconcile(context.Background(), newInput())).To(Succeed())
Expect(patchCollector.CreateMock.Calls()).To(HaveLen(1))
})

})

Context("when module-state secret exists with generic-vmclass-was-ever-created=false", func() {
BeforeEach(func() {
moduleStateSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "module-state",
Namespace: "d8-virtualization",
},
Data: map[string][]byte{
"generic-vmclass-was-ever-created": []byte("false"),
},
}

snapshots.GetMock.When(moduleStateSecretSnapshot).Then([]pkg.Snapshot{
mock.NewSnapshotMock(GinkgoT()).UnmarshalToMock.Set(func(v any) error {
secret, ok := v.(*corev1.Secret)
Expect(ok).To(BeTrue())
*secret = *moduleStateSecret
return nil
}),
})
})

It("should create generic vmclass when it doesn't exist", func() {
snapshots.GetMock.When(vmClassSnapshot).Then([]pkg.Snapshot{})

patchCollector.CreateMock.Set(func(obj interface{}) {
vmClass, ok := obj.(*v1alpha2.VirtualMachineClass)
Expect(ok).To(BeTrue())
Expect(vmClass.Name).To(Equal("generic"))
Expect(vmClass.Labels).To(Equal(map[string]string{
"app": "virtualization-controller",
"module": "virtualization",
}))
})

Expect(Reconcile(context.Background(), newInput())).To(Succeed())
Expect(patchCollector.CreateMock.Calls()).To(HaveLen(1))
})

})
})
Loading
Loading