Skip to content

Commit 6435099

Browse files
fix
Signed-off-by: Yaroslav Borbat <[email protected]>
1 parent a005749 commit 6435099

File tree

6 files changed

+259
-6
lines changed

6 files changed

+259
-6
lines changed

images/virtualization-artifact/cmd/virtualization-controller/main.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import (
3737
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
3838

3939
"github.com/deckhouse/deckhouse/pkg/log"
40+
4041
appconfig "github.com/deckhouse/virtualization-controller/pkg/config"
4142
"github.com/deckhouse/virtualization-controller/pkg/controller/cvi"
4243
"github.com/deckhouse/virtualization-controller/pkg/controller/indexer"
@@ -54,6 +55,7 @@ import (
5455
"github.com/deckhouse/virtualization-controller/pkg/controller/vmrestore"
5556
"github.com/deckhouse/virtualization-controller/pkg/controller/vmsnapshot"
5657
"github.com/deckhouse/virtualization-controller/pkg/logger"
58+
"github.com/deckhouse/virtualization-controller/pkg/migration"
5759
"github.com/deckhouse/virtualization-controller/pkg/version"
5860
"github.com/deckhouse/virtualization/api/client/kubeclient"
5961
virtv2alpha1 "github.com/deckhouse/virtualization/api/core/v1alpha2"
@@ -223,6 +225,14 @@ func main() {
223225
// Setup context to gracefully handle termination.
224226
ctx := signals.SetupSignalHandler()
225227

228+
mCtrl := migration.NewController(mgr.GetClient(), log)
229+
if err = mCtrl.Setup(); err != nil {
230+
log.Error(err.Error())
231+
os.Exit(1)
232+
}
233+
234+
mCtrl.Run(ctx)
235+
226236
if err = indexer.IndexALL(ctx, mgr); err != nil {
227237
log.Error(err.Error())
228238
os.Exit(1)

images/virtualization-artifact/pkg/apiserver/registry/vm/rest/add_volume.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"k8s.io/apiserver/pkg/registry/rest"
2929
virtv1 "kubevirt.io/api/core/v1"
3030

31+
"github.com/deckhouse/virtualization-controller/pkg/controller/kvbuilder"
3132
"github.com/deckhouse/virtualization-controller/pkg/tls/certmanager"
3233
virtlisters "github.com/deckhouse/virtualization/api/client/generated/listers/core/v1alpha2"
3334
"github.com/deckhouse/virtualization/api/subresources"
@@ -119,7 +120,7 @@ func (r AddVolumeREST) genMutateRequestHook(opts *subresources.VirtualMachineAdd
119120
Disk: &virtv1.Disk{
120121
Name: opts.Name,
121122
DiskDevice: dd,
122-
Serial: opts.Name,
123+
Serial: kvbuilder.GenerateSerial(opts.Name),
123124
},
124125
}
125126
switch opts.VolumeKind {

images/virtualization-artifact/pkg/controller/kvbuilder/kvvm_utils.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ limitations under the License.
1717
package kvbuilder
1818

1919
import (
20+
"crypto/md5"
21+
"encoding/hex"
2022
"fmt"
2123
"strings"
2224

@@ -62,6 +64,13 @@ func GetOriginalDiskName(prefixedName string) (string, bool) {
6264
return prefixedName, false
6365
}
6466

67+
func GenerateSerial(input string) string {
68+
hasher := md5.New()
69+
hasher.Write([]byte(input))
70+
hashInBytes := hasher.Sum(nil)
71+
return hex.EncodeToString(hashInBytes)
72+
}
73+
6574
type HotPlugDeviceSettings struct {
6675
VolumeName string
6776
PVCName string
@@ -139,7 +148,7 @@ func ApplyVirtualMachineSpec(
139148
if err := kvvm.SetDisk(name, SetDiskOptions{
140149
PersistentVolumeClaim: pointer.GetPointer(vi.Status.Target.PersistentVolumeClaim),
141150
IsEphemeral: true,
142-
Serial: name,
151+
Serial: GenerateSerial(name),
143152
BootOrder: bootOrder,
144153
}); err != nil {
145154
return err
@@ -148,7 +157,7 @@ func ApplyVirtualMachineSpec(
148157
if err := kvvm.SetDisk(name, SetDiskOptions{
149158
ContainerDisk: pointer.GetPointer(vi.Status.Target.RegistryURL),
150159
IsCdrom: imageformat.IsISO(vi.Status.Format),
151-
Serial: name,
160+
Serial: GenerateSerial(name),
152161
BootOrder: bootOrder,
153162
}); err != nil {
154163
return err
@@ -167,7 +176,7 @@ func ApplyVirtualMachineSpec(
167176
if err := kvvm.SetDisk(name, SetDiskOptions{
168177
ContainerDisk: pointer.GetPointer(cvi.Status.Target.RegistryURL),
169178
IsCdrom: imageformat.IsISO(cvi.Status.Format),
170-
Serial: name,
179+
Serial: GenerateSerial(name),
171180
BootOrder: bootOrder,
172181
}); err != nil {
173182
return err
@@ -186,7 +195,7 @@ func ApplyVirtualMachineSpec(
186195
name := GenerateVMDDiskName(bd.Name)
187196
if err := kvvm.SetDisk(name, SetDiskOptions{
188197
PersistentVolumeClaim: pointer.GetPointer(vd.Status.Target.PersistentVolumeClaim),
189-
Serial: name,
198+
Serial: GenerateSerial(name),
190199
BootOrder: bootOrder,
191200
}); err != nil {
192201
return err

images/virtualization-artifact/pkg/logger/attrs.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ limitations under the License.
1616

1717
package logger
1818

19-
import "log/slog"
19+
import (
20+
"encoding/json"
21+
"log/slog"
22+
)
2023

2124
const (
2225
errAttr = "err"
@@ -55,3 +58,11 @@ func SlogController(controller string) slog.Attr {
5558
func SlogCollector(collector string) slog.Attr {
5659
return slog.String(collectorAttr, collector)
5760
}
61+
62+
func SlogTryJson(key string, i interface{}) slog.Attr {
63+
bytes, err := json.Marshal(i)
64+
if err == nil {
65+
return slog.String(key, string(bytes))
66+
}
67+
return slog.Any(key, i)
68+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
Copyright 2025 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
18+
19+
import (
20+
"context"
21+
"sync"
22+
"time"
23+
24+
"github.com/deckhouse/deckhouse/pkg/log"
25+
"sigs.k8s.io/controller-runtime/pkg/client"
26+
27+
"github.com/deckhouse/virtualization-controller/pkg/logger"
28+
)
29+
30+
type constructor func(client client.Client, logger *log.Logger) (Migration, error)
31+
32+
var newMigrations = []constructor{
33+
newQEMUMaxLength36,
34+
}
35+
36+
type Migration interface {
37+
Name() string
38+
IsBlocking() bool
39+
Migrate(ctx context.Context) error
40+
}
41+
42+
type Controller struct {
43+
client client.Client
44+
log *log.Logger
45+
46+
blockingMigrations []Migration
47+
nonBlockingMigrations []Migration
48+
}
49+
50+
func NewController(client client.Client, log *log.Logger) *Controller {
51+
return &Controller{
52+
client: client,
53+
log: log,
54+
}
55+
}
56+
57+
func (c *Controller) Setup() error {
58+
for _, fn := range newMigrations {
59+
m, err := fn(c.client, c.log)
60+
if err != nil {
61+
return err
62+
}
63+
if m.IsBlocking() {
64+
c.blockingMigrations = append(c.blockingMigrations, m)
65+
} else {
66+
c.nonBlockingMigrations = append(c.nonBlockingMigrations, m)
67+
}
68+
}
69+
return nil
70+
}
71+
72+
func (c *Controller) Run(ctx context.Context) {
73+
run := func(wg *sync.WaitGroup, migrations []Migration) {
74+
for _, m := range migrations {
75+
wg.Add(1)
76+
lg := c.log.With("name", m.Name())
77+
lg.Info("Running migration")
78+
79+
go func() {
80+
defer lg.Info("Finished migration")
81+
defer wg.Done()
82+
83+
for {
84+
select {
85+
case <-ctx.Done():
86+
return
87+
default:
88+
if err := m.Migrate(ctx); err != nil {
89+
lg.Error("Failed to run migration, retry after 5s...", logger.SlogErr(err))
90+
time.Sleep(5 * time.Second)
91+
continue
92+
}
93+
break
94+
}
95+
}
96+
}()
97+
}
98+
}
99+
unused := &sync.WaitGroup{}
100+
used := &sync.WaitGroup{}
101+
102+
run(unused, c.nonBlockingMigrations)
103+
run(used, c.blockingMigrations)
104+
// Wait for blocked migrations only
105+
used.Wait()
106+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
Copyright 2025 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
18+
19+
import (
20+
"context"
21+
22+
"github.com/deckhouse/deckhouse/pkg/log"
23+
virtv1 "kubevirt.io/api/core/v1"
24+
"sigs.k8s.io/controller-runtime/pkg/client"
25+
26+
"github.com/deckhouse/virtualization-controller/pkg/controller/kvbuilder"
27+
"github.com/deckhouse/virtualization-controller/pkg/logger"
28+
)
29+
30+
const (
31+
// https://github.com/qemu/qemu/commit/75997e182b695f2e3f0a2d649734952af5caf3ee
32+
maxSerialLength = 36
33+
qemuMaxLength36ControllerName = "qemu-max-length-36"
34+
)
35+
36+
func newQEMUMaxLength36(client client.Client, logger *log.Logger) (Migration, error) {
37+
return &qemuMaxLength36{
38+
client: client,
39+
logger: logger,
40+
}, nil
41+
}
42+
43+
type qemuMaxLength36 struct {
44+
client client.Client
45+
logger *log.Logger
46+
}
47+
48+
func (r *qemuMaxLength36) Name() string {
49+
return qemuMaxLength36ControllerName
50+
}
51+
52+
func (r *qemuMaxLength36) IsBlocking() bool {
53+
return true
54+
}
55+
56+
func (r *qemuMaxLength36) Migrate(ctx context.Context) error {
57+
kvvmList := &virtv1.VirtualMachineList{}
58+
err := r.client.List(ctx, kvvmList)
59+
if err != nil {
60+
return err
61+
}
62+
63+
for i := range kvvmList.Items {
64+
kvvm := &kvvmList.Items[i]
65+
if !r.needMigrate(&kvvm.Spec.Template.Spec) {
66+
continue
67+
}
68+
copied := kvvm.DeepCopy()
69+
r.mutateSpec(&copied.Spec.Template.Spec)
70+
r.logger.Debug("Patch kvvm with new kvvmi spec", logger.SlogTryJson("newSpce", copied.Spec.Template.Spec))
71+
if err = r.client.Patch(ctx, kvvm, client.MergeFrom(copied)); err != nil {
72+
return err
73+
}
74+
}
75+
76+
kvvmiList := &virtv1.VirtualMachineInstanceList{}
77+
err = r.client.List(ctx, kvvmiList)
78+
if err != nil {
79+
return err
80+
}
81+
82+
for i := range kvvmiList.Items {
83+
kvvmi := &kvvmiList.Items[i]
84+
if !r.needMigrate(&kvvmi.Spec) {
85+
continue
86+
}
87+
copied := kvvmi.DeepCopy()
88+
r.mutateSpec(&copied.Spec)
89+
r.logger.Debug("Patch kvvmi with new spec", logger.SlogTryJson("newSpce", copied.Spec))
90+
if err = r.client.Patch(ctx, kvvmi, client.MergeFrom(copied)); err != nil {
91+
return err
92+
}
93+
}
94+
95+
return nil
96+
}
97+
98+
func (r *qemuMaxLength36) needMigrate(spec *virtv1.VirtualMachineInstanceSpec) bool {
99+
for _, d := range spec.Domain.Devices.Disks {
100+
serial := []rune(d.Serial)
101+
if len(serial) > maxSerialLength {
102+
return true
103+
}
104+
}
105+
return false
106+
}
107+
108+
func (r *qemuMaxLength36) mutateSpec(spec *virtv1.VirtualMachineInstanceSpec) {
109+
for i := range spec.Domain.Devices.Disks {
110+
d := &spec.Domain.Devices.Disks[i]
111+
serial := []rune(d.Serial)
112+
if len(serial) > maxSerialLength {
113+
d.Serial = kvbuilder.GenerateSerial(d.Name)
114+
}
115+
}
116+
}

0 commit comments

Comments
 (0)