Skip to content

Commit

Permalink
Cope with different container runtimes
Browse files Browse the repository at this point in the history
Every container runtime reports the imageID slightly different. However
they all contain the digest in a form which can be extracted via regexp
by searching for `sha256:<digest>`.

If the digest can't be extracted the migration will not proceed to avoid
corrupting guests.

Signed-off-by: Roman Mohr <rmohr@redhat.com>
  • Loading branch information
rmohr committed Oct 11, 2021
1 parent e678c58 commit 15cc1ee
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 25 deletions.
26 changes: 24 additions & 2 deletions pkg/container-disk/container-disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"os"
"path"
"path/filepath"
"regexp"
"strconv"
"strings"

Expand Down Expand Up @@ -53,6 +54,8 @@ const KernelBootVolumeName = KernelBootName + "-volume"

const ephemeralStorageOverheadSize = "50M"

var digestRegex = regexp.MustCompile(`sha256:([a-zA-Z0-9]+)`)

func GetLegacyVolumeMountDirOnHost(vmi *v1.VirtualMachineInstance) string {
return filepath.Join(mountBaseDir, string(vmi.UID))
}
Expand Down Expand Up @@ -361,7 +364,7 @@ func getContainerDiskSocketBasePath(baseDir, podUID string) string {
// ExtractImageIDsFromSourcePod takes the VMI and its source pod to determine the exact image used by containerdisks and boot container images,
// which is recorded in the status section of a started pod.
// It returns a map where the key is the vlume name and the value is the imageID
func ExtractImageIDsFromSourcePod(vmi *v1.VirtualMachineInstance, sourcePod *kubev1.Pod) (imageIDs map[string]string) {
func ExtractImageIDsFromSourcePod(vmi *v1.VirtualMachineInstance, sourcePod *kubev1.Pod) (imageIDs map[string]string, err error) {
imageIDs = map[string]string{}
for _, volume := range vmi.Spec.Volumes {
if volume.ContainerDisk == nil {
Expand All @@ -382,11 +385,30 @@ func ExtractImageIDsFromSourcePod(vmi *v1.VirtualMachineInstance, sourcePod *kub
if _, exists := imageIDs[key]; !exists {
continue
}
imageIDs[key] = status.ImageID
imageID, err := toImageWithDigest(status.Image, status.ImageID)
if err != nil {
return nil, err
}
imageIDs[key] = imageID
}
return
}

func toImageWithDigest(image string, imageID string) (string, error) {
baseImage := image
if strings.LastIndex(image, "@sha256:") != -1 {
baseImage = strings.Split(image, "@sha256:")[0]
} else if colonIndex := strings.LastIndex(image, ":"); colonIndex > strings.LastIndex(image, "/") {
baseImage = baseImage[:colonIndex]
}

digestMatches := digestRegex.FindStringSubmatch(imageID)
if len(digestMatches) < 2 {
return "", fmt.Errorf("failed to identify image digest for container %q with id %q", image, imageID)
}
return fmt.Sprintf("%s@sha256:%s", baseImage, digestMatches[1]), nil
}

func isImageVolume(containerName string) bool {
return strings.HasPrefix(containerName, "volume")
}
Expand Down
104 changes: 82 additions & 22 deletions pkg/container-disk/container-disk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"os"
"os/user"
"path/filepath"
"strings"

. "github.com/onsi/ginkgo"
"github.com/onsi/ginkgo/extensions/table"
Expand Down Expand Up @@ -270,46 +271,80 @@ var _ = Describe("ContainerDisk", func() {
appendNonContainerDisk(vmi, "disk3")
appendContainerDisk(vmi, "disk2")

pod := &k8sv1.Pod{Status: k8sv1.PodStatus{}}
containers := GenerateContainers(vmi, nil, "a-name", "something")
for idx, container := range containers {
pod.Status.ContainerStatuses = append(pod.Status.ContainerStatuses, k8sv1.ContainerStatus{Name: container.Name, ImageID: fmt.Sprintf("finalimg:%v", idx)})
}
pod := createMigrationSourcePod(vmi)

imageIDs := ExtractImageIDsFromSourcePod(vmi, pod)
Expect(imageIDs).To(HaveKeyWithValue("disk1", "finalimg:0"))
Expect(imageIDs).To(HaveKeyWithValue("disk2", "finalimg:1"))
imageIDs, err := ExtractImageIDsFromSourcePod(vmi, pod)
Expect(err).ToNot(HaveOccurred())
Expect(imageIDs).To(HaveKeyWithValue("disk1", "someimage@sha256:0"))
Expect(imageIDs).To(HaveKeyWithValue("disk2", "someimage@sha256:1"))
Expect(imageIDs).To(HaveLen(2))

newContainers := GenerateContainers(vmi, imageIDs, "a-name", "something")
Expect(newContainers[0].Image).To(Equal("finalimg:0"))
Expect(newContainers[1].Image).To(Equal("finalimg:1"))
Expect(newContainers[0].Image).To(Equal("someimage@sha256:0"))
Expect(newContainers[1].Image).To(Equal("someimage@sha256:1"))
})
It("for a new migration pod with a containerDisk and a kernel image", func() {
vmi := v1.NewMinimalVMI("myvmi")
appendContainerDisk(vmi, "disk1")
appendNonContainerDisk(vmi, "disk3")

vmi.Spec.Domain.Firmware = &v1.Firmware{KernelBoot: &v1.KernelBoot{Container: &v1.KernelBootContainer{Image: "myimage"}}}
vmi.Spec.Domain.Firmware = &v1.Firmware{KernelBoot: &v1.KernelBoot{Container: &v1.KernelBootContainer{Image: "someimage:v1.2.3.4"}}}

pod := &k8sv1.Pod{Status: k8sv1.PodStatus{}}
containdiskContainers := GenerateContainers(vmi, nil, "a-name", "something")
bootContainer := GenerateKernelBootContainer(vmi, nil, "a-name", "something")
for idx, container := range append(containdiskContainers, *bootContainer) {
pod.Status.ContainerStatuses = append(pod.Status.ContainerStatuses, k8sv1.ContainerStatus{Name: container.Name, ImageID: fmt.Sprintf("finalimg:%v", idx)})
}
pod := createMigrationSourcePod(vmi)

imageIDs := ExtractImageIDsFromSourcePod(vmi, pod)
Expect(imageIDs).To(HaveKeyWithValue("disk1", "finalimg:0"))
Expect(imageIDs).To(HaveKeyWithValue("kernel-boot-volume", "finalimg:1"))
imageIDs, err := ExtractImageIDsFromSourcePod(vmi, pod)
Expect(err).ToNot(HaveOccurred())
Expect(imageIDs).To(HaveKeyWithValue("disk1", "someimage@sha256:0"))
Expect(imageIDs).To(HaveKeyWithValue("kernel-boot-volume", "someimage@sha256:bootcontainer"))
Expect(imageIDs).To(HaveLen(2))

newContainers := GenerateContainers(vmi, imageIDs, "a-name", "something")
newBootContainer := GenerateKernelBootContainer(vmi, imageIDs, "a-name", "something")
newContainers = append(newContainers, *newBootContainer)
Expect(newContainers[0].Image).To(Equal("finalimg:0"))
Expect(newContainers[1].Image).To(Equal("finalimg:1"))
Expect(newContainers[0].Image).To(Equal("someimage@sha256:0"))
Expect(newContainers[1].Image).To(Equal("someimage@sha256:bootcontainer"))
})

It("should fail if it can't detect a reproducible imageID", func() {
vmi := v1.NewMinimalVMI("myvmi")
appendContainerDisk(vmi, "disk1")
pod := createMigrationSourcePod(vmi)
pod.Status.ContainerStatuses[0].ImageID = "rubish"
_, err := ExtractImageIDsFromSourcePod(vmi, pod)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal(`failed to identify image digest for container "someimage:v1.2.3.4" with id "rubish"`))
})

table.DescribeTable("It should detect the image ID from", func(imageID string) {
expected := "myregistry.io/myimage@sha256:4gjffGJlg4"
res, err := toImageWithDigest("myregistry.io/myimage", imageID)
Expect(err).ToNot(HaveOccurred())
Expect(res).To(Equal(expected))
res, err = toImageWithDigest("myregistry.io/myimage:1234", imageID)
Expect(err).ToNot(HaveOccurred())
Expect(res).To(Equal(expected))
res, err = toImageWithDigest("myregistry.io/myimage:latest", imageID)
Expect(err).ToNot(HaveOccurred())
Expect(res).To(Equal(expected))
},
table.Entry("docker", "docker://sha256:4gjffGJlg4"),
table.Entry("dontainerd", "sha256:4gjffGJlg4"),
table.Entry("cri-o", "myregistry/myimage@sha256:4gjffGJlg4"),
)

table.DescribeTable("It should detect the base image from", func(given, expected string) {
res, err := toImageWithDigest(given, "docker://sha256:4gjffGJlg4")
Expect(err).ToNot(HaveOccurred())
Expect(strings.Split(res, "@sha256:")[0]).To(Equal(expected))
},
table.Entry("image with registry and no tags or shasum", "myregistry.io/myimage", "myregistry.io/myimage"),
table.Entry("image with registry and tag", "myregistry.io/myimage:latest", "myregistry.io/myimage"),
table.Entry("image with registry and shasum", "myregistry.io/myimage@sha256:123534", "myregistry.io/myimage"),
table.Entry("image with registry and no tags or shasum and custom port", "myregistry.io:5000/myimage", "myregistry.io:5000/myimage"),
table.Entry("image with registry and tag and custom port", "myregistry.io:5000/myimage:latest", "myregistry.io:5000/myimage"),
table.Entry("image with registry and shasum and custom port", "myregistry.io:5000/myimage@sha256:123534", "myregistry.io:5000/myimage"),
table.Entry("image with registry and shasum and custom port and group", "myregistry.io:5000/mygroup/myimage@sha256:123534", "myregistry.io:5000/mygroup/myimage"),
)
})
})
})
Expand Down Expand Up @@ -345,3 +380,28 @@ func appendNonContainerDisk(vmi *v1.VirtualMachineInstance, diskName string) {
},
})
}

func createMigrationSourcePod(vmi *v1.VirtualMachineInstance) *k8sv1.Pod {
pod := &k8sv1.Pod{Status: k8sv1.PodStatus{}}
containers := GenerateContainers(vmi, nil, "a-name", "something")

for idx, container := range containers {
status := k8sv1.ContainerStatus{
Name: container.Name,
Image: container.Image,
ImageID: fmt.Sprintf("finalimg@sha256:%v", idx),
}
pod.Status.ContainerStatuses = append(pod.Status.ContainerStatuses, status)
}
bootContainer := GenerateKernelBootContainer(vmi, nil, "a-name", "something")
if bootContainer != nil {
status := k8sv1.ContainerStatus{
Name: bootContainer.Name,
Image: bootContainer.Image,
ImageID: fmt.Sprintf("finalimg@sha256:%v", "bootcontainer"),
}
pod.Status.ContainerStatuses = append(pod.Status.ContainerStatuses, status)
}

return pod
}
6 changes: 5 additions & 1 deletion pkg/virt-controller/services/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,11 @@ func (t *templateService) RenderLaunchManifestNoVm(vmi *v1.VirtualMachineInstanc
}

func (t *templateService) RenderMigrationManifest(vmi *v1.VirtualMachineInstance, pod *k8sv1.Pod) (*k8sv1.Pod, error) {
return t.renderLaunchManifest(vmi, containerdisk.ExtractImageIDsFromSourcePod(vmi, pod), false)
reproducibleImageIDs, err := containerdisk.ExtractImageIDsFromSourcePod(vmi, pod)
if err != nil {
return nil, fmt.Errorf("can not proceed with the migration when no reproducible image digest can be detected: %v", err)
}
return t.renderLaunchManifest(vmi, reproducibleImageIDs, false)
}

func (t *templateService) RenderLaunchManifest(vmi *v1.VirtualMachineInstance) (*k8sv1.Pod, error) {
Expand Down

0 comments on commit 15cc1ee

Please sign in to comment.