Skip to content

Commit b3a509d

Browse files
feat(module): add annotations for kubevirt migration settings
Signed-off-by: Yaroslav Borbat <yaroslav.borbat@flant.com>
1 parent 5fc80f9 commit b3a509d

9 files changed

Lines changed: 424 additions & 228 deletions

File tree

images/hooks/cmd/virtualization-module-hooks/register.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,16 @@ limitations under the License.
1717
package main
1818

1919
import (
20-
_ "hooks/pkg/hooks/discover-kube-apiserver-feature-gates"
21-
2220
_ "hooks/pkg/hooks/ca-discovery"
2321
_ "hooks/pkg/hooks/copy-custom-certificate"
22+
_ "hooks/pkg/hooks/discover-kube-apiserver-feature-gates"
2423
_ "hooks/pkg/hooks/discovery-clusterip-service-for-dvcr"
2524
_ "hooks/pkg/hooks/discovery-workload-nodes"
2625
_ "hooks/pkg/hooks/dvcr-garbage-collection"
2726
_ "hooks/pkg/hooks/generate-secret-for-dvcr"
2827
_ "hooks/pkg/hooks/install-vmclass-generic"
2928
_ "hooks/pkg/hooks/migrate-virthandler-kvm-node-labels"
30-
_ "hooks/pkg/hooks/parallel-outbound-migrations-per-node"
29+
_ "hooks/pkg/hooks/migration-config"
3130
_ "hooks/pkg/hooks/tls-certificates-api"
3231
_ "hooks/pkg/hooks/tls-certificates-api-proxy"
3332
_ "hooks/pkg/hooks/tls-certificates-controller"
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/*
2+
Copyright 2026 Flant JSC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package migration_config
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"hooks/pkg/settings"
23+
"strconv"
24+
25+
"k8s.io/utils/ptr"
26+
27+
"github.com/deckhouse/module-sdk/pkg"
28+
"github.com/deckhouse/module-sdk/pkg/registry"
29+
)
30+
31+
const (
32+
snapshotModuleConfig = "module-config"
33+
moduleConfigJQFilter = `.metadata.annotations`
34+
35+
bandwidthPerMigrationAnnotation = "virtualization.deckhouse.io/bandwidth-per-migration"
36+
completionTimeoutPerGiBAnnotation = "virtualization.deckhouse.io/completion-timeout-per-gib"
37+
parallelOutboundMigrationsPerNodeAnnotation = "virtualization.deckhouse.io/parallel-outbound-migrations-per-node"
38+
progressTimeoutAnnotation = "virtualization.deckhouse.io/progress-timeout"
39+
40+
bandwidthPerMigrationValuesPath = "virtualization.internal.virtConfig.bandwidthPerMigration"
41+
completionTimeoutPerGiBValuesPath = "virtualization.internal.virtConfig.completionTimeoutPerGiB"
42+
parallelOutboundMigrationsPerNodeValuesPath = "virtualization.internal.virtConfig.parallelOutboundMigrationsPerNode"
43+
progressTimeoutValuesPath = "virtualization.internal.virtConfig.progressTimeout"
44+
45+
defaultBandwidthPerMigration = "640Mi"
46+
defaultCompletionTimeoutPerGiB = 800
47+
defaultParallelOutboundMigrationsPerNode = 1
48+
defaultProgressTimeout = 150
49+
)
50+
51+
// migrationParams defines migration parameters configurable via ModuleConfig annotations.
52+
// parallelMigrationsPerCluster is intentionally excluded: it is managed by the
53+
// discovery-workload-nodes hook which reads the actual value from the KubeVirt config.
54+
var migrationParams = []migrationParam{
55+
{
56+
annotation: bandwidthPerMigrationAnnotation,
57+
valuesPath: bandwidthPerMigrationValuesPath,
58+
defaultValue: defaultBandwidthPerMigration,
59+
},
60+
{
61+
annotation: completionTimeoutPerGiBAnnotation,
62+
valuesPath: completionTimeoutPerGiBValuesPath,
63+
defaultValue: defaultCompletionTimeoutPerGiB,
64+
},
65+
{
66+
annotation: parallelOutboundMigrationsPerNodeAnnotation,
67+
valuesPath: parallelOutboundMigrationsPerNodeValuesPath,
68+
defaultValue: defaultParallelOutboundMigrationsPerNode,
69+
},
70+
{
71+
annotation: progressTimeoutAnnotation,
72+
valuesPath: progressTimeoutValuesPath,
73+
defaultValue: defaultProgressTimeout,
74+
},
75+
}
76+
77+
type migrationParam struct {
78+
annotation string
79+
valuesPath string
80+
defaultValue any
81+
}
82+
83+
func (p migrationParam) resolve(annos map[string]string) (any, error) {
84+
val, ok := annos[p.annotation]
85+
if !ok {
86+
return p.defaultValue, nil
87+
}
88+
89+
switch p.defaultValue.(type) {
90+
case int:
91+
v, err := strconv.Atoi(val)
92+
if err != nil {
93+
return nil, fmt.Errorf("failed to parse %q annotation: %w", p.annotation, err)
94+
}
95+
return v, nil
96+
case string:
97+
return val, nil
98+
default:
99+
return nil, fmt.Errorf("unsupported default value type for %q annotation", p.annotation)
100+
}
101+
}
102+
103+
func (p migrationParam) getCurrent(input *pkg.HookInput) any {
104+
switch p.defaultValue.(type) {
105+
case int:
106+
return int(input.Values.Get(p.valuesPath).Int())
107+
case string:
108+
return input.Values.Get(p.valuesPath).String()
109+
default:
110+
return nil
111+
}
112+
}
113+
114+
var _ = registry.RegisterFunc(config, reconcile)
115+
116+
var config = &pkg.HookConfig{
117+
OnBeforeHelm: &pkg.OrderedConfig{Order: 10},
118+
Kubernetes: []pkg.KubernetesConfig{
119+
{
120+
Name: snapshotModuleConfig,
121+
APIVersion: "deckhouse.io/v1alpha1",
122+
Kind: "ModuleConfig",
123+
NameSelector: &pkg.NameSelector{
124+
MatchNames: []string{settings.ModuleName},
125+
},
126+
ExecuteHookOnSynchronization: ptr.To(true),
127+
ExecuteHookOnEvents: ptr.To(true),
128+
JqFilter: moduleConfigJQFilter,
129+
},
130+
},
131+
132+
Queue: fmt.Sprintf("modules/%s", settings.ModuleName),
133+
}
134+
135+
func reconcile(_ context.Context, input *pkg.HookInput) error {
136+
annos, err := annotationsFromSnapshot(input)
137+
if err != nil {
138+
return err
139+
}
140+
141+
for _, param := range migrationParams {
142+
value, err := param.resolve(annos)
143+
if err != nil {
144+
return err
145+
}
146+
if current := param.getCurrent(input); current != value {
147+
input.Values.Set(param.valuesPath, value)
148+
}
149+
}
150+
151+
return nil
152+
}
153+
154+
func annotationsFromSnapshot(input *pkg.HookInput) (map[string]string, error) {
155+
snap := input.Snapshots.Get(snapshotModuleConfig)
156+
if len(snap) < 1 {
157+
return nil, fmt.Errorf("moduleConfig is missing, something wrong with Deckhouse configuration")
158+
}
159+
160+
var annos map[string]string
161+
err := snap[0].UnmarshalTo(&annos)
162+
if err != nil {
163+
return nil, fmt.Errorf("failed to unmarshal moduleConfig annotations: %w", err)
164+
}
165+
166+
return annos, nil
167+
}

0 commit comments

Comments
 (0)