Skip to content
2 changes: 1 addition & 1 deletion api/core/v1alpha2/cluster_virtual_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const (
//
// With this resource in the cluster, a container image is created and stored in a dedicated Deckhouse Virtualization Container Registry (DVCR).
//
// **Note:** The `metadata.name` field must comply with [Kubernetes object naming conventions](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/) and must not exceed 48 characters.
// **Note:** The `metadata.name` field must comply with [Kubernetes object naming conventions](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/).
// +kubebuilder:object:root=true
// +kubebuilder:metadata:labels={heritage=deckhouse,module=virtualization,backup.deckhouse.io/cluster-config=true}
// +kubebuilder:resource:categories={virtualization-cluster},scope=Cluster,shortName={cvi},singular=clustervirtualimage
Expand Down
2 changes: 1 addition & 1 deletion api/core/v1alpha2/virtual_disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const (
//
// Once a VirtualDisk is created, the following fields in `.spec.persistentVolumeClaim` can be changed: `size` and `storageClassName`. All other fields are immutable.
//
// **Note:** The `metadata.name` field must comply with [Kubernetes object naming conventions](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/) and must not exceed 60 characters.
// **Note:** The `metadata.name` field must comply with [Kubernetes object naming conventions](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/).
// +kubebuilder:object:root=true
// +kubebuilder:metadata:labels={heritage=deckhouse,module=virtualization}
// +kubebuilder:resource:categories={virtualization},scope=Namespaced,shortName={vd},singular=virtualdisk
Expand Down
2 changes: 1 addition & 1 deletion api/core/v1alpha2/virtual_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const (
//
// With this resource in the cluster, a container image is created and stored in a dedicated Deckhouse Virtualization Container Registry (DVCR) or PVC, with the data filled in from the source.
//
// **Note:** The `metadata.name` field must comply with [Kubernetes object naming conventions](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/) and must not exceed 49 characters.
// **Note:** The `metadata.name` field must comply with [Kubernetes object naming conventions](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/).
// +genclient
// +kubebuilder:object:root=true
// +kubebuilder:metadata:labels={heritage=deckhouse,module=virtualization}
Expand Down
2 changes: 1 addition & 1 deletion crds/clustervirtualimages.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ spec:

With this resource in the cluster, a container image is created and stored in a dedicated Deckhouse Virtualization Container Registry (DVCR).

**Note:** The `metadata.name` field must comply with [Kubernetes object naming conventions](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/) and must not exceed 48 characters.
**Note:** The `metadata.name` field must comply with [Kubernetes object naming conventions](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/).
properties:
apiVersion:
description: |-
Expand Down
2 changes: 1 addition & 1 deletion crds/doc-ru-clustervirtualimages.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ spec:

После появления в кластере этого ресурса создаётся образ контейнера, который хранится в специальном реестре контейнеров Deckhouse Virtualization Container Registry (DVCR).

**Важно:** Поле `metadata.name` должно соответствовать [правилам именования объектов Kubernetes](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/) и не должно превышать 48 символов.
**Важно:** Поле `metadata.name` должно соответствовать [правилам именования объектов Kubernetes](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/).
properties:
spec:
properties:
Expand Down
2 changes: 1 addition & 1 deletion crds/doc-ru-virtualdisks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ spec:

После создания VirtualDisk в `.spec.persistentVolumeClaim` можно изменить поля `size` и `storageClassName`. Все остальные поля неизменяемы.

**Важно:** Поле `metadata.name` должно соответствовать [правилам именования объектов Kubernetes](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/) и не должно превышать 60 символов.
**Важно:** Поле `metadata.name` должно соответствовать [правилам именования объектов Kubernetes](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/).
properties:
spec:
properties:
Expand Down
2 changes: 1 addition & 1 deletion crds/doc-ru-virtualimages.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ spec:

После появления в кластере этого ресурса создаётся образ контейнера, который хранится в специальном реестре контейнеров Deckhouse Virtualization Container Registry (DVCR).

**Важно:** Поле `metadata.name` должно соответствовать [правилам именования объектов Kubernetes](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/) и не должно превышать 49 символов.
**Важно:** Поле `metadata.name` должно соответствовать [правилам именования объектов Kubernetes](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/).
properties:
spec:
properties:
Expand Down
2 changes: 1 addition & 1 deletion crds/virtualdisks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ spec:

Once a VirtualDisk is created, the following fields in `.spec.persistentVolumeClaim` can be changed: `size` and `storageClassName`. All other fields are immutable.

**Note:** The `metadata.name` field must comply with [Kubernetes object naming conventions](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/) and must not exceed 60 characters.
**Note:** The `metadata.name` field must comply with [Kubernetes object naming conventions](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/).
properties:
apiVersion:
description: |-
Expand Down
2 changes: 1 addition & 1 deletion crds/virtualimages.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ spec:

With this resource in the cluster, a container image is created and stored in a dedicated Deckhouse Virtualization Container Registry (DVCR) or PVC, with the data filled in from the source.

**Note:** The `metadata.name` field must comply with [Kubernetes object naming conventions](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/) and must not exceed 49 characters.
**Note:** The `metadata.name` field must comply with [Kubernetes object naming conventions](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/).
properties:
apiVersion:
description: |-
Expand Down
23 changes: 8 additions & 15 deletions images/virtualization-artifact/pkg/common/validate/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,14 @@ limitations under the License.

package validate

// MaxDiskNameLen determines the max len of vd.
// Disk and volume name in kubevirt can be a valid container name (len 63) since disk name can become a container name which will fail to schedule if invalid.
// We add prefix "vd-" for the vd name, so max len reduced to 60.
const MaxDiskNameLen = 60

// MaxVirtualImageNameLen determines the max len of vi.
// Disk and volume name in kubevirt can be a valid container name (len 63) since disk name can become a container name which will fail to schedule if invalid.
// We and kubevirt add prefixes "vi-", "volume" and suffix "-init", so max len reduced to 49.
const MaxVirtualImageNameLen = 49

// MaxClusterVirtualImageNameLen determines the max len of cvi.
// Disk and volume name in kubevirt can be a valid container name (len 63) since disk name can become a container name which will fail to schedule if invalid.
// We and kubevirt add prefixes "cvi-", "volume" and suffix "-init", so max len reduced to 48.
const MaxClusterVirtualImageNameLen = 48
// VirtualDisk/VirtualImage/ClusterVirtualImage names are not length-limited by DVP:
// the derived KubeVirt volume/disk name is shortened independently (see
// kvbuilder.GenerateDiskName), and the overall name is already bounded by Kubernetes
// (DNS subdomain, <=253). Only VirtualMachine keeps a DVP limit.

// MaxVirtualMachineNameLen determines the max len of vm.
// The limitation is reportedly associated with the PodDisruptionBudget resource, which has a label containing the virtual machine's name, and the label's value cannot exceed 63 characters.
// Unlike disks/images, a VirtualMachine name is not decoupled: it flows into KubeVirt
// pod names (e.g. the launcher pod "d8v-vm-<name>-...") and label values that cap at
// 63 (Kubernetes does not enforce this, so DVP must). Raising it requires changes in
// the KubeVirt fork.
const MaxVirtualMachineNameLen = 63
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"

"github.com/deckhouse/deckhouse/pkg/log"
"github.com/deckhouse/virtualization-controller/pkg/common/validate"
"github.com/deckhouse/virtualization-controller/pkg/controller/conditions"
"github.com/deckhouse/virtualization/api/core/v1alpha2"
"github.com/deckhouse/virtualization/api/core/v1alpha2/cvicondition"
Expand All @@ -52,10 +51,6 @@ func (v *Validator) ValidateCreate(_ context.Context, obj runtime.Object) (admis
return nil, fmt.Errorf("the ClusterVirtualImage name %q is invalid: '.' is forbidden, allowed name symbols are [0-9a-zA-Z-]", cvi.Name)
}

if len(cvi.Name) > validate.MaxClusterVirtualImageNameLen {
return nil, fmt.Errorf("the ClusterVirtualImage name %q is too long: it must be no more than %d characters", cvi.Name, validate.MaxClusterVirtualImageNameLen)
}

return nil, nil
}

Expand Down Expand Up @@ -87,10 +82,6 @@ func (v *Validator) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Obj
warnings = append(warnings, fmt.Sprintf("the ClusterVirtualImage name %q is invalid as it contains now forbidden symbol '.', allowed symbols for name are [0-9a-zA-Z-]. Create another image with valid name to avoid problems with future updates.", newCVI.Name))
}

if len(newCVI.Name) > validate.MaxClusterVirtualImageNameLen {
warnings = append(warnings, fmt.Sprintf("the ClusterVirtualImage name %q is too long: it must be no more than %d characters", newCVI.Name, validate.MaxClusterVirtualImageNameLen))
}

return warnings, nil
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*
Copyright 2026 Flant JSC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package kvbuilder

import (
"strings"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
kvalidation "k8s.io/apimachinery/pkg/util/validation"

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

var _ = Describe("Derived KubeVirt disk/volume names", func() {
DescribeTable("passthrough: short, valid label names are returned unchanged",
func(name string) {
// Every name allowed by the previous validation must map to the
// byte-identical derived name, so existing volumes are never renamed.
Expect(GenerateVDDiskName(name)).To(Equal("vd-" + name))
},
Entry("typical", "web-data"),
Entry("exactly at budget", strings.Repeat("a", vdNameBudget)),
)

It("hashes names over the budget into a valid label within 63 chars", func() {
name := strings.Repeat("a", vdNameBudget+1)
got := GenerateVDDiskName(name)

Expect(got).To(HavePrefix("vd-"))
Expect(len(got)).To(BeNumerically("<=", 63))
Expect(kvalidation.IsDNS1123Label(got)).To(BeEmpty())
Expect(got).NotTo(Equal("vd-" + name))
Expect(got).To(MatchRegexp(`-[0-9a-f]{16}$`)) // FNV-1a 64-bit suffix
})

It("is deterministic for the same input", func() {
name := strings.Repeat("b", 120)
Expect(GenerateVDDiskName(name)).To(Equal(GenerateVDDiskName(name)))
})

It("allows dots by sanitizing and hashing instead of passthrough", func() {
got := GenerateVDDiskName("disk.2024.backup")
Expect(got).NotTo(ContainSubstring("."))
Expect(kvalidation.IsDNS1123Label(got)).To(BeEmpty())
Expect(got).To(HavePrefix("vd-disk-2024-backup-"))
})

It("does not collide when sanitization makes readable parts equal", func() {
// The dotted name is hashed; its dashed twin is a valid label (passthrough).
Expect(GenerateVDDiskName("disk.2024.backup")).
NotTo(Equal(GenerateVDDiskName("disk-2024-backup")))
})

It("does not collide when long names share a truncated prefix", func() {
base := strings.Repeat("a", vdNameBudget)
Expect(GenerateVDDiskName(base + "-one")).
NotTo(Equal(GenerateVDDiskName(base + "-two")))
})

DescribeTable("containerDisk container name stays within 63 for long image names",
func(gen func(string) string) {
vol := gen(strings.Repeat("a", 200))
// KubeVirt wraps a containerDisk volume name as "volume<name>-init".
container := "volume" + vol + "-init"
Expect(kvalidation.IsDNS1123Label(vol)).To(BeEmpty())
Expect(len(container)).To(BeNumerically("<=", 63), container)
},
Entry("VirtualImage", GenerateVIDiskName),
Entry("ClusterVirtualImage", GenerateCVIDiskName),
)

DescribeTable("GenerateDiskName routes to the kind-specific generator",
func(kind v1alpha2.BlockDeviceKind, gen func(string) string) {
name := strings.Repeat("z", 80)
Expect(GenerateDiskName(kind, name)).To(Equal(gen(name)))
},
Entry("disk", v1alpha2.DiskDevice, GenerateVDDiskName),
Entry("image", v1alpha2.ImageDevice, GenerateVIDiskName),
Entry("cluster image", v1alpha2.ClusterImageDevice, GenerateCVIDiskName),
)

It("round-trips legacy short names through GetOriginalDiskName", func() {
name, kind := GetOriginalDiskName(GenerateVDDiskName("data-disk"))
Expect(name).To(Equal("data-disk"))
Expect(kind).To(Equal(v1alpha2.DiskDevice))
})
})

var _ = Describe("VolumeNameResolver", func() {
It("reverses derived names via candidates, including hashed ones", func() {
longName := strings.Repeat("a", vdNameBudget+10) // forced into the hash branch
r := NewVolumeNameResolver()
r.Add(v1alpha2.DiskDevice, longName)
r.Add(v1alpha2.ImageDevice, "ubuntu")

name, kind := r.Resolve(GenerateVDDiskName(longName))
Expect(name).To(Equal(longName))
Expect(kind).To(Equal(v1alpha2.DiskDevice))

name, kind = r.Resolve(GenerateVIDiskName("ubuntu"))
Expect(name).To(Equal("ubuntu"))
Expect(kind).To(Equal(v1alpha2.ImageDevice))
})

It("falls back to prefix-strip for legacy names not among candidates", func() {
r := NewVolumeNameResolver()
name, kind := r.Resolve("vd-legacy-disk")
Expect(name).To(Equal("legacy-disk"))
Expect(kind).To(Equal(v1alpha2.DiskDevice))
})

It("returns empty kind for non block-device volumes", func() {
r := NewVolumeNameResolver()
_, kind := r.Resolve("cloudinit")
Expect(kind).To(BeEmpty())
})
})

var _ = Describe("detectDiskNameCollisions", func() {
It("passes for distinct block devices (incl. long and dotted names)", func() {
vm := &v1alpha2.VirtualMachine{
Spec: v1alpha2.VirtualMachineSpec{
BlockDeviceRefs: []v1alpha2.BlockDeviceSpecRef{
{Kind: v1alpha2.DiskDevice, Name: "data"},
{Kind: v1alpha2.DiskDevice, Name: strings.Repeat("a", 80)},
{Kind: v1alpha2.ImageDevice, Name: "ubuntu"},
{Kind: v1alpha2.DiskDevice, Name: "disk.with.dots"},
},
},
}
vmbda := map[v1alpha2.VMBDAObjectRef][]*v1alpha2.VirtualMachineBlockDeviceAttachment{
{Kind: v1alpha2.VMBDAObjectRefKindVirtualDisk, Name: "extra"}: nil,
}
Expect(detectDiskNameCollisions(vm, vmbda)).To(Succeed())
})

It("does not flag the same resource present in both spec and VMBDA", func() {
vm := &v1alpha2.VirtualMachine{
Spec: v1alpha2.VirtualMachineSpec{
BlockDeviceRefs: []v1alpha2.BlockDeviceSpecRef{
{Kind: v1alpha2.DiskDevice, Name: "shared"},
},
},
}
vmbda := map[v1alpha2.VMBDAObjectRef][]*v1alpha2.VirtualMachineBlockDeviceAttachment{
{Kind: v1alpha2.VMBDAObjectRefKindVirtualDisk, Name: "shared"}: nil,
}
Expect(detectDiskNameCollisions(vm, vmbda)).To(Succeed())
})
})
Loading
Loading