From d788d4af2ffad0affb3f8860813bb42ec673091d Mon Sep 17 00:00:00 2001 From: ChengyuZhu6 Date: Fri, 25 Aug 2023 10:30:56 +0800 Subject: [PATCH] runtime: Add KataVirtualVolume struct in runtime Add the corresponding data structure in the runtime part according to kata-containers/kata-containers/pull/7698. Signed-off-by: ChengyuZhu6 --- .../virtcontainers/types/virtual_volume.go | 156 +++++++++++ .../types/virtual_volume_test.go | 246 ++++++++++++++++++ 2 files changed, 402 insertions(+) create mode 100644 src/runtime/virtcontainers/types/virtual_volume.go create mode 100644 src/runtime/virtcontainers/types/virtual_volume_test.go diff --git a/src/runtime/virtcontainers/types/virtual_volume.go b/src/runtime/virtcontainers/types/virtual_volume.go new file mode 100644 index 0000000000..2d93519b01 --- /dev/null +++ b/src/runtime/virtcontainers/types/virtual_volume.go @@ -0,0 +1,156 @@ +package types + +import ( + "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" + "strings" + + "github.com/pkg/errors" +) + +const ( + minBlockSize = 1 << 9 + maxBlockSize = 1 << 19 +) + +const ( + KataVirtualVolumeDirectBlockType = "direct_block" + KataVirtualVolumeImageRawBlockType = "image_raw_block" + KataVirtualVolumeLayerRawBlockType = "layer_raw_block" + KataVirtualVolumeImageNydusBlockType = "image_nydus_block" + KataVirtualVolumeLayerNydusBlockType = "layer_nydus_block" + KataVirtualVolumeImageNydusFsType = "image_nydus_fs" + KataVirtualVolumeLayerNydusFsType = "layer_nydus_fs" + KataVirtualVolumeImageGuestPullType = "image_guest_pull" +) + +// DmVerityInfo contains configuration information for DmVerity device. +type DmVerityInfo struct { + HashType string `json:"hashtype"` + Hash string `json:"hash"` + BlockNum uint64 `json:"blocknum"` + Blocksize uint64 `json:"blocksize"` + Hashsize uint64 `json:"hashsize"` + Offset uint64 `json:"offset"` +} + +// DirectAssignedVolume contains meta information for a directly assigned volume. +type DirectAssignedVolume struct { + Metadata map[string]string `json:"metadata"` +} + +// ImagePullVolume contains meta information for pulling an image inside the guest. +type ImagePullVolume struct { + Metadata map[string]string `json:"metadata"` +} + +// NydusImageVolume contains Nydus image volume information. +type NydusImageVolume struct { + Config string `json:"config"` + SnapshotDir string `json:"snapshot_dir"` +} + +// KataVirtualVolume encapsulates information for extra mount options and direct volumes. +type KataVirtualVolume struct { + VolumeType string `json:"volume_type"` + Source string `json:"source,omitempty"` + FSType string `json:"fs_type,omitempty"` + Options []string `json:"options,omitempty"` + DirectVolume *DirectAssignedVolume `json:"direct_volume,omitempty"` + ImagePull *ImagePullVolume `json:"image_pull,omitempty"` + NydusImage *NydusImageVolume `json:"nydus_image,omitempty"` + DmVerity *DmVerityInfo `json:"dm_verity,omitempty"` +} + +func (d *DmVerityInfo) IsValid() error { + err := d.validateHashType() + if err != nil { + return err + } + + if d.BlockNum == 0 || d.BlockNum > uint64(^uint32(0)) { + return fmt.Errorf("Zero block count for DmVerity device %s", d.Hash) + } + + if !isValidBlockSize(d.Blocksize) || !isValidBlockSize(d.Hashsize) { + return fmt.Errorf("Unsupported verity block size: data_block_size = %d, hash_block_size = %d", d.Blocksize, d.Hashsize) + } + + if d.Offset%d.Hashsize != 0 || d.Offset < d.Blocksize*d.BlockNum { + return fmt.Errorf("Invalid hashvalue offset %d for DmVerity device %s", d.Offset, d.Hash) + } + + return nil +} + +func (d *DirectAssignedVolume) IsValid() bool { + return d.Metadata != nil +} + +func (i *ImagePullVolume) IsValid() bool { + return i.Metadata != nil +} + +func (n *NydusImageVolume) IsValid() bool { + return len(n.Config) > 0 || len(n.SnapshotDir) > 0 +} + +func (k *KataVirtualVolume) IsValid() bool { + return len(k.VolumeType) > 0 && + (k.DirectVolume == nil || k.DirectVolume.IsValid()) && + (k.ImagePull == nil || k.ImagePull.IsValid()) && + (k.NydusImage == nil || k.NydusImage.IsValid()) && + (k.DmVerity == nil || k.DmVerity.IsValid() == nil) +} + +func (d *DmVerityInfo) validateHashType() error { + switch strings.ToLower(d.HashType) { + case "sha256": + return d.isValidHash(64, "sha256") + case "sha1": + return d.isValidHash(40, "sha1") + default: + return fmt.Errorf("Unsupported hash algorithm %s for DmVerity device %s", d.HashType, d.Hash) + } +} + +func isValidBlockSize(blockSize uint64) bool { + return minBlockSize <= blockSize && blockSize <= maxBlockSize +} + +func (d *DmVerityInfo) isValidHash(expectedLen int, hashType string) error { + _, err := hex.DecodeString(d.Hash) + if len(d.Hash) != expectedLen || err != nil { + return fmt.Errorf("Invalid hash value %s:%s for DmVerity device with %s", hashType, d.Hash, hashType) + } + return nil +} + +func ParseDmVerityInfo(option string) (*DmVerityInfo, error) { + no := &DmVerityInfo{} + if err := json.Unmarshal([]byte(option), no); err != nil { + return nil, errors.Wrapf(err, "DmVerityInfo json unmarshal err") + } + if err := no.IsValid(); err != nil { + return nil, fmt.Errorf("DmVerityInfo is not correct, %+v; error = %+v", no, err) + } + return no, nil +} + +func ParseKataVirtualVolume(option string) (*KataVirtualVolume, error) { + opt, err := base64.StdEncoding.DecodeString(option) + if err != nil { + return nil, errors.Wrap(err, "KataVirtualVolume base64 decoding err") + } + no := &KataVirtualVolume{} + if err := json.Unmarshal(opt, no); err != nil { + return nil, errors.Wrapf(err, "KataVirtualVolume json unmarshal err") + } + if !no.IsValid() { + return nil, fmt.Errorf("KataVirtualVolume is not correct, %+v", no) + } + + return no, nil +} diff --git a/src/runtime/virtcontainers/types/virtual_volume_test.go b/src/runtime/virtcontainers/types/virtual_volume_test.go new file mode 100644 index 0000000000..6acecf3f34 --- /dev/null +++ b/src/runtime/virtcontainers/types/virtual_volume_test.go @@ -0,0 +1,246 @@ +package types + +import ( + "encoding/base64" + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDmVerityInfoValidation(t *testing.T) { + TestData := []DmVerityInfo{ + { + HashType: "md5", // "md5" is not a supported hash algorithm + Blocksize: 512, + Hashsize: 512, + BlockNum: 16384, + Offset: 8388608, + Hash: "9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174", + }, + { + HashType: "sha256", + Blocksize: 3000, // Invalid block size, not a power of 2. + Hashsize: 512, + BlockNum: 16384, + Offset: 8388608, + Hash: "9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174", + }, + { + HashType: "sha256", + Blocksize: 0, // Invalid block size, less than 512. + Hashsize: 512, + BlockNum: 16384, + Offset: 8388608, + Hash: "9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174", + }, + { + HashType: "sha256", + Blocksize: 524800, // Invalid block size, greater than 524288. + Hashsize: 512, + BlockNum: 16384, + Offset: 8388608, + Hash: "9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174", + }, + { + HashType: "sha256", + Blocksize: 512, + Hashsize: 3000, // Invalid hash block size, not a power of 2. + BlockNum: 16384, + Offset: 8388608, + Hash: "9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174", + }, + { + HashType: "sha256", + Blocksize: 512, + Hashsize: 0, // Invalid hash block size, less than 512. + BlockNum: 16384, + Offset: 8388608, + Hash: "9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174", + }, + { + HashType: "sha256", + Blocksize: 512, + Hashsize: 524800, // Invalid hash block size, greater than 524288. + BlockNum: 16384, + Offset: 8388608, + Hash: "9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174", + }, + { + HashType: "sha256", + Blocksize: 512, + Hashsize: 512, + BlockNum: 0, // Invalid BlockNum, it must be greater than 0. + Offset: 8388608, + Hash: "9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174", + }, + { + HashType: "sha256", + Blocksize: 512, + Hashsize: 512, + BlockNum: 16384, + Offset: 0, // Invalid offset, it must be greater than 0. + Hash: "9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174", + }, + { + HashType: "sha256", + Blocksize: 512, + Hashsize: 512, + BlockNum: 16384, + Offset: 8193, // Invalid offset, it must be aligned to 512. + Hash: "9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174", + }, + { + HashType: "sha256", + Blocksize: 512, + Hashsize: 512, + BlockNum: 16384, + Offset: 8388608 - 4096, // Invalid offset, it must be equal to blocksize * BlockNum. + Hash: "9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174", + }, + } + + for _, d := range TestData { + assert.Error(t, d.IsValid()) + } + TestCorrectData := DmVerityInfo{ + HashType: "sha256", + Blocksize: 512, + Hashsize: 512, + BlockNum: 16384, + Offset: 8388608, + Hash: "9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174", + } + assert.NoError(t, TestCorrectData.IsValid()) +} + +func TestDirectAssignedVolumeValidation(t *testing.T) { + validDirectVolume := DirectAssignedVolume{ + Metadata: map[string]string{"key": "value"}, + } + assert.True(t, validDirectVolume.IsValid()) + + invalidDirectVolume := DirectAssignedVolume{ + Metadata: nil, + } + assert.False(t, invalidDirectVolume.IsValid()) +} + +func TestImagePullVolumeValidation(t *testing.T) { + validImagePull := ImagePullVolume{ + Metadata: map[string]string{"key": "value"}, + } + assert.True(t, validImagePull.IsValid()) + + invalidImagePull := ImagePullVolume{ + Metadata: nil, + } + assert.False(t, invalidImagePull.IsValid()) +} + +func TestNydusImageVolumeValidation(t *testing.T) { + validNydusImage := NydusImageVolume{ + Config: "config_value", + SnapshotDir: "", + } + assert.True(t, validNydusImage.IsValid()) + + invalidNydusImage := NydusImageVolume{ + Config: "", + SnapshotDir: "", + } + assert.False(t, invalidNydusImage.IsValid()) +} + +func TestKataVirtualVolumeValidation(t *testing.T) { + validKataVirtualVolume := KataVirtualVolume{ + VolumeType: "direct_block", + Source: "/dev/sdb", + FSType: "ext4", + Options: []string{"rw"}, + DirectVolume: &DirectAssignedVolume{ + Metadata: map[string]string{"key": "value"}, + }, + // Initialize other fields + } + assert.True(t, validKataVirtualVolume.IsValid()) + + invalidKataVirtualVolume := KataVirtualVolume{ + VolumeType: "direct_block", + Source: "/dev/sdb", + FSType: "", + Options: nil, + DirectVolume: &DirectAssignedVolume{ + Metadata: nil, + }, + // Initialize other fields + } + assert.False(t, invalidKataVirtualVolume.IsValid()) +} + +func TestParseDmVerityInfo(t *testing.T) { + // Create a mock valid KataVirtualVolume + validDmVerityInfo := DmVerityInfo{ + HashType: "sha256", + Blocksize: 512, + Hashsize: 512, + BlockNum: 16384, + Offset: 8388608, + Hash: "9de18652fe74edfb9b805aaed72ae2aa48f94333f1ba5c452ac33b1c39325174", + } + validKataVirtualVolumeJSON, _ := json.Marshal(validDmVerityInfo) + + t.Run("Valid Option", func(t *testing.T) { + volume, err := ParseDmVerityInfo(string(validKataVirtualVolumeJSON)) + assert.NoError(t, err) + assert.NotNil(t, volume) + assert.NoError(t, volume.IsValid()) + }) + + t.Run("Invalid JSON Option", func(t *testing.T) { + volume, err := ParseDmVerityInfo("invalid_json") + assert.Error(t, err) + assert.Nil(t, volume) + }) + +} + +func TestParseKataVirtualVolume(t *testing.T) { + // Create a mock valid KataVirtualVolume + validKataVirtualVolume := KataVirtualVolume{ + VolumeType: "direct_block", + Source: "/dev/sdb", + FSType: "ext4", + Options: []string{"rw"}, + DirectVolume: &DirectAssignedVolume{ + Metadata: map[string]string{"key": "value"}, + }, + // Initialize other fields + } + validKataVirtualVolumeJSON, _ := json.Marshal(validKataVirtualVolume) + validOption := base64.StdEncoding.EncodeToString(validKataVirtualVolumeJSON) + + t.Run("Valid Option", func(t *testing.T) { + volume, err := ParseKataVirtualVolume(validOption) + + assert.NoError(t, err) + assert.NotNil(t, volume) + assert.True(t, volume.IsValid()) + }) + + t.Run("Invalid JSON Option", func(t *testing.T) { + invalidJSONOption := base64.StdEncoding.EncodeToString([]byte("invalid_json")) + volume, err := ParseKataVirtualVolume(invalidJSONOption) + + assert.Error(t, err) + assert.Nil(t, volume) + }) + + invalidBase64Option := "invalid_base64" + t.Run("Invalid Base64 Option", func(t *testing.T) { + volume, err := ParseKataVirtualVolume(invalidBase64Option) + + assert.Error(t, err) + assert.Nil(t, volume) + }) +}