Merge lp:~darkmuggle-deactivatedaccount/cloud-init/azure-disk_format into lp:~cloud-init-dev/cloud-init/trunk

Proposed by Ben Howard
Status: Merged
Merged at revision: 878
Proposed branch: lp:~darkmuggle-deactivatedaccount/cloud-init/azure-disk_format
Merge into: lp:~cloud-init-dev/cloud-init/trunk
Diff against target: 745 lines (+245/-145)
8 files modified
ChangeLog (+1/-0)
cloudinit/config/cc_disk_setup.py (+83/-82)
cloudinit/sources/DataSourceAzure.py (+21/-7)
cloudinit/sources/DataSourceSmartOS.py (+8/-8)
doc/examples/cloud-config-disk-setup.txt (+46/-16)
doc/sources/smartos/README.rst (+17/-11)
tests/unittests/test_datasource/test_azure.py (+55/-5)
tests/unittests/test_datasource/test_smartos.py (+14/-16)
To merge this branch: bzr merge lp:~darkmuggle-deactivatedaccount/cloud-init/azure-disk_format
Reviewer Review Type Date Requested Status
Scott Moser Pending
Review via email: mp+187805@code.launchpad.net

Commit message

Azure: partition and create filesystem of ephemeral storage device.

This utilizes the work on the 'cc_disk_setup' to make the Azure datasource
by default partition and create a filesystem on the ephemeral storage
device.

The user can override this behavior via user-data.

Description of the change

Add the ability for cloud-init to handle formating the Azure ephemeral storage.

To post a comment you must log in.
873. By Scott Moser

merge from trunk

874. By Scott Moser

fix pep8

875. By Scott Moser

add entry to ChangeLog

876. By Scott Moser

re-work 'ephemeral_disk' and location of builtin config

Previously we had this 'ephemeral_disk' entry in the datasource config
for Azure, and then we also copied some entries into the .cfg
for that datasource from the datasource config.

Ie, datasource['Azure']['disk_setup'] would be oddly copied
into the .cfg object that was returned by 'get_config_obj'

Now, instead, we have a BUILTIN_CLOUD_CONFIG, which has those same
values in it.

The other change here is that 'ephemeral_disk' now has no meaning.
Instead, we add a populated-by-default entry 'disk_aliases' to the
BUILTIN_DS_CFG, and then just return entries in it for
'device_name_to_device'

877. By Scott Moser

fix tests small other changes

Also
 * cloudinit/sources/DataSourceAzure.py: invalid xml in a file called
   'ovfenv.xml' should raise BrokenAzureDatasource rather than
   NonAzureDataSource
 * cloudinit/sources/DataSourceSmartOS.py:
   cloudinit/sources/DataSourceAzure.py
   use 'ephemeral0' as the device name in builtin fs_setup

 * tests/unittests/test_datasource/test_azure.py:
    * always patch 'list_possible_azure_ds_devs' as it calls find_devs_with
      which calls blkid, and dramatically was slowing down tests on my system.
    * test_user_cfg_set_agent_command_plain:
      fix this test to not depend on specific format of yaml.dumps().
    * test_userdata_arrives: add a test that user-data makes it through

878. By Scott Moser

fix doc for smartos

879. By Scott Moser

fix probably debug code that explicitly set 'partition' to 'any'.

880. By Scott Moser

update documentation

881. By Scott Moser

azure data source 'ephemeral0' point to '/dev/sdb', not /dev/sdb1

in general block device mappings should be to block devices, not
partitoins.

882. By Scott Moser

fix syntax error in last commit

883. By Scott Moser

update to get functional with any alias in a 'device' entry

884. By Scott Moser

adjust aliases early rather than late.

this adds 2 functions
  update_disk_setup_devices
  update_fs_setup_devices

Which update the appropriate datatype, and translate the names.
Translating early means we don't have to deal with updating in the mkfs or
mkpart calls explicitly.

These are more easily unit tested as they just take a dictionary of the
expected type and a 'transformer' that should return a new name or None.

885. By Scott Moser

merge from ben. disable partition creation on smartos

886. By Scott Moser

demote .info to .debug, add another debug

887. By Scott Moser

fix test to adjust for 'ephemeral0' being /dev/sdb

888. By Scott Moser

fix one bug

889. By Scott Moser

change LOG.debug('format' % var) to ('format', var)

890. By Scott Moser

find_device_node: treat label=None as label=""

Since for a string there is no difference, we're just
checking for this here.

891. By Ben Howard

Updated the documentation on disk formating

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'ChangeLog'
--- ChangeLog 2013-09-26 12:47:14 +0000
+++ ChangeLog 2013-09-27 22:26:03 +0000
@@ -3,6 +3,7 @@
3 - small fix for OVF datasource for iso transport on non-iso9660 filesystem3 - small fix for OVF datasource for iso transport on non-iso9660 filesystem
4 - determine if upstart version is suitable for4 - determine if upstart version is suitable for
5 'initctl reload-configuration' (LP: #1124384). If so, then invoke it.5 'initctl reload-configuration' (LP: #1124384). If so, then invoke it.
6 supports setting up instance-store disk with partition table and filesystem.
6 - add Azure datasource.7 - add Azure datasource.
7 - add support for SuSE / SLES [Juerg Haefliger]8 - add support for SuSE / SLES [Juerg Haefliger]
8 - add a trailing carriage return to chpasswd input, which reportedly9 - add a trailing carriage return to chpasswd input, which reportedly
910
=== modified file 'cloudinit/config/cc_disk_setup.py'
--- cloudinit/config/cc_disk_setup.py 2013-09-19 22:49:50 +0000
+++ cloudinit/config/cc_disk_setup.py 2013-09-27 22:26:03 +0000
@@ -41,7 +41,8 @@
41 """41 """
42 disk_setup = cfg.get("disk_setup")42 disk_setup = cfg.get("disk_setup")
43 if isinstance(disk_setup, dict):43 if isinstance(disk_setup, dict):
44 log.info("Partitioning disks.")44 update_disk_setup_devices(disk_setup, cloud.device_name_to_device)
45 log.debug("Partitioning disks: %s", str(disk_setup))
45 for disk, definition in disk_setup.items():46 for disk, definition in disk_setup.items():
46 if not isinstance(definition, dict):47 if not isinstance(definition, dict):
47 log.warn("Invalid disk definition for %s" % disk)48 log.warn("Invalid disk definition for %s" % disk)
@@ -51,13 +52,14 @@
51 log.debug("Creating new partition table/disk")52 log.debug("Creating new partition table/disk")
52 util.log_time(logfunc=LOG.debug,53 util.log_time(logfunc=LOG.debug,
53 msg="Creating partition on %s" % disk,54 msg="Creating partition on %s" % disk,
54 func=mkpart, args=(disk, cloud, definition))55 func=mkpart, args=(disk, definition))
55 except Exception as e:56 except Exception as e:
56 util.logexc(LOG, "Failed partitioning operation\n%s" % e)57 util.logexc(LOG, "Failed partitioning operation\n%s" % e)
5758
58 fs_setup = cfg.get("fs_setup")59 fs_setup = cfg.get("fs_setup")
59 if isinstance(fs_setup, list):60 if isinstance(fs_setup, list):
60 log.info("Creating file systems.")61 log.debug("setting up filesystems: %s", str(fs_setup))
62 update_fs_setup_devices(fs_setup, cloud.device_name_to_device)
61 for definition in fs_setup:63 for definition in fs_setup:
62 if not isinstance(definition, dict):64 if not isinstance(definition, dict):
63 log.warn("Invalid file system definition: %s" % definition)65 log.warn("Invalid file system definition: %s" % definition)
@@ -68,31 +70,48 @@
68 device = definition.get('device')70 device = definition.get('device')
69 util.log_time(logfunc=LOG.debug,71 util.log_time(logfunc=LOG.debug,
70 msg="Creating fs for %s" % device,72 msg="Creating fs for %s" % device,
71 func=mkfs, args=(cloud, definition))73 func=mkfs, args=(definition,))
72 except Exception as e:74 except Exception as e:
73 util.logexc(LOG, "Failed during filesystem operation\n%s" % e)75 util.logexc(LOG, "Failed during filesystem operation\n%s" % e)
7476
7577
76def is_default_device(name, cloud, fallback=None):78def update_disk_setup_devices(disk_setup, tformer):
77 """79 # update 'disk_setup' dictionary anywhere were a device may occur
78 Ask the cloud datasource if the 'name' maps to a default80 # update it with the response from 'tformer'
79 device. If so, return that value, otherwise return 'name', or81 for origname in disk_setup.keys():
80 fallback if so defined.82 transformed = tformer(origname)
81 """83 if transformed is None or transformed == origname:
8284 continue
83 _dev = None85 if transformed in disk_setup:
84 try:86 LOG.info("Replacing %s in disk_setup for translation of %s",
85 _dev = cloud.device_name_to_device(name)87 origname, transformed)
86 except Exception as e:88 del disk_setup[transformed]
87 util.logexc(LOG, "Failed to find mapping for %s" % e)89
8890 disk_setup[transformed] = disk_setup[origname]
89 if _dev:91 disk_setup[transformed]['_origname'] = origname
90 return _dev92 del disk_setup[origname]
9193 LOG.debug("updated disk_setup device entry '%s' to '%s'",
92 if fallback:94 origname, transformed)
93 return fallback95
9496
95 return name97def update_fs_setup_devices(disk_setup, tformer):
98 # update 'fs_setup' dictionary anywhere were a device may occur
99 # update it with the response from 'tformer'
100 for definition in disk_setup:
101 if not isinstance(definition, dict):
102 LOG.warn("entry in disk_setup not a dict: %s", definition)
103 continue
104
105 origname = definition.get('device')
106 if origname is None:
107 continue
108
109 transformed = tformer(origname)
110 if transformed is None or transformed == origname:
111 continue
112
113 definition['_origname'] = origname
114 definition['device'] = transformed
96115
97116
98def value_splitter(values, start=None):117def value_splitter(values, start=None):
@@ -195,6 +214,10 @@
195214
196 Note: This works with GPT partition tables!215 Note: This works with GPT partition tables!
197 """216 """
217 # label of None is same as no label
218 if label is None:
219 label = ""
220
198 if not valid_targets:221 if not valid_targets:
199 valid_targets = ['disk', 'part']222 valid_targets = ['disk', 'part']
200223
@@ -219,8 +242,8 @@
219 for key, value in value_splitter(part):242 for key, value in value_splitter(part):
220 d[key.lower()] = value243 d[key.lower()] = value
221244
222 if d['fstype'] == fs_type and \245 if (d['fstype'] == fs_type and
223 ((label_match and d['label'] == label) or not label_match):246 ((label_match and d['label'] == label) or not label_match)):
224 # If we find a matching device, we return that247 # If we find a matching device, we return that
225 return ('/dev/%s' % d['name'], True)248 return ('/dev/%s' % d['name'], True)
226249
@@ -397,8 +420,8 @@
397 # Create a single partition420 # Create a single partition
398 return "0,"421 return "0,"
399422
400 if (len(layout) == 0 and isinstance(layout, list)) or \423 if ((len(layout) == 0 and isinstance(layout, list)) or
401 not isinstance(layout, list):424 not isinstance(layout, list)):
402 raise Exception("Partition layout is invalid")425 raise Exception("Partition layout is invalid")
403426
404 last_part_num = len(layout)427 last_part_num = len(layout)
@@ -414,8 +437,7 @@
414437
415 if isinstance(part, list):438 if isinstance(part, list):
416 if len(part) != 2:439 if len(part) != 2:
417 raise Exception("Partition was incorrectly defined: %s" % \440 raise Exception("Partition was incorrectly defined: %s" % part)
418 part)
419 percent, part_type = part441 percent, part_type = part
420442
421 part_size = int((float(size) * (float(percent) / 100)) / 1024)443 part_size = int((float(size) * (float(percent) / 100)) / 1024)
@@ -488,12 +510,11 @@
488 return get_dyn_func("exec_mkpart_%s", table_type, device, layout)510 return get_dyn_func("exec_mkpart_%s", table_type, device, layout)
489511
490512
491def mkpart(device, cloud, definition):513def mkpart(device, definition):
492 """514 """
493 Creates the partition table.515 Creates the partition table.
494516
495 Parameters:517 Parameters:
496 cloud: the cloud object
497 definition: dictionary describing how to create the partition.518 definition: dictionary describing how to create the partition.
498519
499 The following are supported values in the dict:520 The following are supported values in the dict:
@@ -508,29 +529,18 @@
508 overwrite = definition.get('overwrite', False)529 overwrite = definition.get('overwrite', False)
509 layout = definition.get('layout', False)530 layout = definition.get('layout', False)
510 table_type = definition.get('table_type', 'mbr')531 table_type = definition.get('table_type', 'mbr')
511 _device = is_default_device(device, cloud)
512532
513 # Check if the default device is a partition or not533 # Check if the default device is a partition or not
514 LOG.debug("Checking against default devices")534 LOG.debug("Checking against default devices")
515 if _device and (_device != device):
516 if not is_device_valid(_device):
517 _device = _device[:-1]
518
519 if not is_device_valid(_device):
520 raise Exception("Unable to find backing block device for %s" % \
521 device)
522 else:
523 LOG.debug("Mapped %s to physical device %s" % (device, _device))
524 device = _device
525535
526 if (isinstance(layout, bool) and not layout) or not layout:536 if (isinstance(layout, bool) and not layout) or not layout:
527 LOG.debug("Device is not to be partitioned, skipping")537 LOG.debug("Device is not to be partitioned, skipping")
528 return # Device is not to be partitioned538 return # Device is not to be partitioned
529539
530 # This prevents you from overwriting the device540 # This prevents you from overwriting the device
531 LOG.debug("Checking if device %s is a valid device" % device)541 LOG.debug("Checking if device %s is a valid device", device)
532 if not is_device_valid(device):542 if not is_device_valid(device):
533 raise Exception("Device %s is not a disk device!" % device)543 raise Exception("Device %s is not a disk device!", device)
534544
535 LOG.debug("Checking if device layout matches")545 LOG.debug("Checking if device layout matches")
536 if check_partition_layout(table_type, device, layout):546 if check_partition_layout(table_type, device, layout):
@@ -549,13 +559,13 @@
549 part_definition = get_partition_layout(table_type, device_size, layout)559 part_definition = get_partition_layout(table_type, device_size, layout)
550 LOG.debug(" Layout is: %s" % part_definition)560 LOG.debug(" Layout is: %s" % part_definition)
551561
552 LOG.debug("Creating partition table on %s" % device)562 LOG.debug("Creating partition table on %s", device)
553 exec_mkpart(table_type, device, part_definition)563 exec_mkpart(table_type, device, part_definition)
554564
555 LOG.debug("Partition table created for %s" % device)565 LOG.debug("Partition table created for %s", device)
556566
557567
558def mkfs(cloud, fs_cfg):568def mkfs(fs_cfg):
559 """569 """
560 Create a file system on the device.570 Create a file system on the device.
561571
@@ -576,54 +586,45 @@
576586
577 When 'cmd' is provided then no other parameter is required.587 When 'cmd' is provided then no other parameter is required.
578 """588 """
579 fs_cfg['partition'] = 'any'
580 label = fs_cfg.get('label')589 label = fs_cfg.get('label')
581 device = fs_cfg.get('device')590 device = fs_cfg.get('device')
582 partition = str(fs_cfg.get('partition'))591 partition = str(fs_cfg.get('partition', 'any'))
583 fs_type = fs_cfg.get('filesystem')592 fs_type = fs_cfg.get('filesystem')
584 fs_cmd = fs_cfg.get('cmd', [])593 fs_cmd = fs_cfg.get('cmd', [])
585 fs_opts = fs_cfg.get('extra_opts', [])594 fs_opts = fs_cfg.get('extra_opts', [])
586 overwrite = fs_cfg.get('overwrite', False)595 overwrite = fs_cfg.get('overwrite', False)
587596
588 # This allows you to define the default ephemeral or swap597 # This allows you to define the default ephemeral or swap
589 LOG.debug("Checking %s against default devices" % device)598 LOG.debug("Checking %s against default devices", device)
590 _device = is_default_device(label, cloud, fallback=device)
591 if _device and (_device != device):
592 if not is_device_valid(_device):
593 raise Exception("Unable to find backing block device for %s" % \
594 device)
595 else:
596 LOG.debug("Mapped %s to physical device %s" % (device, _device))
597 device = _device
598599
599 if not partition or partition.isdigit():600 if not partition or partition.isdigit():
600 # Handle manual definition of partition601 # Handle manual definition of partition
601 if partition.isdigit():602 if partition.isdigit():
602 device = "%s%s" % (device, partition)603 device = "%s%s" % (device, partition)
603 LOG.debug("Manual request of partition %s for %s" % (604 LOG.debug("Manual request of partition %s for %s",
604 partition, device))605 partition, device)
605606
606 # Check to see if the fs already exists607 # Check to see if the fs already exists
607 LOG.debug("Checking device %s" % device)608 LOG.debug("Checking device %s", device)
608 check_label, check_fstype, _ = check_fs(device)609 check_label, check_fstype, _ = check_fs(device)
609 LOG.debug("Device %s has %s %s" % (device, check_label, check_fstype))610 LOG.debug("Device %s has %s %s", device, check_label, check_fstype)
610611
611 if check_label == label and check_fstype == fs_type:612 if check_label == label and check_fstype == fs_type:
612 LOG.debug("Existing file system found at %s" % device)613 LOG.debug("Existing file system found at %s", device)
613614
614 if not overwrite:615 if not overwrite:
615 LOG.warn("Device %s has required file system" % device)616 LOG.debug("Device %s has required file system", device)
616 return617 return
617 else:618 else:
618 LOG.warn("Destroying filesystem on %s" % device)619 LOG.warn("Destroying filesystem on %s", device)
619620
620 else:621 else:
621 LOG.debug("Device %s is cleared for formating" % device)622 LOG.debug("Device %s is cleared for formating", device)
622623
623 elif partition and str(partition).lower() in ('auto', 'any'):624 elif partition and str(partition).lower() in ('auto', 'any'):
624 # For auto devices, we match if the filesystem does exist625 # For auto devices, we match if the filesystem does exist
625 odevice = device626 odevice = device
626 LOG.debug("Identifying device to create %s filesytem on" % label)627 LOG.debug("Identifying device to create %s filesytem on", label)
627628
628 # any mean pick the first match on the device with matching fs_type629 # any mean pick the first match on the device with matching fs_type
629 label_match = True630 label_match = True
@@ -632,33 +633,32 @@
632633
633 device, reuse = find_device_node(device, fs_type=fs_type, label=label,634 device, reuse = find_device_node(device, fs_type=fs_type, label=label,
634 label_match=label_match)635 label_match=label_match)
635 LOG.debug("Automatic device for %s identified as %s" % (636 LOG.debug("Automatic device for %s identified as %s", odevice, device)
636 odevice, device))
637637
638 if reuse:638 if reuse:
639 LOG.debug("Found filesystem match, skipping formating.")639 LOG.debug("Found filesystem match, skipping formating.")
640 return640 return
641641
642 if not device:642 if not device:
643 LOG.debug("No device aviable that matches request.")643 LOG.debug("No device aviable that matches request. "
644 LOG.debug("Skipping fs creation for %s" % fs_cfg)644 "Skipping fs creation for %s", fs_cfg)
645 return645 return
646646
647 else:647 else:
648 LOG.debug("Error in device identification handling.")648 LOG.debug("Error in device identification handling.")
649 return649 return
650650
651 LOG.debug("File system %s will be created on %s" % (label, device))651 LOG.debug("File system %s will be created on %s", label, device)
652652
653 # Make sure the device is defined653 # Make sure the device is defined
654 if not device:654 if not device:
655 LOG.critical("Device is not known: %s" % fs_cfg)655 LOG.warn("Device is not known: %s", device)
656 return656 return
657657
658 # Check that we can create the FS658 # Check that we can create the FS
659 if not label or not fs_type:659 if not (fs_type or fs_cmd):
660 LOG.debug("Command to create filesystem %s is bad. Skipping." % \660 raise Exception("No way to create filesystem '%s'. fs_type or fs_cmd "
661 label)661 "must be set.", label)
662662
663 # Create the commands663 # Create the commands
664 if fs_cmd:664 if fs_cmd:
@@ -673,7 +673,8 @@
673 mkfs_cmd = util.which("mk%s" % fs_type)673 mkfs_cmd = util.which("mk%s" % fs_type)
674674
675 if not mkfs_cmd:675 if not mkfs_cmd:
676 LOG.critical("Unable to locate command to create filesystem.")676 LOG.warn("Cannot create fstype '%s'. No mkfs.%s command", fs_type,
677 fs_type)
677 return678 return
678679
679 fs_cmd = [mkfs_cmd, device]680 fs_cmd = [mkfs_cmd, device]
@@ -685,8 +686,8 @@
685 if fs_opts:686 if fs_opts:
686 fs_cmd.extend(fs_opts)687 fs_cmd.extend(fs_opts)
687688
688 LOG.debug("Creating file system %s on %s" % (label, device))689 LOG.debug("Creating file system %s on %s", label, device)
689 LOG.debug(" Using cmd: %s" % "".join(fs_cmd))690 LOG.debug(" Using cmd: %s", "".join(fs_cmd))
690 try:691 try:
691 util.subp(fs_cmd)692 util.subp(fs_cmd)
692 except Exception as e:693 except Exception as e:
693694
=== modified file 'cloudinit/sources/DataSourceAzure.py'
--- cloudinit/sources/DataSourceAzure.py 2013-09-09 23:44:50 +0000
+++ cloudinit/sources/DataSourceAzure.py 2013-09-27 22:26:03 +0000
@@ -44,8 +44,20 @@
44 'policy': True,44 'policy': True,
45 'command': BOUNCE_COMMAND,45 'command': BOUNCE_COMMAND,
46 'hostname_command': 'hostname',46 'hostname_command': 'hostname',
47 }47 },
48}48 'disk_aliases': {'ephemeral0': '/dev/sdb'},
49}
50
51BUILTIN_CLOUD_CONFIG = {
52 'disk_setup': {
53 'ephemeral0': {'table_type': 'mbr',
54 'layout': True,
55 'overwrite': False}
56 },
57 'fs_setup': [{'filesystem': 'ext4', 'device': 'ephemeral0',
58 'partition': 'auto'}],
59}
60
49DS_CFG_PATH = ['datasource', DS_NAME]61DS_CFG_PATH = ['datasource', DS_NAME]
5062
5163
@@ -94,7 +106,7 @@
94 (md, self.userdata_raw, cfg, files) = ret106 (md, self.userdata_raw, cfg, files) = ret
95 self.seed = cdev107 self.seed = cdev
96 self.metadata = util.mergemanydict([md, DEFAULT_METADATA])108 self.metadata = util.mergemanydict([md, DEFAULT_METADATA])
97 self.cfg = cfg109 self.cfg = util.mergemanydict([cfg, BUILTIN_CLOUD_CONFIG])
98 found = cdev110 found = cdev
99111
100 LOG.debug("found datasource in %s", cdev)112 LOG.debug("found datasource in %s", cdev)
@@ -112,8 +124,8 @@
112 self.metadata['random_seed'] = seed124 self.metadata['random_seed'] = seed
113125
114 # now update ds_cfg to reflect contents pass in config126 # now update ds_cfg to reflect contents pass in config
115 usercfg = util.get_cfg_by_path(self.cfg, DS_CFG_PATH, {})127 user_ds_cfg = util.get_cfg_by_path(self.cfg, DS_CFG_PATH, {})
116 self.ds_cfg = util.mergemanydict([usercfg, self.ds_cfg])128 self.ds_cfg = util.mergemanydict([user_ds_cfg, self.ds_cfg])
117 mycfg = self.ds_cfg129 mycfg = self.ds_cfg
118130
119 # walinux agent writes files world readable, but expects131 # walinux agent writes files world readable, but expects
@@ -161,9 +173,11 @@
161 pubkeys = pubkeys_from_crt_files(fp_files)173 pubkeys = pubkeys_from_crt_files(fp_files)
162174
163 self.metadata['public-keys'] = pubkeys175 self.metadata['public-keys'] = pubkeys
164
165 return True176 return True
166177
178 def device_name_to_device(self, name):
179 return self.ds_cfg['disk_aliases'].get(name)
180
167 def get_config_obj(self):181 def get_config_obj(self):
168 return self.cfg182 return self.cfg
169183
@@ -349,7 +363,7 @@
349 try:363 try:
350 dom = minidom.parseString(contents)364 dom = minidom.parseString(contents)
351 except Exception as e:365 except Exception as e:
352 raise NonAzureDataSource("invalid xml: %s" % e)366 raise BrokenAzureDataSource("invalid xml: %s" % e)
353367
354 results = find_child(dom.documentElement,368 results = find_child(dom.documentElement,
355 lambda n: n.localName == "ProvisioningSection")369 lambda n: n.localName == "ProvisioningSection")
356370
=== modified file 'cloudinit/sources/DataSourceSmartOS.py'
--- cloudinit/sources/DataSourceSmartOS.py 2013-09-19 22:49:50 +0000
+++ cloudinit/sources/DataSourceSmartOS.py 2013-09-27 22:26:03 +0000
@@ -72,14 +72,17 @@
72 'iptables_disable'],72 'iptables_disable'],
73 'base64_keys': [],73 'base64_keys': [],
74 'base64_all': False,74 'base64_all': False,
75 'ephemeral_disk': '/dev/vdb',75 'disk_aliases': {'ephemeral0': '/dev/vdb'},
76}
77
78BUILTIN_CLOUD_CONFIG = {
76 'disk_setup': {79 'disk_setup': {
77 'ephemeral0': {'table_type': 'mbr',80 'ephemeral0': {'table_type': 'mbr',
78 'layout': True,81 'layout': False,
79 'overwrite': False}82 'overwrite': False}
80 },83 },
81 'fs_setup': [{'label': 'ephemeral0', 'filesystem': 'ext3',84 'fs_setup': [{'label': 'ephemeral0', 'filesystem': 'ext3',
82 'device': '/dev/xvdb', 'partition': 'auto'}],85 'device': 'ephemeral0', 'partition': 'auto'}],
83}86}
8487
8588
@@ -94,9 +97,7 @@
94 BUILTIN_DS_CONFIG])97 BUILTIN_DS_CONFIG])
9598
96 self.metadata = {}99 self.metadata = {}
97 self.cfg = {}100 self.cfg = BUILTIN_CLOUD_CONFIG
98 self.cfg['disk_setup'] = self.ds_cfg.get('disk_setup')
99 self.cfg['fs_setup'] = self.ds_cfg.get('fs_setup')
100101
101 self.seed = self.ds_cfg.get("serial_device")102 self.seed = self.ds_cfg.get("serial_device")
102 self.seed_timeout = self.ds_cfg.get("serial_timeout")103 self.seed_timeout = self.ds_cfg.get("serial_timeout")
@@ -154,8 +155,7 @@
154 return True155 return True
155156
156 def device_name_to_device(self, name):157 def device_name_to_device(self, name):
157 if 'ephemeral0' in name:158 return self.ds_cfg['disk_aliases'].get(name)
158 return self.ds_cfg['ephemeral_disk']
159159
160 def get_config_obj(self):160 def get_config_obj(self):
161 return self.cfg161 return self.cfg
162162
=== modified file 'doc/examples/cloud-config-disk-setup.txt'
--- doc/examples/cloud-config-disk-setup.txt 2013-09-19 22:49:50 +0000
+++ doc/examples/cloud-config-disk-setup.txt 2013-09-27 22:26:03 +0000
@@ -19,36 +19,36 @@
1919
20Default disk definitions for Windows Azure20Default disk definitions for Windows Azure
21------------------------------------------21------------------------------------------
22(Not implemented yet due to conflict with WALinuxAgent in Ubuntu)
2322
23device_aliases: {'ephemeral0': '/dev/sdb'}
24disk_setup:24disk_setup:
25 /dev/sdb:25 ephemeral0:
26 type: mbr26 type: mbr
27 layout: True27 layout: True
28 overwrite: False28 overwrite: False
2929
30fs_setup:30fs_setup:
31 - label: ephemeral031 - label: ephemeral0
32 filesystem: ext332 filesystem: ext4
33 device: ephemeral033 device: ephemeral0
34 partition: any34 partition: auto
3535
3636
37Default disk definitions for SmartOS37Default disk definitions for SmartOS
38------------------------------------38------------------------------------
3939
40ephemeral_disk: /dev/vdb40device_aliases: {'ephemeral0': '/dev/sdb'}
41disk_setup:41disk_setup:
42 /dev/vdb:42 ephemeral0:
43 type: mbr43 type: mbr
44 layout: True44 layout: False
45 overwrite: False45 overwrite: False
4646
47fs_setup:47fs_setup:
48 - label: ephemeral048 - label: ephemeral0
49 filesystem: ext349 filesystem: ext3
50 device: /dev/vdb50 device: ephemeral0
51 partition: 151 partition: auto
5252
53Cavaut for SmartOS: if ephemeral disk is not defined, then the disk will53Cavaut for SmartOS: if ephemeral disk is not defined, then the disk will
54 not be automatically added to the mounts.54 not be automatically added to the mounts.
@@ -188,13 +188,43 @@
188 of the ephemeral storage layer.188 of the ephemeral storage layer.
189189
190 <PART_VALUE>: The valid options are:190 <PART_VALUE>: The valid options are:
191 "auto": auto is a special in the sense that you are telling cloud-init191 "auto|any": tell cloud-init not to care whether there is a partition
192 not to care whether there is a partition or not. Auto will put the192 or not. Auto will use the first partition that does not contain a
193 first partition that does not contain a file system already. In193 file system already. In the absence of a partition table, it will
194 the absence of a partition table, it will put it directly on the194 put it directly on the disk.
195 disk.195
196196 "auto": If a file system that matches the specification in terms of
197 "none": Put the partition directly on the disk.197 label, type and device, then cloud-init will skip the creation of
198 the file system.
199
200 "any": If a file system that matches the file system type and device,
201 then cloud-init will skip the creation of the file system.
202
203 Devices are selected based on first-detected, starting with partitions
204 and then the raw disk. Consider the following:
205 NAME FSTYPE LABEL
206 xvdb
207 ├─xvdb1 ext4
208 ├─xvdb2
209 ├─xvdb3 btrfs test
210 └─xvdb4 ext4 test
211
212 If you ask for 'auto', label of 'test, and file system of 'ext4'
213 then cloud-init will select the 2nd partition, even though there
214 is a partition match at the 4th partition.
215
216 If you ask for 'any' and a label of 'test', then cloud-init will
217 select the 1st partition.
218
219 If you ask for 'auto' and don't define label, then cloud-init will
220 select the 1st partition.
221
222 In general, if you have a specific partition configuration in mind,
223 you should define either the device or the partition number. 'auto'
224 and 'any' are specifically intended for formating ephemeral storage or
225 for simple schemes.
226
227 "none": Put the file system directly on the device.
198228
199 <NUM>: where NUM is the actual partition number.229 <NUM>: where NUM is the actual partition number.
200230
201231
=== modified file 'doc/sources/smartos/README.rst'
--- doc/sources/smartos/README.rst 2013-09-19 22:49:50 +0000
+++ doc/sources/smartos/README.rst 2013-09-27 22:26:03 +0000
@@ -73,15 +73,21 @@
73 (i.e. /etc/cloud/cloud.cfg.d) that sets which values should not be73 (i.e. /etc/cloud/cloud.cfg.d) that sets which values should not be
74 base64 decoded.74 base64 decoded.
7575
76ephemeral_disk:76disk_aliases and ephemeral disk:
77---------------77---------------
7878By default, SmartOS only supports a single ephemeral disk. That disk is
79In order to instruct Cloud-init which disk to auto-mount. By default,79completely empty (un-partitioned with no filesystem).
80SmartOS only supports a single ephemeral disk.80
8181The SmartOS datasource has built-in cloud-config which instructs the
82The default SmartOS configuration will prepare the ephemeral disk and format82'disk_setup' module to partition and format the ephemeral disk.
83it for you. SmartOS does not, by default, prepare the ephemeral disk for you.83
8484You can control the disk_setup then in 2 ways:
85If you change ephemeral_disk, you should also consider changing85 1. through the datasource config, you can change the 'alias' of
86the default disk formatting parameters. See86 ephermeral0 to reference another device. The default is:
87doc/examples/cloud-config-disk-setup.txt for information on using this.87 'disk_aliases': {'ephemeral0': '/dev/vdb'},
88 Which means anywhere disk_setup sees a device named 'ephemeral0'
89 then /dev/vdb will be substituted.
90 2. you can provide disk_setup or fs_setup data in user-data to overwrite
91 the datasource's built-in values.
92
93See doc/examples/cloud-config-disk-setup.txt for information on disk_setup.
8894
=== modified file 'tests/unittests/test_datasource/test_azure.py'
--- tests/unittests/test_datasource/test_azure.py 2013-08-15 17:16:01 +0000
+++ tests/unittests/test_datasource/test_azure.py 2013-09-27 22:26:03 +0000
@@ -120,8 +120,7 @@
120120
121 mod = DataSourceAzure121 mod = DataSourceAzure
122122
123 if data.get('dsdevs'):123 self.apply_patches([(mod, 'list_possible_azure_ds_devs', dsdevs)])
124 self.apply_patches([(mod, 'list_possible_azure_ds_devs', dsdevs)])
125124
126 self.apply_patches([(mod, 'invoke_agent', _invoke_agent),125 self.apply_patches([(mod, 'invoke_agent', _invoke_agent),
127 (mod, 'write_files', _write_files),126 (mod, 'write_files', _write_files),
@@ -154,9 +153,12 @@
154153
155 def test_user_cfg_set_agent_command_plain(self):154 def test_user_cfg_set_agent_command_plain(self):
156 # set dscfg in via plaintext155 # set dscfg in via plaintext
157 cfg = {'agent_command': "my_command"}156 # we must have friendly-to-xml formatted plaintext in yaml_cfg
157 # not all plaintext is expected to work.
158 yaml_cfg = "{agent_command: my_command}\n"
159 cfg = yaml.safe_load(yaml_cfg)
158 odata = {'HostName': "myhost", 'UserName': "myuser",160 odata = {'HostName': "myhost", 'UserName': "myuser",
159 'dscfg': {'text': yaml.dump(cfg), 'encoding': 'plain'}}161 'dscfg': {'text': yaml_cfg, 'encoding': 'plain'}}
160 data = {'ovfcontent': construct_valid_ovf_env(data=odata)}162 data = {'ovfcontent': construct_valid_ovf_env(data=odata)}
161163
162 dsrc = self._get_ds(data)164 dsrc = self._get_ds(data)
@@ -290,11 +292,59 @@
290292
291 self.assertEqual(data.get('apply_hostname_bounce', "N/A"), "N/A")293 self.assertEqual(data.get('apply_hostname_bounce', "N/A"), "N/A")
292294
295 def test_default_ephemeral(self):
296 # make sure the ephemeral device works
297 odata = {}
298 data = {'ovfcontent': construct_valid_ovf_env(data=odata),
299 'sys_cfg': {}}
300
301 dsrc = self._get_ds(data)
302 ret = dsrc.get_data()
303 self.assertTrue(ret)
304 cfg = dsrc.get_config_obj()
305
306 self.assertEquals(dsrc.device_name_to_device("ephemeral0"),
307 "/dev/sdb")
308 assert 'disk_setup' in cfg
309 assert 'fs_setup' in cfg
310 self.assertIsInstance(cfg['disk_setup'], dict)
311 self.assertIsInstance(cfg['fs_setup'], list)
312
313 def test_provide_disk_aliases(self):
314 # Make sure that user can affect disk aliases
315 dscfg = {'disk_aliases': {'ephemeral0': '/dev/sdc'}}
316 odata = {'HostName': "myhost", 'UserName': "myuser",
317 'dscfg': {'text': base64.b64encode(yaml.dump(dscfg)),
318 'encoding': 'base64'}}
319 usercfg = {'disk_setup': {'/dev/sdc': {'something': '...'},
320 'ephemeral0': False}}
321 userdata = '#cloud-config' + yaml.dump(usercfg) + "\n"
322
323 ovfcontent = construct_valid_ovf_env(data=odata, userdata=userdata)
324 data = {'ovfcontent': ovfcontent, 'sys_cfg': {}}
325
326 dsrc = self._get_ds(data)
327 ret = dsrc.get_data()
328 self.assertTrue(ret)
329 cfg = dsrc.get_config_obj()
330 self.assertTrue(cfg)
331 self.assertEquals(dsrc.device_name_to_device("ephemeral0"),
332 "/dev/sdc")
333
334 def test_userdata_arrives(self):
335 userdata = "This is my user-data"
336 xml = construct_valid_ovf_env(data={}, userdata=userdata)
337 data = {'ovfcontent': xml}
338 dsrc = self._get_ds(data)
339 dsrc.get_data()
340
341 self.assertEqual(userdata, dsrc.userdata_raw)
342
293343
294class TestReadAzureOvf(MockerTestCase):344class TestReadAzureOvf(MockerTestCase):
295 def test_invalid_xml_raises_non_azure_ds(self):345 def test_invalid_xml_raises_non_azure_ds(self):
296 invalid_xml = "<foo>" + construct_valid_ovf_env(data={})346 invalid_xml = "<foo>" + construct_valid_ovf_env(data={})
297 self.assertRaises(DataSourceAzure.NonAzureDataSource,347 self.assertRaises(DataSourceAzure.BrokenAzureDataSource,
298 DataSourceAzure.read_azure_ovf, invalid_xml)348 DataSourceAzure.read_azure_ovf, invalid_xml)
299349
300 def test_load_with_pubkeys(self):350 def test_load_with_pubkeys(self):
301351
=== modified file 'tests/unittests/test_datasource/test_smartos.py'
--- tests/unittests/test_datasource/test_smartos.py 2013-09-19 22:49:50 +0000
+++ tests/unittests/test_datasource/test_smartos.py 2013-09-27 22:26:03 +0000
@@ -79,7 +79,6 @@
79 if self.last in self.mockdata:79 if self.last in self.mockdata:
80 if not self.mocked_out:80 if not self.mocked_out:
81 self.mocked_out = [x for x in self._format_out()]81 self.mocked_out = [x for x in self._format_out()]
82 print self.mocked_out
8382
84 if len(self.mocked_out) > self.count:83 if len(self.mocked_out) > self.count:
85 self.count += 184 self.count += 1
@@ -275,26 +274,25 @@
275 self.assertIsInstance(cfg['disk_setup'], dict)274 self.assertIsInstance(cfg['disk_setup'], dict)
276 self.assertIsInstance(cfg['fs_setup'], list)275 self.assertIsInstance(cfg['fs_setup'], list)
277276
278 def test_override_builtin_ds(self):277 def test_override_disk_aliases(self):
279 # Test to make sure that the built-in DS is overriden278 # Test to make sure that the built-in DS is overriden
280 data = {}279 builtin = DataSourceSmartOS.BUILTIN_DS_CONFIG
281 data['disk_setup'] = {'test_dev': {}}280
282 data['fs_setup'] = [{'label': 'test_dev'}]281 mydscfg = {'disk_aliases': {'FOO': '/dev/bar'}}
283 data['serial_device'] = '/dev/ttyS2'282
284 dsrc = self._get_ds(ds_cfg=data)283 # expect that these values are in builtin, or this is pointless
285 cfg = dsrc.get_config_obj()284 for k in mydscfg:
286285 self.assertIn(k, builtin)
286
287 dsrc = self._get_ds(ds_cfg=mydscfg)
287 ret = dsrc.get_data()288 ret = dsrc.get_data()
288 self.assertTrue(ret)289 self.assertTrue(ret)
289290
290 assert 'disk_setup' in cfg291 self.assertEqual(mydscfg['disk_aliases']['FOO'],
291 assert 'fs_setup' in cfg292 dsrc.ds_cfg['disk_aliases']['FOO'])
292 self.assertIsInstance(cfg['disk_setup'], dict)
293 self.assertIsInstance(cfg['fs_setup'], list)
294 assert 'test_dev' in cfg['disk_setup']
295 assert 'test_dev' in cfg['fs_setup'][0]['label']
296293
297 self.assertEquals(data['serial_device'], dsrc.seed)294 self.assertEqual(dsrc.device_name_to_device('FOO'),
295 mydscfg['disk_aliases']['FOO'])
298296
299297
300def apply_patches(patches):298def apply_patches(patches):