Skip to content

Commit

Permalink
Merge pull request kubevirt#1207 from j-griffith/sn_with_cirros
Browse files Browse the repository at this point in the history
Add optional serial number param to disk creation
  • Loading branch information
rmohr authored Jun 22, 2018
2 parents aa08498 + 7f6723e commit f38377a
Show file tree
Hide file tree
Showing 12 changed files with 170 additions and 0 deletions.
4 changes: 4 additions & 0 deletions api/openapi-spec/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -3487,6 +3487,10 @@
"description": "Name is the device name",
"type": "string"
},
"serial": {
"description": "Serial provides the ability to specify a serial number for the disk device.\n+optional",
"type": "string"
},
"volumeName": {
"description": "Name of the volume which is referenced.\nMust match the Name of a Volume.",
"type": "string"
Expand Down
2 changes: 2 additions & 0 deletions manifests/generated/vm-resource.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ spec:
type: boolean
name:
type: string
serial:
type: string
volumeName:
type: string
required:
Expand Down
2 changes: 2 additions & 0 deletions manifests/generated/vmi-resource.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ spec:
type: boolean
name:
type: string
serial:
type: string
volumeName:
type: string
required:
Expand Down
2 changes: 2 additions & 0 deletions manifests/generated/vmipreset-resource.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ spec:
type: boolean
name:
type: string
serial:
type: string
volumeName:
type: string
required:
Expand Down
2 changes: 2 additions & 0 deletions manifests/generated/vmirs-resource.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ spec:
type: boolean
name:
type: string
serial:
type: string
volumeName:
type: string
required:
Expand Down
7 changes: 7 additions & 0 deletions pkg/api/v1/openapi_generated.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,13 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
Format: "int32",
},
},
"serial": {
SchemaProps: spec.SchemaProps{
Description: "Serial provides the ability to specify a serial number for the disk device.",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"name", "volumeName"},
},
Expand Down
3 changes: 3 additions & 0 deletions pkg/api/v1/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ type Disk struct {
// Disks without a boot order are not tried if a disk with a boot order exists.
// +optional
BootOrder *uint `json:"bootOrder,omitempty"`
// Serial provides the ability to specify a serial number for the disk device.
// +optional
Serial string `json:"serial,omitempty"`
}

// Represents the target of a volume to mount.
Expand Down
1 change: 1 addition & 0 deletions pkg/api/v1/schema_swagger_generated.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ func (Disk) SwaggerDoc() map[string]string {
"name": "Name is the device name",
"volumeName": "Name of the volume which is referenced.\nMust match the Name of a Volume.",
"bootOrder": "BootOrder is an integer value > 0, used to determine ordering of boot devices.\nLower values take precedence.\nDisks without a boot order are not tried if a disk with a boot order exists.\n+optional",
"serial": "Serial provides the ability to specify a serial number for the disk device.\n+optional",
}
}

Expand Down
19 changes: 19 additions & 0 deletions pkg/api/v1/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,14 @@ var exampleJSON = `{
"bus": "virtio",
"readonly": true
}
},
{
"name": "disk1",
"volumeName": "volume4",
"disk": {
"bus": "virtio"
},
"serial": "sn-11223344"
}
],
"interfaces": [
Expand Down Expand Up @@ -240,6 +248,17 @@ var _ = Describe("Schema", func() {
},
},
},
{
Name: "disk1",
VolumeName: "volume4",
Serial: "sn-11223344",
DiskDevice: DiskDevice{
Disk: &DiskTarget{
Bus: "virtio",
ReadOnly: false,
},
},
},
}

exampleVMI.Spec.Volumes = []Volume{
Expand Down
21 changes: 21 additions & 0 deletions pkg/virt-api/validating-webhook/validating-webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"fmt"
"io/ioutil"
"net/http"
"regexp"

"k8s.io/apimachinery/pkg/api/resource"

Expand All @@ -42,6 +43,7 @@ import (
const (
cloudInitMaxLen = 2048
arrayLenMax = 256
maxStrLen = 256
)

var validInterfaceModels = []string{"e1000", "e1000e", "ne2k_pci", "pcnet", "rtl8139", "virtio"}
Expand Down Expand Up @@ -187,6 +189,25 @@ func validateDisks(field *k8sfield.Path, disks []v1.Disk) []metav1.StatusCause {
Field: field.Index(idx).Child("bootorder").String(),
})
}

// Verify serial number is made up of valid characters for libvirt, if provided
isValid := regexp.MustCompile(`^[A-Za-z0-9_.+-]+$`).MatchString
if disk.Serial != "" && !isValid(disk.Serial) {
causes = append(causes, metav1.StatusCause{
Type: metav1.CauseTypeFieldValueInvalid,
Message: fmt.Sprintf("%s must be made up of the following characters [A-Za-z0-9_.+-], if specified", field.Index(idx).String()),
Field: field.Index(idx).Child("serial").String(),
})
}

// Verify serial number is within valid length, if provided
if disk.Serial != "" && len([]rune(disk.Serial)) > maxStrLen {
causes = append(causes, metav1.StatusCause{
Type: metav1.CauseTypeFieldValueInvalid,
Message: fmt.Sprintf("%s must be less than or equal to %d in length, if specified", field.Index(idx).String(), maxStrLen),
Field: field.Index(idx).Child("serial").String(),
})
}
}

return causes
Expand Down
60 changes: 60 additions & 0 deletions pkg/virt-api/validating-webhook/validating-webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"strings"

. "github.com/onsi/ginkgo"
"github.com/onsi/ginkgo/extensions/table"
Expand Down Expand Up @@ -1044,6 +1045,65 @@ var _ = Describe("Validating Webhook", func() {
Expect(len(causes)).To(Equal(1))
Expect(causes[0].Field).To(Equal("fake[0].bootorder"))
})

It("should reject invalid SN characters", func() {
vmi := v1.NewMinimalVMI("testvmi")
order := uint(1)
sn := "$$$$"

vmi.Spec.Domain.Devices.Disks = append(vmi.Spec.Domain.Devices.Disks, v1.Disk{
Name: "testdisk2",
VolumeName: "testvolume2",
BootOrder: &order,
Serial: sn,
DiskDevice: v1.DiskDevice{
Disk: &v1.DiskTarget{},
},
})

causes := validateDisks(k8sfield.NewPath("fake"), vmi.Spec.Domain.Devices.Disks)
Expect(len(causes)).To(Equal(1))
Expect(causes[0].Field).To(Equal("fake[0].serial"))
})

It("should reject SN > maxStrLen characters", func() {
vmi := v1.NewMinimalVMI("testvmi")
order := uint(1)
sn := strings.Repeat("1", maxStrLen+1)

vmi.Spec.Domain.Devices.Disks = append(vmi.Spec.Domain.Devices.Disks, v1.Disk{
Name: "testdisk2",
VolumeName: "testvolume2",
BootOrder: &order,
Serial: sn,
DiskDevice: v1.DiskDevice{
Disk: &v1.DiskTarget{},
},
})

causes := validateDisks(k8sfield.NewPath("fake"), vmi.Spec.Domain.Devices.Disks)
Expect(len(causes)).To(Equal(1))
Expect(causes[0].Field).To(Equal("fake[0].serial"))
})

It("should accept valid SN", func() {
vmi := v1.NewMinimalVMI("testvmi")
order := uint(1)

vmi.Spec.Domain.Devices.Disks = append(vmi.Spec.Domain.Devices.Disks, v1.Disk{
Name: "testdisk2",
VolumeName: "testvolume2",
BootOrder: &order,
Serial: "SN-1_a",
DiskDevice: v1.DiskDevice{
Disk: &v1.DiskTarget{},
},
})

causes := validateDisks(k8sfield.NewPath("fake"), vmi.Spec.Domain.Devices.Disks)
Expect(len(causes)).To(Equal(0))
})

})
})

Expand Down
47 changes: 47 additions & 0 deletions tests/storage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package tests_test

import (
"flag"
"fmt"
"time"

"github.com/google/goexpect"
Expand All @@ -36,6 +37,10 @@ import (
"kubevirt.io/kubevirt/tests"
)

const (
diskSerial = "FB-fb_18030C10002032"
)

type VMICreationFunc func(string) *v1.VirtualMachineInstance

var _ = Describe("Storage", func() {
Expand Down Expand Up @@ -155,6 +160,48 @@ var _ = Describe("Storage", func() {

})

Context("With an emptyDisk defined and a specified serial number", func() {
// The following case is mostly similar to the alpine PVC test above, except using different VirtualMachineInstance.
It("should create a writeable emptyDisk with the specified serial number", func() {

// Start the VirtualMachineInstance with the empty disk attached
vmi := tests.NewRandomVMIWithEphemeralDiskAndUserdata(tests.RegistryDiskFor(tests.RegistryDiskCirros), "echo hi!")
vmi.Spec.Domain.Devices.Disks = append(vmi.Spec.Domain.Devices.Disks, v1.Disk{
Name: "emptydisk1",
VolumeName: "emptydiskvolume1",
Serial: diskSerial,
DiskDevice: v1.DiskDevice{
Disk: &v1.DiskTarget{
Bus: "virtio",
},
},
})
vmi.Spec.Volumes = append(vmi.Spec.Volumes, v1.Volume{
Name: "emptydiskvolume1",
VolumeSource: v1.VolumeSource{
EmptyDisk: &v1.EmptyDiskSource{
Capacity: resource.MustParse("1Gi"),
},
},
})
RunVMIAndExpectLaunch(vmi, false, 90)

expecter, err := tests.LoggedInCirrosExpecter(vmi)
Expect(err).To(BeNil())
defer expecter.Close()

snQuery := fmt.Sprintf("sudo find /sys -type f -regex \".*/block/vdb/serial\" | xargs cat && echo %s\n", diskSerial)
By("Checking for the specified serial number")
res, err := expecter.ExpectBatch([]expect.Batcher{
&expect.BSnd{S: snQuery},
&expect.BExp{R: diskSerial},
}, 10*time.Second)
log.DefaultLogger().Object(vmi).Infof("%v", res)
Expect(err).To(BeNil())
})

})

Context("With ephemeral alpine PVC", func() {
// The following case is mostly similar to the alpine PVC test above, except using different VirtualMachineInstance.
It("should be successfully started", func() {
Expand Down

0 comments on commit f38377a

Please sign in to comment.