diff --git a/docs/cloud-init-data-generation.md b/docs/cloud-init-data-generation.md index 3343c32b4..d16eb75bc 100644 --- a/docs/cloud-init-data-generation.md +++ b/docs/cloud-init-data-generation.md @@ -1,9 +1,10 @@ # Cloud-init data generation The [cloud-init](http://cloudinit.readthedocs.io/en/latest/index.html) -data generated by Virtlet consists of `meta-data` and `user-data` +data generated by Virtlet consists of `meta-data`, `user-data` and `network-config` parts. It's currently provided by means of -[no-cloud](http://cloudinit.readthedocs.io/en/latest/topics/datasources/nocloud.html) +[NoCloud](http://cloudinit.readthedocs.io/en/latest/topics/datasources/nocloud.html) +and [Config Drive](http://cloudinit.readthedocs.io/en/latest/topics/datasources/configdrive.html) datasource, which in this case involves making an iso9660 image to be mounted by the VM. An implementation of either [Amazon EC2](http://cloudinit.readthedocs.io/en/latest/topics/datasources/ec2.html) @@ -11,6 +12,13 @@ or [OpenStack](http://cloudinit.readthedocs.io/en/latest/topics/datasources/openstack.html) datasource can be added later. +## Output ISO image format + +Virtlet supports two types of Cloud-init ISO9660-based datasources, NoCloud and +ConfigDrive. The user may choose an appropriate one using +`VirtletCloudInitImageType` annotation, which can have either `nocloud` or +`configdrive` as its value. When there's no `VirtletCloudInitImageType` +annotation, Virtlet defaults to `nocloud`. ## Basic idea with an example @@ -149,6 +157,9 @@ The `meta-data` part is a piece of JSON that looks like this: } ``` +In case of ConfigDrive, this JSON has `instance-id` repeated as `uuid` and +`local-hostname` repeated as `hostname`. + We're using JSON format here so as to be compatible with older cloud-init implementations such as one used in [CirrOS](https://launchpad.net/cirros) images which are used to test @@ -194,10 +205,8 @@ users: ``` [Network configuration](http://cloudinit.readthedocs.io/en/latest/topics/network-config.html) -options may be added here later, too. There's an idea to circumvent -the limitations of DHCP server that make it hard to use more complex -CNI implementations like Calico with the VMs by applying the network -configuration via the cloud-init mechanism. +uses YAML to provide data in +[Network Config Version 1](http://cloudinit.readthedocs.io/en/latest/topics/network-config-format-v1.html). The `user-data` content is generated as follows: * `mounts` are generated based on `volumeMount` options of the diff --git a/pkg/libvirttools/TestContainerLifecycle.json b/pkg/libvirttools/TestContainerLifecycle.json index a00ed4fd6..48e9a650a 100644 --- a/pkg/libvirttools/TestContainerLifecycle.json +++ b/pkg/libvirttools/TestContainerLifecycle.json @@ -291,7 +291,7 @@ }, "Auth": null, "Source": { - "File": "/var/lib/virtlet/nocloud/nocloud-231700d5-c9a6-5a49-738d-99a954c51550.iso", + "File": "/var/lib/virtlet/config/config-231700d5-c9a6-5a49-738d-99a954c51550.iso", "Device": "", "Protocol": "", "Name": "", diff --git a/pkg/libvirttools/TestDomainDefinitions__ceph_flexvolume.json b/pkg/libvirttools/TestDomainDefinitions__ceph_flexvolume.json index eee390976..e885a61fa 100644 --- a/pkg/libvirttools/TestDomainDefinitions__ceph_flexvolume.json +++ b/pkg/libvirttools/TestDomainDefinitions__ceph_flexvolume.json @@ -373,7 +373,7 @@ }, "Auth": null, "Source": { - "File": "/var/lib/virtlet/nocloud/nocloud-231700d5-c9a6-5a49-738d-99a954c51550.iso", + "File": "/var/lib/virtlet/config/config-231700d5-c9a6-5a49-738d-99a954c51550.iso", "Device": "", "Protocol": "", "Name": "", diff --git a/pkg/libvirttools/TestDomainDefinitions__cloud-init.json b/pkg/libvirttools/TestDomainDefinitions__cloud-init.json index 7026317c8..b4894b4d9 100644 --- a/pkg/libvirttools/TestDomainDefinitions__cloud-init.json +++ b/pkg/libvirttools/TestDomainDefinitions__cloud-init.json @@ -287,7 +287,7 @@ }, "Auth": null, "Source": { - "File": "/var/lib/virtlet/nocloud/nocloud-231700d5-c9a6-5a49-738d-99a954c51550.iso", + "File": "/var/lib/virtlet/config/config-231700d5-c9a6-5a49-738d-99a954c51550.iso", "Device": "", "Protocol": "", "Name": "", diff --git a/pkg/libvirttools/TestDomainDefinitions__cloud-init_with_user_data.json b/pkg/libvirttools/TestDomainDefinitions__cloud-init_with_user_data.json index 849954b54..f6d373de0 100644 --- a/pkg/libvirttools/TestDomainDefinitions__cloud-init_with_user_data.json +++ b/pkg/libvirttools/TestDomainDefinitions__cloud-init_with_user_data.json @@ -287,7 +287,7 @@ }, "Auth": null, "Source": { - "File": "/var/lib/virtlet/nocloud/nocloud-231700d5-c9a6-5a49-738d-99a954c51550.iso", + "File": "/var/lib/virtlet/config/config-231700d5-c9a6-5a49-738d-99a954c51550.iso", "Device": "", "Protocol": "", "Name": "", diff --git a/pkg/libvirttools/TestDomainDefinitions__plain_domain.json b/pkg/libvirttools/TestDomainDefinitions__plain_domain.json index ae026f21c..8f4b01814 100644 --- a/pkg/libvirttools/TestDomainDefinitions__plain_domain.json +++ b/pkg/libvirttools/TestDomainDefinitions__plain_domain.json @@ -287,7 +287,7 @@ }, "Auth": null, "Source": { - "File": "/var/lib/virtlet/nocloud/nocloud-231700d5-c9a6-5a49-738d-99a954c51550.iso", + "File": "/var/lib/virtlet/config/config-231700d5-c9a6-5a49-738d-99a954c51550.iso", "Device": "", "Protocol": "", "Name": "", diff --git a/pkg/libvirttools/TestDomainDefinitions__raw_devices.json b/pkg/libvirttools/TestDomainDefinitions__raw_devices.json index 7806598bc..238e9cb08 100644 --- a/pkg/libvirttools/TestDomainDefinitions__raw_devices.json +++ b/pkg/libvirttools/TestDomainDefinitions__raw_devices.json @@ -336,7 +336,7 @@ }, "Auth": null, "Source": { - "File": "/var/lib/virtlet/nocloud/nocloud-231700d5-c9a6-5a49-738d-99a954c51550.iso", + "File": "/var/lib/virtlet/config/config-231700d5-c9a6-5a49-738d-99a954c51550.iso", "Device": "", "Protocol": "", "Name": "", diff --git a/pkg/libvirttools/TestDomainDefinitions__vcpu_count.json b/pkg/libvirttools/TestDomainDefinitions__vcpu_count.json index fcb11d5a4..91b3570d8 100644 --- a/pkg/libvirttools/TestDomainDefinitions__vcpu_count.json +++ b/pkg/libvirttools/TestDomainDefinitions__vcpu_count.json @@ -287,7 +287,7 @@ }, "Auth": null, "Source": { - "File": "/var/lib/virtlet/nocloud/nocloud-231700d5-c9a6-5a49-738d-99a954c51550.iso", + "File": "/var/lib/virtlet/config/config-231700d5-c9a6-5a49-738d-99a954c51550.iso", "Device": "", "Protocol": "", "Name": "", diff --git a/pkg/libvirttools/TestDomainDefinitions__virtio_disk_driver.json b/pkg/libvirttools/TestDomainDefinitions__virtio_disk_driver.json index d09e1e37a..a5b5050ad 100644 --- a/pkg/libvirttools/TestDomainDefinitions__virtio_disk_driver.json +++ b/pkg/libvirttools/TestDomainDefinitions__virtio_disk_driver.json @@ -287,7 +287,7 @@ }, "Auth": null, "Source": { - "File": "/var/lib/virtlet/nocloud/nocloud-231700d5-c9a6-5a49-738d-99a954c51550.iso", + "File": "/var/lib/virtlet/config/config-231700d5-c9a6-5a49-738d-99a954c51550.iso", "Device": "", "Protocol": "", "Name": "", diff --git a/pkg/libvirttools/TestDomainDefinitions__volumes.json b/pkg/libvirttools/TestDomainDefinitions__volumes.json index e143e59a3..9cc783eae 100644 --- a/pkg/libvirttools/TestDomainDefinitions__volumes.json +++ b/pkg/libvirttools/TestDomainDefinitions__volumes.json @@ -545,7 +545,7 @@ }, "Auth": null, "Source": { - "File": "/var/lib/virtlet/nocloud/nocloud-231700d5-c9a6-5a49-738d-99a954c51550.iso", + "File": "/var/lib/virtlet/config/config-231700d5-c9a6-5a49-738d-99a954c51550.iso", "Device": "", "Protocol": "", "Name": "", diff --git a/pkg/libvirttools/TestDomainForcedShutdown.json b/pkg/libvirttools/TestDomainForcedShutdown.json index 66ac6427c..3669c9048 100644 --- a/pkg/libvirttools/TestDomainForcedShutdown.json +++ b/pkg/libvirttools/TestDomainForcedShutdown.json @@ -287,7 +287,7 @@ }, "Auth": null, "Source": { - "File": "/var/lib/virtlet/nocloud/nocloud-231700d5-c9a6-5a49-738d-99a954c51550.iso", + "File": "/var/lib/virtlet/config/config-231700d5-c9a6-5a49-738d-99a954c51550.iso", "Device": "", "Protocol": "", "Name": "", diff --git a/pkg/libvirttools/annotations.go b/pkg/libvirttools/annotations.go index 4a94c0f9f..b8ee5955b 100644 --- a/pkg/libvirttools/annotations.go +++ b/pkg/libvirttools/annotations.go @@ -31,6 +31,7 @@ import ( ) type DiskDriver string +type ImageType string const ( maxVCPUCount = 255 @@ -40,15 +41,19 @@ const ( CloudInitUserDataSourceKeyName = "VirtletCloudInitUserDataSource" CloudInitUserDataOverwriteKeyName = "VirtletCloudInitUserDataOverwrite" CloudInitUserDataScriptKeyName = "VirtletCloudInitUserDataScript" + CloudInitImageType = "VirtletCloudInitImageType" SSHKeysKeyName = "VirtletSSHKeys" SSHKeySourceKeyName = "VirtletSSHKeySource" DiskDriverKeyName = "VirtletDiskDriver" DiskDriverVirtio DiskDriver = "virtio" DiskDriverScsi DiskDriver = "scsi" + ImageTypeNoCloud ImageType = "nocloud" + ImageTypeConfigDrive ImageType = "configdrive" ) type VirtletAnnotations struct { VCPUCount int + ImageType ImageType MetaData map[string]interface{} UserData map[string]interface{} UserDataOverwrite bool @@ -119,7 +124,9 @@ func (va *VirtletAnnotations) parsePodAnnotations(ns string, podAnnotations map[ } } + va.ImageType = ImageType(strings.ToLower(podAnnotations[CloudInitImageType])) va.DiskDriver = DiskDriver(podAnnotations[DiskDriverKeyName]) + return nil } @@ -127,9 +134,14 @@ func (va *VirtletAnnotations) applyDefaults() { if va.VCPUCount <= 0 { va.VCPUCount = 1 } + if va.DiskDriver == "" { va.DiskDriver = DiskDriverScsi } + + if va.ImageType == "" { + va.ImageType = ImageTypeNoCloud + } } func (va *VirtletAnnotations) validate() error { @@ -139,7 +151,11 @@ func (va *VirtletAnnotations) validate() error { } if va.DiskDriver != DiskDriverVirtio && va.DiskDriver != DiskDriverScsi { - errs = append(errs, fmt.Sprintf("bad disk driver %q. Must be either %q or %q", DiskDriverVirtio, DiskDriverScsi)) + errs = append(errs, fmt.Sprintf("bad disk driver %q. Must be either %q or %q", va.DiskDriver, DiskDriverVirtio, DiskDriverScsi)) + } + + if va.ImageType != ImageTypeNoCloud && va.ImageType != ImageTypeConfigDrive { + errs = append(errs, fmt.Sprintf("unknown config image type %q. Must be either %q or %q", va.ImageType, ImageTypeNoCloud, ImageTypeConfigDrive)) } if errs != nil { diff --git a/pkg/libvirttools/annotations_test.go b/pkg/libvirttools/annotations_test.go index 3a01f4f2c..4ee3de6c4 100644 --- a/pkg/libvirttools/annotations_test.go +++ b/pkg/libvirttools/annotations_test.go @@ -34,6 +34,7 @@ func TestVirtletAnnotations(t *testing.T) { va: &VirtletAnnotations{ VCPUCount: 1, DiskDriver: "scsi", + ImageType: "nocloud", }, }, { @@ -42,6 +43,16 @@ func TestVirtletAnnotations(t *testing.T) { va: &VirtletAnnotations{ VCPUCount: 1, DiskDriver: "scsi", + ImageType: "nocloud", + }, + }, + { + name: "non empty cloud init type annotation", + annotations: map[string]string{"VirtletCloudInitImageType": "configdrive"}, + va: &VirtletAnnotations{ + VCPUCount: 1, + DiskDriver: "scsi", + ImageType: "configdrive", }, }, { @@ -50,6 +61,7 @@ func TestVirtletAnnotations(t *testing.T) { va: &VirtletAnnotations{ VCPUCount: 1, DiskDriver: "scsi", + ImageType: "nocloud", }, }, { @@ -58,6 +70,7 @@ func TestVirtletAnnotations(t *testing.T) { va: &VirtletAnnotations{ VCPUCount: 1, DiskDriver: "scsi", + ImageType: "nocloud", }, }, { @@ -66,6 +79,7 @@ func TestVirtletAnnotations(t *testing.T) { va: &VirtletAnnotations{ VCPUCount: 4, DiskDriver: "scsi", + ImageType: "nocloud", }, }, { @@ -93,6 +107,7 @@ func TestVirtletAnnotations(t *testing.T) { }, SSHKeys: []string{"key1", "key2"}, DiskDriver: "scsi", + ImageType: "nocloud", }, }, { @@ -104,6 +119,7 @@ func TestVirtletAnnotations(t *testing.T) { VCPUCount: 1, UserDataOverwrite: true, DiskDriver: "scsi", + ImageType: "nocloud", }, }, { @@ -115,6 +131,7 @@ func TestVirtletAnnotations(t *testing.T) { VCPUCount: 1, UserDataScript: "#!/bin/sh\necho hi\n", DiskDriver: "scsi", + ImageType: "nocloud", }, }, // bad metadata items follow @@ -132,6 +149,12 @@ func TestVirtletAnnotations(t *testing.T) { "VirtletCloudInitMetaData": "{", }, }, + { + name: "bad cloud-init image type", + annotations: map[string]string{ + "VirtletCloudInitImageType": "ducttape", + }, + }, { name: "bad cloud-init user-data", annotations: map[string]string{ diff --git a/pkg/libvirttools/cloudinit.go b/pkg/libvirttools/cloudinit.go index aaef81a02..2639258dd 100644 --- a/pkg/libvirttools/cloudinit.go +++ b/pkg/libvirttools/cloudinit.go @@ -29,6 +29,7 @@ import ( "strconv" "strings" + cnitypes "github.com/containernetworking/cni/pkg/types" cnicurrent "github.com/containernetworking/cni/pkg/types/current" "github.com/ghodss/yaml" "github.com/golang/glog" @@ -62,6 +63,12 @@ func (g *CloudInitGenerator) generateMetaData() ([]byte, error) { "instance-id": fmt.Sprintf("%s.%s", g.config.PodName, g.config.PodNamespace), "local-hostname": g.config.PodName, } + + if g.config.ParsedAnnotations.ImageType == ImageTypeConfigDrive { + m["uuid"] = m["instance-id"] + m["hostname"] = m["local-hostname"] + } + if len(g.config.ParsedAnnotations.SSHKeys) != 0 { var keys []string for _, key := range g.config.ParsedAnnotations.SSHKeys { @@ -69,13 +76,16 @@ func (g *CloudInitGenerator) generateMetaData() ([]byte, error) { } m["public-keys"] = keys } + for k, v := range g.config.ParsedAnnotations.MetaData { m[k] = v } + r, err := json.Marshal(m) if err != nil { return nil, fmt.Errorf("error marshaling meta-data: %v", err) } + return r, nil } @@ -120,6 +130,17 @@ func (g *CloudInitGenerator) generateUserData(volumeMap diskPathMap) ([]byte, er } func (g *CloudInitGenerator) generateNetworkConfiguration() ([]byte, error) { + switch g.config.ParsedAnnotations.ImageType { + case ImageTypeNoCloud: + return g.generateNetworkConfigurationNoCloud() + case ImageTypeConfigDrive: + return g.generateNetworkConfigurationConfigDrive() + } + + return nil, fmt.Errorf("unknown cloud-init config image type: %q", g.config.ParsedAnnotations.ImageType) +} + +func (g *CloudInitGenerator) generateNetworkConfigurationNoCloud() ([]byte, error) { cniResult, err := cni.BytesToResult([]byte(g.config.CNIConfig)) if err != nil { return nil, err @@ -181,6 +202,12 @@ func (g *CloudInitGenerator) generateNetworkConfiguration() ([]byte, error) { config = append(config, route) } + // dns + dnsData := getDnsData(cniResult.DNS) + if dnsData != nil { + config = append(config, dnsData...) + } + r, err := yaml.Marshal(map[string]interface{}{ "config": config, }) @@ -208,15 +235,6 @@ func (g *CloudInitGenerator) getSubnetsAndGatewaysForNthInterface(interfaceNo in // The routes must be specified in Routes field // of the CNI result. } - // Cloud Init requires dns settings on the subnet level (yeah, I know...) - // while we get just one setting for all the IP configurations from CNI, - // so as a workaround we're adding it to all subnets configurations - if cniResult.DNS.Nameservers != nil { - subnet["dns_nameservers"] = cniResult.DNS.Nameservers - } - if cniResult.DNS.Search != nil { - subnet["dns_search"] = cniResult.DNS.Search - } subnets = append(subnets, subnet) } } @@ -231,8 +249,104 @@ func (g *CloudInitGenerator) getSubnetsAndGatewaysForNthInterface(interfaceNo in return subnets, gateways } +func getDnsData(cniDns cnitypes.DNS) []map[string]interface{} { + var dnsData []map[string]interface{} + if cniDns.Nameservers != nil { + dnsData = append(dnsData, map[string]interface{}{ + "type": "nameserver", + "address": cniDns.Nameservers, + }) + if cniDns.Search != nil { + dnsData[0]["search"] = cniDns.Search + } + } + return dnsData +} + +func (g *CloudInitGenerator) generateNetworkConfigurationConfigDrive() ([]byte, error) { + cniResult, err := cni.BytesToResult([]byte(g.config.CNIConfig)) + if err != nil { + return nil, err + } + if cniResult == nil { + // This can only happen during integration tests + // where a dummy sandbox is used + return []byte("{}"), nil + } + + config := make(map[string]interface{}) + + // links + var links []map[string]interface{} + for _, iface := range cniResult.Interfaces { + if iface.Sandbox == "" { + // skip host interfaces + continue + } + linkConf := map[string]interface{}{ + "type": "phy", + "id": iface.Name, + "ethernet_mac_address": iface.Mac, + } + links = append(links, linkConf) + } + config["links"] = links + + var networks []map[string]interface{} + for i, ipConfig := range cniResult.IPs { + netConf := map[string]interface{}{ + "id": fmt.Sprintf("net-%d", i), + // config from openstack have as network_id network uuid + "network_id": fmt.Sprintf("net-%d", i), + "type": fmt.Sprintf("ipv%s", ipConfig.Version), + "link": cniResult.Interfaces[ipConfig.Interface].Name, + "ip_address": ipConfig.Address.IP.String(), + "netmask": net.IP(ipConfig.Address.Mask).String(), + } + + routes := routesForIP(ipConfig.Address, cniResult.Routes) + if routes != nil { + netConf["routes"] = routes + } + + networks = append(networks, netConf) + } + config["networks"] = networks + + dnsData := getDnsData(cniResult.DNS) + if dnsData != nil { + config["services"] = dnsData + } + + r, err := json.Marshal(config) + if err != nil { + return nil, fmt.Errorf("error marshaling network configuration: %v", err) + } + return r, nil +} + +func routesForIP(sourceIP net.IPNet, allRoutes []*cnitypes.Route) []map[string]interface{} { + var routes []map[string]interface{} + + // NOTE: at the moment on cni result level there is no distinction + // for which interface particular route should be set, + // so we are returning there all routes with gateway accessible + // by particular source ip address. + for _, route := range allRoutes { + if sourceIP.Contains(route.GW) { + routes = append(routes, map[string]interface{}{ + "network": route.Dst.IP.String(), + "netmask": net.IP(route.Dst.Mask).String(), + "gateway": route.GW.String(), + }) + } + } + + return routes +} + func (g *CloudInitGenerator) IsoPath() string { - return filepath.Join(g.isoDir, fmt.Sprintf("nocloud-%s.iso", g.config.DomainUUID)) + return filepath.Join(g.isoDir, fmt.Sprintf("config-%s.iso", g.config.DomainUUID)) } func (g *CloudInitGenerator) DiskDef() *libvirtxml.DomainDisk { @@ -246,9 +360,9 @@ func (g *CloudInitGenerator) DiskDef() *libvirtxml.DomainDisk { } func (g *CloudInitGenerator) GenerateImage(volumeMap diskPathMap) error { - tmpDir, err := ioutil.TempDir("", "nocloud-") + tmpDir, err := ioutil.TempDir("", "config-") if err != nil { - return fmt.Errorf("can't create temp dir for nocloud: %v", err) + return fmt.Errorf("can't create temp dir for config image: %v", err) } defer os.RemoveAll(tmpDir) @@ -264,10 +378,30 @@ func (g *CloudInitGenerator) GenerateImage(volumeMap diskPathMap) error { return err } + var userDataLocation, metaDataLocation, networkConfigLocation string + var volumeName string + + switch g.config.ParsedAnnotations.ImageType { + case ImageTypeNoCloud: + userDataLocation = "user-data" + metaDataLocation = "meta-data" + networkConfigLocation = "network-config" + volumeName = "cidata" + case ImageTypeConfigDrive: + userDataLocation = "openstack/latest/user_data" + metaDataLocation = "openstack/latest/meta_data.json" + networkConfigLocation = "openstack/latest/network_data.json" + volumeName = "config-2" + default: + // that should newer happen, as ImageType should be validated + // already earlier + return fmt.Errorf("unknown cloud-init config image type: %q", g.config.ParsedAnnotations.ImageType) + } + if err := utils.WriteFiles(tmpDir, map[string][]byte{ - "user-data": userData, - "meta-data": metaData, - "network-config": networkConfiguration, + userDataLocation: userData, + metaDataLocation: metaData, + networkConfigLocation: networkConfiguration, }); err != nil { return fmt.Errorf("can't write user-data: %v", err) } @@ -276,7 +410,7 @@ func (g *CloudInitGenerator) GenerateImage(volumeMap diskPathMap) error { return fmt.Errorf("error making iso directory %q: %v", g.isoDir, err) } - if err := utils.GenIsoImage(g.IsoPath(), "cidata", tmpDir); err != nil { + if err := utils.GenIsoImage(g.IsoPath(), volumeName, tmpDir); err != nil { if rmErr := os.Remove(g.IsoPath()); rmErr != nil { glog.Warningf("Error removing iso file %s: %v", g.IsoPath(), rmErr) } diff --git a/pkg/libvirttools/cloudinit_test.go b/pkg/libvirttools/cloudinit_test.go index 2654ca890..30f778cad 100644 --- a/pkg/libvirttools/cloudinit_test.go +++ b/pkg/libvirttools/cloudinit_test.go @@ -63,7 +63,7 @@ func newFakeFlexvolume(t *testing.T, parentDir string, uuid string, part int) *f } } -func buildNetworkedPodConfig(cniResult *cnicurrent.Result) *VMConfig { +func buildNetworkedPodConfig(cniResult *cnicurrent.Result, imageType string) *VMConfig { r, err := json.Marshal(cniResult) if err != nil { panic("failed to marshal CNI result") @@ -71,7 +71,7 @@ func buildNetworkedPodConfig(cniResult *cnicurrent.Result) *VMConfig { return &VMConfig{ PodName: "foo", PodNamespace: "default", - ParsedAnnotations: &VirtletAnnotations{}, + ParsedAnnotations: &VirtletAnnotations{ImageType: ImageType(imageType)}, CNIConfig: string(r), } } @@ -102,25 +102,39 @@ func TestCloudInitGenerator(t *testing.T) { config: &VMConfig{ PodName: "foo", PodNamespace: "default", - ParsedAnnotations: &VirtletAnnotations{}, + ParsedAnnotations: &VirtletAnnotations{ImageType: "nocloud"}, }, expectedMetaData: map[string]interface{}{ "instance-id": "foo.default", "local-hostname": "foo", }, - expectedUserData: nil, expectedNetworkConfig: map[string]interface{}{ // that's how yaml parses the number "version": float64(1), }, }, + { + name: "metadata for configdrive", + config: &VMConfig{ + PodName: "foo", + PodNamespace: "default", + ParsedAnnotations: &VirtletAnnotations{ImageType: "configdrive"}, + }, + expectedMetaData: map[string]interface{}{ + "instance-id": "foo.default", + "local-hostname": "foo", + "uuid": "foo.default", + "hostname": "foo", + }, + }, { name: "pod with ssh keys", config: &VMConfig{ PodName: "foo", PodNamespace: "default", ParsedAnnotations: &VirtletAnnotations{ - SSHKeys: []string{"key1", "key2"}, + SSHKeys: []string{"key1", "key2"}, + ImageType: "nocloud", }, }, expectedMetaData: map[string]interface{}{ @@ -128,7 +142,6 @@ func TestCloudInitGenerator(t *testing.T) { "local-hostname": "foo", "public-keys": []interface{}{"key1", "key2"}, }, - expectedUserData: nil, }, { name: "pod with ssh keys and meta-data override", @@ -140,6 +153,7 @@ func TestCloudInitGenerator(t *testing.T) { MetaData: map[string]interface{}{ "instance-id": "foobar", }, + ImageType: "nocloud", }, }, expectedMetaData: map[string]interface{}{ @@ -147,7 +161,6 @@ func TestCloudInitGenerator(t *testing.T) { "local-hostname": "foo", "public-keys": []interface{}{"key1", "key2"}, }, - expectedUserData: nil, }, { name: "pod with user data", @@ -162,7 +175,8 @@ func TestCloudInitGenerator(t *testing.T) { }, }, }, - SSHKeys: []string{"key1", "key2"}, + SSHKeys: []string{"key1", "key2"}, + ImageType: "nocloud", }, }, expectedMetaData: map[string]interface{}{ @@ -183,7 +197,7 @@ func TestCloudInitGenerator(t *testing.T) { config: &VMConfig{ PodName: "foo", PodNamespace: "default", - ParsedAnnotations: &VirtletAnnotations{}, + ParsedAnnotations: &VirtletAnnotations{ImageType: "nocloud"}, Environment: []*VMKeyValue{ {"foo", "bar"}, {"baz", "abc"}, @@ -222,6 +236,7 @@ func TestCloudInitGenerator(t *testing.T) { }, }, }, + ImageType: "nocloud", }, Environment: []*VMKeyValue{ {"foo", "bar"}, @@ -259,6 +274,7 @@ func TestCloudInitGenerator(t *testing.T) { ParsedAnnotations: &VirtletAnnotations{ UserDataScript: "#!/bin/sh\necho hi\n", SSHKeys: []string{"key1", "key2"}, + ImageType: "nocloud", }, }, expectedMetaData: map[string]interface{}{ @@ -273,7 +289,7 @@ func TestCloudInitGenerator(t *testing.T) { config: &VMConfig{ PodName: "foo", PodNamespace: "default", - ParsedAnnotations: &VirtletAnnotations{}, + ParsedAnnotations: &VirtletAnnotations{ImageType: "nocloud"}, Mounts: []*VMMount{ { ContainerPath: "/opt", @@ -332,6 +348,7 @@ func TestCloudInitGenerator(t *testing.T) { PodNamespace: "default", ParsedAnnotations: &VirtletAnnotations{ UserDataScript: "#!/bin/sh\necho hi\n@virtlet-mount-script@", + ImageType: "nocloud", }, Mounts: []*VMMount{ { @@ -393,7 +410,7 @@ func TestCloudInitGenerator(t *testing.T) { Nameservers: []string{"1.2.3.4"}, Search: []string{"some", "search"}, }, - }), + }, "nocloud"), expectedNetworkConfig: map[string]interface{}{ "version": float64(1), "config": []interface{}{ @@ -402,11 +419,9 @@ func TestCloudInitGenerator(t *testing.T) { "name": "cni0", "subnets": []interface{}{ map[string]interface{}{ - "address": "1.1.1.1", - "netmask": "255.0.0.0", - "dns_nameservers": []interface{}{"1.2.3.4"}, - "dns_search": []interface{}{"some", "search"}, - "type": "static", + "address": "1.1.1.1", + "netmask": "255.0.0.0", + "type": "static", }, }, "type": "physical", @@ -416,6 +431,11 @@ func TestCloudInitGenerator(t *testing.T) { "gateway": "1.2.3.4", "type": "route", }, + map[string]interface{}{ + "address": []interface{}{"1.2.3.4"}, + "search": []interface{}{"some", "search"}, + "type": "nameserver", + }, }, }, }, @@ -474,7 +494,7 @@ func TestCloudInitGenerator(t *testing.T) { Nameservers: []string{"1.2.3.4"}, Search: []string{"some", "search"}, }, - }), + }, "nocloud"), expectedNetworkConfig: map[string]interface{}{ "version": float64(1), "config": []interface{}{ @@ -483,11 +503,9 @@ func TestCloudInitGenerator(t *testing.T) { "name": "cni0", "subnets": []interface{}{ map[string]interface{}{ - "address": "1.1.1.1", - "netmask": "255.0.0.0", - "dns_nameservers": []interface{}{"1.2.3.4"}, - "dns_search": []interface{}{"some", "search"}, - "type": "static", + "address": "1.1.1.1", + "netmask": "255.0.0.0", + "type": "static", }, }, "type": "physical", @@ -497,11 +515,9 @@ func TestCloudInitGenerator(t *testing.T) { "name": "cni1", "subnets": []interface{}{ map[string]interface{}{ - "address": "192.168.100.42", - "netmask": "255.255.255.0", - "dns_nameservers": []interface{}{"1.2.3.4"}, - "dns_search": []interface{}{"some", "search"}, - "type": "static", + "address": "192.168.100.42", + "netmask": "255.255.255.0", + "type": "static", }, }, "type": "physical", @@ -511,6 +527,191 @@ func TestCloudInitGenerator(t *testing.T) { "gateway": "1.2.3.4", "type": "route", }, + map[string]interface{}{ + "address": []interface{}{"1.2.3.4"}, + "search": []interface{}{"some", "search"}, + "type": "nameserver", + }, + }, + }, + }, + { + name: "pod with network config - configdrive", + config: buildNetworkedPodConfig(&cnicurrent.Result{ + Interfaces: []*cnicurrent.Interface{ + { + Name: "cni0", + Mac: "00:11:22:33:44:55", + Sandbox: "/var/run/netns/bae464f1-6ee7-4ee2-826e-33293a9de95e", + }, + { + Name: "ignoreme0", + Mac: "00:12:34:56:78:9a", + Sandbox: "", // host interface + }, + }, + IPs: []*cnicurrent.IPConfig{ + { + Version: "4", + Address: net.IPNet{ + IP: net.IPv4(1, 1, 1, 1), + Mask: net.CIDRMask(8, 32), + }, + Gateway: net.IPv4(1, 2, 3, 4), + Interface: 0, + }, + }, + Routes: []*cnitypes.Route{ + { + Dst: net.IPNet{ + IP: net.IPv4zero, + Mask: net.CIDRMask(0, 32), + }, + GW: nil, + }, + }, + DNS: cnitypes.DNS{ + Nameservers: []string{"1.2.3.4"}, + Search: []string{"some", "search"}, + }, + }, "configdrive"), + expectedNetworkConfig: map[string]interface{}{ + "links": []interface{}{ + map[string]interface{}{ + "ethernet_mac_address": "00:11:22:33:44:55", + "id": "cni0", + "type": "phy", + }, + }, + "networks": []interface{}{ + map[string]interface{}{ + "id": "net-0", + "ip_address": "1.1.1.1", + "link": "cni0", + "netmask": "255.0.0.0", + "network_id": "net-0", + "type": "ipv4", + }, + }, + "services": []interface{}{ + map[string]interface{}{ + "address": []interface{}{ + "1.2.3.4", + }, + "search": []interface{}{ + "some", + "search", + }, + "type": "nameserver", + }, + }, + }, + }, + { + name: "pod with multiple network interfaces - configdrive", + config: buildNetworkedPodConfig(&cnicurrent.Result{ + Interfaces: []*cnicurrent.Interface{ + { + Name: "cni0", + Mac: "00:11:22:33:44:55", + Sandbox: "/var/run/netns/bae464f1-6ee7-4ee2-826e-33293a9de95e", + }, + { + Name: "cni1", + Mac: "00:11:22:33:ab:cd", + Sandbox: "/var/run/netns/d920d2e2-5849-4c70-b9a6-5e3cb4f831cb", + }, + { + Name: "ignoreme0", + Mac: "00:12:34:56:78:9a", + Sandbox: "", // host interface + }, + }, + IPs: []*cnicurrent.IPConfig{ + // Note that Gateway addresses are not used because + // there's no routes with nil gateway + { + Version: "4", + Address: net.IPNet{ + IP: net.IPv4(1, 1, 1, 1), + Mask: net.CIDRMask(8, 32), + }, + Gateway: net.IPv4(1, 2, 3, 4), + Interface: 0, + }, + { + Version: "4", + Address: net.IPNet{ + IP: net.IPv4(192, 168, 100, 42), + Mask: net.CIDRMask(24, 32), + }, + Gateway: net.IPv4(192, 168, 100, 1), + Interface: 1, + }, + }, + Routes: []*cnitypes.Route{ + { + Dst: net.IPNet{ + IP: net.IPv4zero, + Mask: net.CIDRMask(0, 32), + }, + GW: net.IPv4(1, 2, 3, 4), + }, + }, + DNS: cnitypes.DNS{ + Nameservers: []string{"1.2.3.4"}, + Search: []string{"some", "search"}, + }, + }, "configdrive"), + expectedNetworkConfig: map[string]interface{}{ + "services": []interface{}{ + map[string]interface{}{ + "address": []interface{}{ + "1.2.3.4", + }, + "search": []interface{}{ + "some", + "search", + }, + "type": "nameserver", + }, + }, + "links": []interface{}{ + map[string]interface{}{ + "id": "cni0", + "type": "phy", + "ethernet_mac_address": "00:11:22:33:44:55", + }, + map[string]interface{}{ + "type": "phy", + "ethernet_mac_address": "00:11:22:33:ab:cd", + "id": "cni1", + }, + }, + "networks": []interface{}{ + map[string]interface{}{ + "netmask": "255.0.0.0", + "network_id": "net-0", + "routes": []interface{}{ + map[string]interface{}{ + "netmask": "0.0.0.0", + "network": "0.0.0.0", + "gateway": "1.2.3.4", + }, + }, + "type": "ipv4", + "id": "net-0", + "ip_address": "1.1.1.1", + "link": "cni0", + }, + map[string]interface{}{ + "type": "ipv4", + "id": "net-1", + "ip_address": "192.168.100.42", + "link": "cni1", + "netmask": "255.255.255.0", + "network_id": "net-1", + }, }, }, }, @@ -578,7 +779,7 @@ func TestCloudInitDiskDef(t *testing.T) { g := NewCloudInitGenerator(&VMConfig{ PodName: "foo", PodNamespace: "default", - ParsedAnnotations: &VirtletAnnotations{}, + ParsedAnnotations: &VirtletAnnotations{ImageType: "nocloud"}, }, "") diskDef := g.DiskDef() if !reflect.DeepEqual(diskDef, &libvirtxml.DomainDisk{ @@ -593,7 +794,7 @@ func TestCloudInitDiskDef(t *testing.T) { } func TestCloudInitGenerateImage(t *testing.T) { - tmpDir, err := ioutil.TempDir("", "nocloud-") + tmpDir, err := ioutil.TempDir("", "config-") if err != nil { t.Fatalf("Can't create temp dir: %v", err) } @@ -602,7 +803,7 @@ func TestCloudInitGenerateImage(t *testing.T) { g := NewCloudInitGenerator(&VMConfig{ PodName: "foo", PodNamespace: "default", - ParsedAnnotations: &VirtletAnnotations{}, + ParsedAnnotations: &VirtletAnnotations{ImageType: "nocloud"}, }, tmpDir) if err := g.GenerateImage(nil); err != nil { diff --git a/pkg/libvirttools/nocloud_volumesource.go b/pkg/libvirttools/config_volumesource.go similarity index 58% rename from pkg/libvirttools/nocloud_volumesource.go rename to pkg/libvirttools/config_volumesource.go index da4f2ef4a..d556f884a 100644 --- a/pkg/libvirttools/nocloud_volumesource.go +++ b/pkg/libvirttools/config_volumesource.go @@ -23,50 +23,50 @@ import ( libvirtxml "github.com/libvirt/libvirt-go-xml" ) -var nocloudIsoDir = "/var/lib/virtlet/nocloud" +var configIsoDir = "/var/lib/virtlet/config" -// nocloudVolume denotes an ISO image using nocloud format +// configVolume denotes an ISO image using config format // that contains cloud-init meta-data nad user-data -type nocloudVolume struct { +type configVolume struct { volumeBase } -var _ VMVolume = &nocloudVolume{} +var _ VMVolume = &configVolume{} -func GetNocloudVolume(config *VMConfig, owner VolumeOwner) ([]VMVolume, error) { +func GetConfigVolume(config *VMConfig, owner VolumeOwner) ([]VMVolume, error) { return []VMVolume{ - &nocloudVolume{ + &configVolume{ volumeBase{config, owner}, }, }, nil } -func (v *nocloudVolume) Uuid() string { return "" } +func (v *configVolume) Uuid() string { return "" } -func (v *nocloudVolume) cloudInitGenerator() *CloudInitGenerator { - return NewCloudInitGenerator(v.config, nocloudIsoDir) +func (v *configVolume) cloudInitGenerator() *CloudInitGenerator { + return NewCloudInitGenerator(v.config, configIsoDir) } -func (v *nocloudVolume) Setup() (*libvirtxml.DomainDisk, error) { +func (v *configVolume) Setup() (*libvirtxml.DomainDisk, error) { return v.cloudInitGenerator().DiskDef(), nil } -func (v *nocloudVolume) WriteImage(volumeMap diskPathMap) error { +func (v *configVolume) WriteImage(volumeMap diskPathMap) error { return v.cloudInitGenerator().GenerateImage(volumeMap) } -func (v *nocloudVolume) Teardown() error { +func (v *configVolume) Teardown() error { isoPath := v.cloudInitGenerator().IsoPath() if err := os.Remove(isoPath); err != nil && !os.IsNotExist(err) { - glog.Warningf("Cannot remove temporary nocloud file %q: %v", isoPath, err) + glog.Warningf("Cannot remove temporary config file %q: %v", isoPath, err) } return nil } -// SetNocloudIsoDir sets a directory for nocloud iso dir. +// SetConfigIsoDir sets a directory for config iso dir. // It can be useful in tests -func SetNocloudIsoDir(dir string) { - nocloudIsoDir = dir +func SetConfigIsoDir(dir string) { + configIsoDir = dir } // TODO: this file needs a test diff --git a/pkg/libvirttools/gc.go b/pkg/libvirttools/gc.go index d70b18be3..40c7627b1 100644 --- a/pkg/libvirttools/gc.go +++ b/pkg/libvirttools/gc.go @@ -24,7 +24,7 @@ import ( ) const ( - nocloudFilenameTemplate = "nocloud-*.iso" + configFilenameTemplate = "config-*.iso" ) // GarbageCollect retrieves from metadata store list of container ids, @@ -42,7 +42,7 @@ func (v *VirtualizationTool) GarbageCollect() (allErrors []error) { allErrors = append(allErrors, v.removeOrphanDomains(ids)...) allErrors = append(allErrors, v.removeOrphanRootVolumes(ids)...) allErrors = append(allErrors, v.removeOrphanQcow2Volumes(ids)...) - allErrors = append(allErrors, v.removeOrphanNoCloudImages(ids, nocloudIsoDir)...) + allErrors = append(allErrors, v.removeOrphanConfigImages(ids, configIsoDir)...) return } @@ -218,14 +218,14 @@ func (v *VirtualizationTool) removeOrphanQcow2Volumes(ids []string) []error { return allErrors } -func (v *VirtualizationTool) removeOrphanNoCloudImages(ids []string, directory string) []error { - files, err := filepath.Glob(filepath.Join(directory, nocloudFilenameTemplate)) +func (v *VirtualizationTool) removeOrphanConfigImages(ids []string, directory string) []error { + files, err := filepath.Glob(filepath.Join(directory, configFilenameTemplate)) if err != nil { return []error{ fmt.Errorf( "error while globbing '%s' files in '%s' directory: %v", - nocloudFilenameTemplate, - nocloudIsoDir, + configFilenameTemplate, + configIsoDir, err, ), } @@ -236,10 +236,10 @@ func (v *VirtualizationTool) removeOrphanNoCloudImages(ids []string, directory s filename := filepath.Base(path) filter := func(id string) bool { - return filename == "nocloud-"+id+".iso" + return filename == "config-"+id+".iso" } - if strings.HasPrefix(filename, "nocloud-") && strings.HasSuffix(filename, ".iso") && !inList(ids, filter) { + if strings.HasPrefix(filename, "config-") && strings.HasSuffix(filename, ".iso") && !inList(ids, filter) { if err := os.Remove(path); err != nil { allErrors = append( allErrors, diff --git a/pkg/libvirttools/gc_test.go b/pkg/libvirttools/gc_test.go index a0d4b22ce..7685aab28 100644 --- a/pkg/libvirttools/gc_test.go +++ b/pkg/libvirttools/gc_test.go @@ -157,7 +157,7 @@ func TestQcow2VolumesCleanup(t *testing.T) { gm.Verify(t, ct.rec.Content()) } -func TestNocloudISOsCleanup(t *testing.T) { +func TestConfigISOsCleanup(t *testing.T) { ct := newContainerTester(t, fake.NewToplevelRecorder()) defer ct.teardown() @@ -168,7 +168,7 @@ func TestNocloudISOsCleanup(t *testing.T) { defer os.RemoveAll(directory) for _, uuid := range randomUUIDs { - fname := filepath.Join(directory, "nocloud-"+uuid+".iso") + fname := filepath.Join(directory, "config-"+uuid+".iso") if file, err := os.Create(fname); err != nil { t.Fatalf("Cannot create fake iso with name %q: %v", fname, err) } else { @@ -190,11 +190,11 @@ func TestNocloudISOsCleanup(t *testing.T) { t.Fatalf("Expected 4 files in temporary directory, found: %d", len(preCallFileNames)) } - // this should remove only nocloud iso file corresponding to the first + // this should remove only config iso file corresponding to the first // element of randomUUIDs slice, keeping other files - errors := ct.virtTool.removeOrphanNoCloudImages(randomUUIDs[1:], directory) + errors := ct.virtTool.removeOrphanConfigImages(randomUUIDs[1:], directory) if errors != nil { - t.Errorf("removeOrphanNoCloudImages returned errors: %v", errors) + t.Errorf("removeOrphanConfigImages returned errors: %v", errors) } postCallFileNames, err := filepath.Glob(filepath.Join(directory, "*")) @@ -204,12 +204,12 @@ func TestNocloudISOsCleanup(t *testing.T) { diff := difference(preCallFileNames, postCallFileNames) if len(diff) != 1 { - t.Fatalf("Expected removeOrphanNoCloudImages to remove single file, but it removed %d files", len(diff)) + t.Fatalf("Expected removeOrphanConfigImages to remove single file, but it removed %d files", len(diff)) } - expectedPath := filepath.Join(directory, "nocloud-"+randomUUIDs[0]+".iso") + expectedPath := filepath.Join(directory, "config-"+randomUUIDs[0]+".iso") if diff[0] != expectedPath { - t.Fatalf("Expected removeOrphanNoCloudImages to remove only %q file, but it also removed: %q", expectedPath, diff[0]) + t.Fatalf("Expected removeOrphanConfigImages to remove only %q file, but it also removed: %q", expectedPath, diff[0]) } // no gm validation, because we are testing only file operations in this test diff --git a/pkg/libvirttools/virtualization_test.go b/pkg/libvirttools/virtualization_test.go index ee2fb9627..ef80c9d31 100644 --- a/pkg/libvirttools/virtualization_test.go +++ b/pkg/libvirttools/virtualization_test.go @@ -72,8 +72,8 @@ func newContainerTester(t *testing.T, rec *fake.TopLevelRecorder) *containerTest t.Fatalf("TempDir(): %v", err) } - // __nocloud__ is a hint for fake libvirt domain to fix the path - SetNocloudIsoDir(filepath.Join(ct.tmpDir, "__nocloud__")) + // __config__ is a hint for fake libvirt domain to fix the path + SetConfigIsoDir(filepath.Join(ct.tmpDir, "__config__")) downloader := utils.NewFakeDownloader(ct.tmpDir) ct.rec = rec @@ -93,9 +93,9 @@ func newContainerTester(t *testing.T, rec *fake.TopLevelRecorder) *containerTest volSrc := CombineVMVolumeSources( GetRootVolume, ScanFlexvolumes, - // XXX: GetNocloudVolume must go last because it + // XXX: GetConfigVolume must go last because it // doesn't produce correct name for cdrom devices - GetNocloudVolume) + GetConfigVolume) ct.virtTool, err = NewVirtualizationTool(ct.domainConn, ct.storageConn, imageTool, ct.metadataStore, "volumes", "loop*", volSrc) if err != nil { t.Fatalf("failed to create VirtualizationTool: %v", err) diff --git a/pkg/manager/manager.go b/pkg/manager/manager.go index f5fbdaaac..c82e91d13 100644 --- a/pkg/manager/manager.go +++ b/pkg/manager/manager.go @@ -86,9 +86,9 @@ func NewVirtletManager(libvirtUri, poolName, downloadProtocol, storageBackend, r volSrc := libvirttools.CombineVMVolumeSources( libvirttools.GetRootVolume, libvirttools.ScanFlexvolumes, - // XXX: GetNocloudVolume must go last because it + // XXX: GetConfigVolume must go last because it // doesn't produce correct name for cdrom devices - libvirttools.GetNocloudVolume) + libvirttools.GetConfigVolume) // TODO: pool name should be passed like for imageTool libvirtVirtualizationTool, err := libvirttools.NewVirtualizationTool(conn, conn, libvirtImageTool, metadataStore, "volumes", rawDevices, volSrc) if err != nil { diff --git a/pkg/virt/fake/fake_domain.go b/pkg/virt/fake/fake_domain.go index c5d59eaaf..e98381438 100644 --- a/pkg/virt/fake/fake_domain.go +++ b/pkg/virt/fake/fake_domain.go @@ -30,8 +30,8 @@ import ( ) const ( - nocloudPathHint = "/__nocloud__/" - nocloudPathReplacement = "/var/lib/virtlet/nocloud/" + configPathHint = "/__config__/" + configPathReplacement = "/var/lib/virtlet/config/" ) type FakeDomainConnection struct { @@ -103,9 +103,9 @@ func (dc *FakeDomainConnection) DefineDomain(def *libvirtxml.Domain) (virt.VirtD if disk.Type != "file" || disk.Source == nil { continue } - p := strings.Index(disk.Source.File, nocloudPathHint) + p := strings.Index(disk.Source.File, configPathHint) if p >= 0 { - disk.Source.File = nocloudPathReplacement + disk.Source.File[p+len(nocloudPathHint):] + disk.Source.File = configPathReplacement + disk.Source.File[p+len(configPathHint):] } } } @@ -195,7 +195,7 @@ func (d *FakeDomain) Create() error { continue } origPath := disk.Source.File - if filepath.Ext(origPath) == ".iso" || strings.HasPrefix(filepath.Base(origPath), "nocloud-iso") { + if filepath.Ext(origPath) == ".iso" || strings.HasPrefix(filepath.Base(origPath), "config-iso") { m, err := testutils.IsoToMap(origPath) if err != nil { return fmt.Errorf("bad iso image: %q", origPath) diff --git a/tests/integration/configdrive_test.go b/tests/integration/configdrive_test.go new file mode 100644 index 000000000..292a64ae1 --- /dev/null +++ b/tests/integration/configdrive_test.go @@ -0,0 +1,71 @@ +/* +Copyright 2018 Mirantis + +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 integration + +import ( + "reflect" + "testing" + + kubeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" + + "github.com/Mirantis/virtlet/pkg/utils" + testutils "github.com/Mirantis/virtlet/pkg/utils/testing" +) + +const ( + configDriveMetaData = "{\"hostname\":\"testName_0\",\"instance-id\":\"testName_0.default\",\"local-hostname\":\"testName_0\",\"uuid\":\"testName_0.default\"}" + configDriveUserData = "#cloud-config\n" + configDriveNetworkConfig = "{}" +) + +func TestCloudInitConfigDrive(t *testing.T) { + ct := newContainerTester(t) + defer ct.teardown() + imageSpec := ct.imageSpecs[0] + sandbox := ct.sandboxes[0] + sandbox.Annotations["VirtletCloudInitImageType"] = "configdrive" + container := ct.containers[0] + + ct.pullImage(imageSpec) + ct.runPodSandbox(sandbox) + ct.createContainer(sandbox, container, imageSpec, nil) + ct.startContainer(container.ContainerId) + + ct.verifyContainerState(container.ContainerId, container.Name, kubeapi.ContainerState_CONTAINER_RUNNING) + + isoPath := runShellCommand(t, `virsh domblklist $(virsh list --name)|grep -o '/.*config-.*\.iso[^ ]*'`) + files, err := testutils.IsoToMap(isoPath) + if err != nil { + t.Fatalf("isoToMap() on %q: %v", isoPath, err) + } + expectedFiles := map[string]interface{}{ + "openstack": map[string]interface{}{ + "latest": map[string]interface{}{ + "meta_data.json": configDriveMetaData, + "network_data.json": configDriveNetworkConfig, + "user_data": configDriveUserData, + }, + }, + } + if !reflect.DeepEqual(files, expectedFiles) { + t.Errorf("bad config drive iso:\n%s\n-- instead of --\n%s", utils.MapToJson(files), utils.MapToJson(expectedFiles)) + } + + ct.stopContainer(container.ContainerId) + ct.removeContainer(container.ContainerId) + ct.verifyNoContainers(nil) +} diff --git a/tests/integration/nocloud_test.go b/tests/integration/nocloud_test.go index b70ddae7e..136a2665e 100644 --- a/tests/integration/nocloud_test.go +++ b/tests/integration/nocloud_test.go @@ -46,7 +46,7 @@ func TestCloudInitNoCloud(t *testing.T) { ct.verifyContainerState(container.ContainerId, container.Name, kubeapi.ContainerState_CONTAINER_RUNNING) - isoPath := runShellCommand(t, `virsh domblklist $(virsh list --name)|grep -o '/.*nocloud-.*\.iso[^ ]*'`) + isoPath := runShellCommand(t, `virsh domblklist $(virsh list --name)|grep -o '/.*config-.*\.iso[^ ]*'`) files, err := testutils.IsoToMap(isoPath) if err != nil { t.Fatalf("isoToMap() on %q: %v", isoPath, err)