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
1=== modified file 'ChangeLog'
2--- ChangeLog 2013-09-26 12:47:14 +0000
3+++ ChangeLog 2013-09-27 22:26:03 +0000
4@@ -3,6 +3,7 @@
5 - small fix for OVF datasource for iso transport on non-iso9660 filesystem
6 - determine if upstart version is suitable for
7 'initctl reload-configuration' (LP: #1124384). If so, then invoke it.
8+ supports setting up instance-store disk with partition table and filesystem.
9 - add Azure datasource.
10 - add support for SuSE / SLES [Juerg Haefliger]
11 - add a trailing carriage return to chpasswd input, which reportedly
12
13=== modified file 'cloudinit/config/cc_disk_setup.py'
14--- cloudinit/config/cc_disk_setup.py 2013-09-19 22:49:50 +0000
15+++ cloudinit/config/cc_disk_setup.py 2013-09-27 22:26:03 +0000
16@@ -41,7 +41,8 @@
17 """
18 disk_setup = cfg.get("disk_setup")
19 if isinstance(disk_setup, dict):
20- log.info("Partitioning disks.")
21+ update_disk_setup_devices(disk_setup, cloud.device_name_to_device)
22+ log.debug("Partitioning disks: %s", str(disk_setup))
23 for disk, definition in disk_setup.items():
24 if not isinstance(definition, dict):
25 log.warn("Invalid disk definition for %s" % disk)
26@@ -51,13 +52,14 @@
27 log.debug("Creating new partition table/disk")
28 util.log_time(logfunc=LOG.debug,
29 msg="Creating partition on %s" % disk,
30- func=mkpart, args=(disk, cloud, definition))
31+ func=mkpart, args=(disk, definition))
32 except Exception as e:
33 util.logexc(LOG, "Failed partitioning operation\n%s" % e)
34
35 fs_setup = cfg.get("fs_setup")
36 if isinstance(fs_setup, list):
37- log.info("Creating file systems.")
38+ log.debug("setting up filesystems: %s", str(fs_setup))
39+ update_fs_setup_devices(fs_setup, cloud.device_name_to_device)
40 for definition in fs_setup:
41 if not isinstance(definition, dict):
42 log.warn("Invalid file system definition: %s" % definition)
43@@ -68,31 +70,48 @@
44 device = definition.get('device')
45 util.log_time(logfunc=LOG.debug,
46 msg="Creating fs for %s" % device,
47- func=mkfs, args=(cloud, definition))
48+ func=mkfs, args=(definition,))
49 except Exception as e:
50 util.logexc(LOG, "Failed during filesystem operation\n%s" % e)
51
52
53-def is_default_device(name, cloud, fallback=None):
54- """
55- Ask the cloud datasource if the 'name' maps to a default
56- device. If so, return that value, otherwise return 'name', or
57- fallback if so defined.
58- """
59-
60- _dev = None
61- try:
62- _dev = cloud.device_name_to_device(name)
63- except Exception as e:
64- util.logexc(LOG, "Failed to find mapping for %s" % e)
65-
66- if _dev:
67- return _dev
68-
69- if fallback:
70- return fallback
71-
72- return name
73+def update_disk_setup_devices(disk_setup, tformer):
74+ # update 'disk_setup' dictionary anywhere were a device may occur
75+ # update it with the response from 'tformer'
76+ for origname in disk_setup.keys():
77+ transformed = tformer(origname)
78+ if transformed is None or transformed == origname:
79+ continue
80+ if transformed in disk_setup:
81+ LOG.info("Replacing %s in disk_setup for translation of %s",
82+ origname, transformed)
83+ del disk_setup[transformed]
84+
85+ disk_setup[transformed] = disk_setup[origname]
86+ disk_setup[transformed]['_origname'] = origname
87+ del disk_setup[origname]
88+ LOG.debug("updated disk_setup device entry '%s' to '%s'",
89+ origname, transformed)
90+
91+
92+def update_fs_setup_devices(disk_setup, tformer):
93+ # update 'fs_setup' dictionary anywhere were a device may occur
94+ # update it with the response from 'tformer'
95+ for definition in disk_setup:
96+ if not isinstance(definition, dict):
97+ LOG.warn("entry in disk_setup not a dict: %s", definition)
98+ continue
99+
100+ origname = definition.get('device')
101+ if origname is None:
102+ continue
103+
104+ transformed = tformer(origname)
105+ if transformed is None or transformed == origname:
106+ continue
107+
108+ definition['_origname'] = origname
109+ definition['device'] = transformed
110
111
112 def value_splitter(values, start=None):
113@@ -195,6 +214,10 @@
114
115 Note: This works with GPT partition tables!
116 """
117+ # label of None is same as no label
118+ if label is None:
119+ label = ""
120+
121 if not valid_targets:
122 valid_targets = ['disk', 'part']
123
124@@ -219,8 +242,8 @@
125 for key, value in value_splitter(part):
126 d[key.lower()] = value
127
128- if d['fstype'] == fs_type and \
129- ((label_match and d['label'] == label) or not label_match):
130+ if (d['fstype'] == fs_type and
131+ ((label_match and d['label'] == label) or not label_match)):
132 # If we find a matching device, we return that
133 return ('/dev/%s' % d['name'], True)
134
135@@ -397,8 +420,8 @@
136 # Create a single partition
137 return "0,"
138
139- if (len(layout) == 0 and isinstance(layout, list)) or \
140- not isinstance(layout, list):
141+ if ((len(layout) == 0 and isinstance(layout, list)) or
142+ not isinstance(layout, list)):
143 raise Exception("Partition layout is invalid")
144
145 last_part_num = len(layout)
146@@ -414,8 +437,7 @@
147
148 if isinstance(part, list):
149 if len(part) != 2:
150- raise Exception("Partition was incorrectly defined: %s" % \
151- part)
152+ raise Exception("Partition was incorrectly defined: %s" % part)
153 percent, part_type = part
154
155 part_size = int((float(size) * (float(percent) / 100)) / 1024)
156@@ -488,12 +510,11 @@
157 return get_dyn_func("exec_mkpart_%s", table_type, device, layout)
158
159
160-def mkpart(device, cloud, definition):
161+def mkpart(device, definition):
162 """
163 Creates the partition table.
164
165 Parameters:
166- cloud: the cloud object
167 definition: dictionary describing how to create the partition.
168
169 The following are supported values in the dict:
170@@ -508,29 +529,18 @@
171 overwrite = definition.get('overwrite', False)
172 layout = definition.get('layout', False)
173 table_type = definition.get('table_type', 'mbr')
174- _device = is_default_device(device, cloud)
175
176 # Check if the default device is a partition or not
177 LOG.debug("Checking against default devices")
178- if _device and (_device != device):
179- if not is_device_valid(_device):
180- _device = _device[:-1]
181-
182- if not is_device_valid(_device):
183- raise Exception("Unable to find backing block device for %s" % \
184- device)
185- else:
186- LOG.debug("Mapped %s to physical device %s" % (device, _device))
187- device = _device
188
189 if (isinstance(layout, bool) and not layout) or not layout:
190 LOG.debug("Device is not to be partitioned, skipping")
191 return # Device is not to be partitioned
192
193 # This prevents you from overwriting the device
194- LOG.debug("Checking if device %s is a valid device" % device)
195+ LOG.debug("Checking if device %s is a valid device", device)
196 if not is_device_valid(device):
197- raise Exception("Device %s is not a disk device!" % device)
198+ raise Exception("Device %s is not a disk device!", device)
199
200 LOG.debug("Checking if device layout matches")
201 if check_partition_layout(table_type, device, layout):
202@@ -549,13 +559,13 @@
203 part_definition = get_partition_layout(table_type, device_size, layout)
204 LOG.debug(" Layout is: %s" % part_definition)
205
206- LOG.debug("Creating partition table on %s" % device)
207+ LOG.debug("Creating partition table on %s", device)
208 exec_mkpart(table_type, device, part_definition)
209
210- LOG.debug("Partition table created for %s" % device)
211-
212-
213-def mkfs(cloud, fs_cfg):
214+ LOG.debug("Partition table created for %s", device)
215+
216+
217+def mkfs(fs_cfg):
218 """
219 Create a file system on the device.
220
221@@ -576,54 +586,45 @@
222
223 When 'cmd' is provided then no other parameter is required.
224 """
225- fs_cfg['partition'] = 'any'
226 label = fs_cfg.get('label')
227 device = fs_cfg.get('device')
228- partition = str(fs_cfg.get('partition'))
229+ partition = str(fs_cfg.get('partition', 'any'))
230 fs_type = fs_cfg.get('filesystem')
231 fs_cmd = fs_cfg.get('cmd', [])
232 fs_opts = fs_cfg.get('extra_opts', [])
233 overwrite = fs_cfg.get('overwrite', False)
234
235 # This allows you to define the default ephemeral or swap
236- LOG.debug("Checking %s against default devices" % device)
237- _device = is_default_device(label, cloud, fallback=device)
238- if _device and (_device != device):
239- if not is_device_valid(_device):
240- raise Exception("Unable to find backing block device for %s" % \
241- device)
242- else:
243- LOG.debug("Mapped %s to physical device %s" % (device, _device))
244- device = _device
245+ LOG.debug("Checking %s against default devices", device)
246
247 if not partition or partition.isdigit():
248 # Handle manual definition of partition
249 if partition.isdigit():
250 device = "%s%s" % (device, partition)
251- LOG.debug("Manual request of partition %s for %s" % (
252- partition, device))
253+ LOG.debug("Manual request of partition %s for %s",
254+ partition, device)
255
256 # Check to see if the fs already exists
257- LOG.debug("Checking device %s" % device)
258+ LOG.debug("Checking device %s", device)
259 check_label, check_fstype, _ = check_fs(device)
260- LOG.debug("Device %s has %s %s" % (device, check_label, check_fstype))
261+ LOG.debug("Device %s has %s %s", device, check_label, check_fstype)
262
263 if check_label == label and check_fstype == fs_type:
264- LOG.debug("Existing file system found at %s" % device)
265+ LOG.debug("Existing file system found at %s", device)
266
267 if not overwrite:
268- LOG.warn("Device %s has required file system" % device)
269+ LOG.debug("Device %s has required file system", device)
270 return
271 else:
272- LOG.warn("Destroying filesystem on %s" % device)
273+ LOG.warn("Destroying filesystem on %s", device)
274
275 else:
276- LOG.debug("Device %s is cleared for formating" % device)
277+ LOG.debug("Device %s is cleared for formating", device)
278
279 elif partition and str(partition).lower() in ('auto', 'any'):
280 # For auto devices, we match if the filesystem does exist
281 odevice = device
282- LOG.debug("Identifying device to create %s filesytem on" % label)
283+ LOG.debug("Identifying device to create %s filesytem on", label)
284
285 # any mean pick the first match on the device with matching fs_type
286 label_match = True
287@@ -632,33 +633,32 @@
288
289 device, reuse = find_device_node(device, fs_type=fs_type, label=label,
290 label_match=label_match)
291- LOG.debug("Automatic device for %s identified as %s" % (
292- odevice, device))
293+ LOG.debug("Automatic device for %s identified as %s", odevice, device)
294
295 if reuse:
296 LOG.debug("Found filesystem match, skipping formating.")
297 return
298
299 if not device:
300- LOG.debug("No device aviable that matches request.")
301- LOG.debug("Skipping fs creation for %s" % fs_cfg)
302+ LOG.debug("No device aviable that matches request. "
303+ "Skipping fs creation for %s", fs_cfg)
304 return
305
306 else:
307 LOG.debug("Error in device identification handling.")
308 return
309
310- LOG.debug("File system %s will be created on %s" % (label, device))
311+ LOG.debug("File system %s will be created on %s", label, device)
312
313 # Make sure the device is defined
314 if not device:
315- LOG.critical("Device is not known: %s" % fs_cfg)
316+ LOG.warn("Device is not known: %s", device)
317 return
318
319 # Check that we can create the FS
320- if not label or not fs_type:
321- LOG.debug("Command to create filesystem %s is bad. Skipping." % \
322- label)
323+ if not (fs_type or fs_cmd):
324+ raise Exception("No way to create filesystem '%s'. fs_type or fs_cmd "
325+ "must be set.", label)
326
327 # Create the commands
328 if fs_cmd:
329@@ -673,7 +673,8 @@
330 mkfs_cmd = util.which("mk%s" % fs_type)
331
332 if not mkfs_cmd:
333- LOG.critical("Unable to locate command to create filesystem.")
334+ LOG.warn("Cannot create fstype '%s'. No mkfs.%s command", fs_type,
335+ fs_type)
336 return
337
338 fs_cmd = [mkfs_cmd, device]
339@@ -685,8 +686,8 @@
340 if fs_opts:
341 fs_cmd.extend(fs_opts)
342
343- LOG.debug("Creating file system %s on %s" % (label, device))
344- LOG.debug(" Using cmd: %s" % "".join(fs_cmd))
345+ LOG.debug("Creating file system %s on %s", label, device)
346+ LOG.debug(" Using cmd: %s", "".join(fs_cmd))
347 try:
348 util.subp(fs_cmd)
349 except Exception as e:
350
351=== modified file 'cloudinit/sources/DataSourceAzure.py'
352--- cloudinit/sources/DataSourceAzure.py 2013-09-09 23:44:50 +0000
353+++ cloudinit/sources/DataSourceAzure.py 2013-09-27 22:26:03 +0000
354@@ -44,8 +44,20 @@
355 'policy': True,
356 'command': BOUNCE_COMMAND,
357 'hostname_command': 'hostname',
358- }
359-}
360+ },
361+ 'disk_aliases': {'ephemeral0': '/dev/sdb'},
362+}
363+
364+BUILTIN_CLOUD_CONFIG = {
365+ 'disk_setup': {
366+ 'ephemeral0': {'table_type': 'mbr',
367+ 'layout': True,
368+ 'overwrite': False}
369+ },
370+ 'fs_setup': [{'filesystem': 'ext4', 'device': 'ephemeral0',
371+ 'partition': 'auto'}],
372+}
373+
374 DS_CFG_PATH = ['datasource', DS_NAME]
375
376
377@@ -94,7 +106,7 @@
378 (md, self.userdata_raw, cfg, files) = ret
379 self.seed = cdev
380 self.metadata = util.mergemanydict([md, DEFAULT_METADATA])
381- self.cfg = cfg
382+ self.cfg = util.mergemanydict([cfg, BUILTIN_CLOUD_CONFIG])
383 found = cdev
384
385 LOG.debug("found datasource in %s", cdev)
386@@ -112,8 +124,8 @@
387 self.metadata['random_seed'] = seed
388
389 # now update ds_cfg to reflect contents pass in config
390- usercfg = util.get_cfg_by_path(self.cfg, DS_CFG_PATH, {})
391- self.ds_cfg = util.mergemanydict([usercfg, self.ds_cfg])
392+ user_ds_cfg = util.get_cfg_by_path(self.cfg, DS_CFG_PATH, {})
393+ self.ds_cfg = util.mergemanydict([user_ds_cfg, self.ds_cfg])
394 mycfg = self.ds_cfg
395
396 # walinux agent writes files world readable, but expects
397@@ -161,9 +173,11 @@
398 pubkeys = pubkeys_from_crt_files(fp_files)
399
400 self.metadata['public-keys'] = pubkeys
401-
402 return True
403
404+ def device_name_to_device(self, name):
405+ return self.ds_cfg['disk_aliases'].get(name)
406+
407 def get_config_obj(self):
408 return self.cfg
409
410@@ -349,7 +363,7 @@
411 try:
412 dom = minidom.parseString(contents)
413 except Exception as e:
414- raise NonAzureDataSource("invalid xml: %s" % e)
415+ raise BrokenAzureDataSource("invalid xml: %s" % e)
416
417 results = find_child(dom.documentElement,
418 lambda n: n.localName == "ProvisioningSection")
419
420=== modified file 'cloudinit/sources/DataSourceSmartOS.py'
421--- cloudinit/sources/DataSourceSmartOS.py 2013-09-19 22:49:50 +0000
422+++ cloudinit/sources/DataSourceSmartOS.py 2013-09-27 22:26:03 +0000
423@@ -72,14 +72,17 @@
424 'iptables_disable'],
425 'base64_keys': [],
426 'base64_all': False,
427- 'ephemeral_disk': '/dev/vdb',
428+ 'disk_aliases': {'ephemeral0': '/dev/vdb'},
429+}
430+
431+BUILTIN_CLOUD_CONFIG = {
432 'disk_setup': {
433 'ephemeral0': {'table_type': 'mbr',
434- 'layout': True,
435+ 'layout': False,
436 'overwrite': False}
437 },
438 'fs_setup': [{'label': 'ephemeral0', 'filesystem': 'ext3',
439- 'device': '/dev/xvdb', 'partition': 'auto'}],
440+ 'device': 'ephemeral0', 'partition': 'auto'}],
441 }
442
443
444@@ -94,9 +97,7 @@
445 BUILTIN_DS_CONFIG])
446
447 self.metadata = {}
448- self.cfg = {}
449- self.cfg['disk_setup'] = self.ds_cfg.get('disk_setup')
450- self.cfg['fs_setup'] = self.ds_cfg.get('fs_setup')
451+ self.cfg = BUILTIN_CLOUD_CONFIG
452
453 self.seed = self.ds_cfg.get("serial_device")
454 self.seed_timeout = self.ds_cfg.get("serial_timeout")
455@@ -154,8 +155,7 @@
456 return True
457
458 def device_name_to_device(self, name):
459- if 'ephemeral0' in name:
460- return self.ds_cfg['ephemeral_disk']
461+ return self.ds_cfg['disk_aliases'].get(name)
462
463 def get_config_obj(self):
464 return self.cfg
465
466=== modified file 'doc/examples/cloud-config-disk-setup.txt'
467--- doc/examples/cloud-config-disk-setup.txt 2013-09-19 22:49:50 +0000
468+++ doc/examples/cloud-config-disk-setup.txt 2013-09-27 22:26:03 +0000
469@@ -19,36 +19,36 @@
470
471 Default disk definitions for Windows Azure
472 ------------------------------------------
473-(Not implemented yet due to conflict with WALinuxAgent in Ubuntu)
474
475+device_aliases: {'ephemeral0': '/dev/sdb'}
476 disk_setup:
477- /dev/sdb:
478+ ephemeral0:
479 type: mbr
480 layout: True
481 overwrite: False
482
483 fs_setup:
484 - label: ephemeral0
485- filesystem: ext3
486+ filesystem: ext4
487 device: ephemeral0
488- partition: any
489+ partition: auto
490
491
492 Default disk definitions for SmartOS
493 ------------------------------------
494
495-ephemeral_disk: /dev/vdb
496+device_aliases: {'ephemeral0': '/dev/sdb'}
497 disk_setup:
498- /dev/vdb:
499+ ephemeral0:
500 type: mbr
501- layout: True
502+ layout: False
503 overwrite: False
504
505 fs_setup:
506 - label: ephemeral0
507 filesystem: ext3
508- device: /dev/vdb
509- partition: 1
510+ device: ephemeral0
511+ partition: auto
512
513 Cavaut for SmartOS: if ephemeral disk is not defined, then the disk will
514 not be automatically added to the mounts.
515@@ -188,13 +188,43 @@
516 of the ephemeral storage layer.
517
518 <PART_VALUE>: The valid options are:
519- "auto": auto is a special in the sense that you are telling cloud-init
520- not to care whether there is a partition or not. Auto will put the
521- first partition that does not contain a file system already. In
522- the absence of a partition table, it will put it directly on the
523- disk.
524-
525- "none": Put the partition directly on the disk.
526+ "auto|any": tell cloud-init not to care whether there is a partition
527+ or not. Auto will use the first partition that does not contain a
528+ file system already. In the absence of a partition table, it will
529+ put it directly on the disk.
530+
531+ "auto": If a file system that matches the specification in terms of
532+ label, type and device, then cloud-init will skip the creation of
533+ the file system.
534+
535+ "any": If a file system that matches the file system type and device,
536+ then cloud-init will skip the creation of the file system.
537+
538+ Devices are selected based on first-detected, starting with partitions
539+ and then the raw disk. Consider the following:
540+ NAME FSTYPE LABEL
541+ xvdb
542+ ├─xvdb1 ext4
543+ ├─xvdb2
544+ ├─xvdb3 btrfs test
545+ └─xvdb4 ext4 test
546+
547+ If you ask for 'auto', label of 'test, and file system of 'ext4'
548+ then cloud-init will select the 2nd partition, even though there
549+ is a partition match at the 4th partition.
550+
551+ If you ask for 'any' and a label of 'test', then cloud-init will
552+ select the 1st partition.
553+
554+ If you ask for 'auto' and don't define label, then cloud-init will
555+ select the 1st partition.
556+
557+ In general, if you have a specific partition configuration in mind,
558+ you should define either the device or the partition number. 'auto'
559+ and 'any' are specifically intended for formating ephemeral storage or
560+ for simple schemes.
561+
562+ "none": Put the file system directly on the device.
563
564 <NUM>: where NUM is the actual partition number.
565
566
567=== modified file 'doc/sources/smartos/README.rst'
568--- doc/sources/smartos/README.rst 2013-09-19 22:49:50 +0000
569+++ doc/sources/smartos/README.rst 2013-09-27 22:26:03 +0000
570@@ -73,15 +73,21 @@
571 (i.e. /etc/cloud/cloud.cfg.d) that sets which values should not be
572 base64 decoded.
573
574-ephemeral_disk:
575+disk_aliases and ephemeral disk:
576 ---------------
577-
578-In order to instruct Cloud-init which disk to auto-mount. By default,
579-SmartOS only supports a single ephemeral disk.
580-
581-The default SmartOS configuration will prepare the ephemeral disk and format
582-it for you. SmartOS does not, by default, prepare the ephemeral disk for you.
583-
584-If you change ephemeral_disk, you should also consider changing
585-the default disk formatting parameters. See
586-doc/examples/cloud-config-disk-setup.txt for information on using this.
587+By default, SmartOS only supports a single ephemeral disk. That disk is
588+completely empty (un-partitioned with no filesystem).
589+
590+The SmartOS datasource has built-in cloud-config which instructs the
591+'disk_setup' module to partition and format the ephemeral disk.
592+
593+You can control the disk_setup then in 2 ways:
594+ 1. through the datasource config, you can change the 'alias' of
595+ ephermeral0 to reference another device. The default is:
596+ 'disk_aliases': {'ephemeral0': '/dev/vdb'},
597+ Which means anywhere disk_setup sees a device named 'ephemeral0'
598+ then /dev/vdb will be substituted.
599+ 2. you can provide disk_setup or fs_setup data in user-data to overwrite
600+ the datasource's built-in values.
601+
602+See doc/examples/cloud-config-disk-setup.txt for information on disk_setup.
603
604=== modified file 'tests/unittests/test_datasource/test_azure.py'
605--- tests/unittests/test_datasource/test_azure.py 2013-08-15 17:16:01 +0000
606+++ tests/unittests/test_datasource/test_azure.py 2013-09-27 22:26:03 +0000
607@@ -120,8 +120,7 @@
608
609 mod = DataSourceAzure
610
611- if data.get('dsdevs'):
612- self.apply_patches([(mod, 'list_possible_azure_ds_devs', dsdevs)])
613+ self.apply_patches([(mod, 'list_possible_azure_ds_devs', dsdevs)])
614
615 self.apply_patches([(mod, 'invoke_agent', _invoke_agent),
616 (mod, 'write_files', _write_files),
617@@ -154,9 +153,12 @@
618
619 def test_user_cfg_set_agent_command_plain(self):
620 # set dscfg in via plaintext
621- cfg = {'agent_command': "my_command"}
622+ # we must have friendly-to-xml formatted plaintext in yaml_cfg
623+ # not all plaintext is expected to work.
624+ yaml_cfg = "{agent_command: my_command}\n"
625+ cfg = yaml.safe_load(yaml_cfg)
626 odata = {'HostName': "myhost", 'UserName': "myuser",
627- 'dscfg': {'text': yaml.dump(cfg), 'encoding': 'plain'}}
628+ 'dscfg': {'text': yaml_cfg, 'encoding': 'plain'}}
629 data = {'ovfcontent': construct_valid_ovf_env(data=odata)}
630
631 dsrc = self._get_ds(data)
632@@ -290,11 +292,59 @@
633
634 self.assertEqual(data.get('apply_hostname_bounce', "N/A"), "N/A")
635
636+ def test_default_ephemeral(self):
637+ # make sure the ephemeral device works
638+ odata = {}
639+ data = {'ovfcontent': construct_valid_ovf_env(data=odata),
640+ 'sys_cfg': {}}
641+
642+ dsrc = self._get_ds(data)
643+ ret = dsrc.get_data()
644+ self.assertTrue(ret)
645+ cfg = dsrc.get_config_obj()
646+
647+ self.assertEquals(dsrc.device_name_to_device("ephemeral0"),
648+ "/dev/sdb")
649+ assert 'disk_setup' in cfg
650+ assert 'fs_setup' in cfg
651+ self.assertIsInstance(cfg['disk_setup'], dict)
652+ self.assertIsInstance(cfg['fs_setup'], list)
653+
654+ def test_provide_disk_aliases(self):
655+ # Make sure that user can affect disk aliases
656+ dscfg = {'disk_aliases': {'ephemeral0': '/dev/sdc'}}
657+ odata = {'HostName': "myhost", 'UserName': "myuser",
658+ 'dscfg': {'text': base64.b64encode(yaml.dump(dscfg)),
659+ 'encoding': 'base64'}}
660+ usercfg = {'disk_setup': {'/dev/sdc': {'something': '...'},
661+ 'ephemeral0': False}}
662+ userdata = '#cloud-config' + yaml.dump(usercfg) + "\n"
663+
664+ ovfcontent = construct_valid_ovf_env(data=odata, userdata=userdata)
665+ data = {'ovfcontent': ovfcontent, 'sys_cfg': {}}
666+
667+ dsrc = self._get_ds(data)
668+ ret = dsrc.get_data()
669+ self.assertTrue(ret)
670+ cfg = dsrc.get_config_obj()
671+ self.assertTrue(cfg)
672+ self.assertEquals(dsrc.device_name_to_device("ephemeral0"),
673+ "/dev/sdc")
674+
675+ def test_userdata_arrives(self):
676+ userdata = "This is my user-data"
677+ xml = construct_valid_ovf_env(data={}, userdata=userdata)
678+ data = {'ovfcontent': xml}
679+ dsrc = self._get_ds(data)
680+ dsrc.get_data()
681+
682+ self.assertEqual(userdata, dsrc.userdata_raw)
683+
684
685 class TestReadAzureOvf(MockerTestCase):
686 def test_invalid_xml_raises_non_azure_ds(self):
687 invalid_xml = "<foo>" + construct_valid_ovf_env(data={})
688- self.assertRaises(DataSourceAzure.NonAzureDataSource,
689+ self.assertRaises(DataSourceAzure.BrokenAzureDataSource,
690 DataSourceAzure.read_azure_ovf, invalid_xml)
691
692 def test_load_with_pubkeys(self):
693
694=== modified file 'tests/unittests/test_datasource/test_smartos.py'
695--- tests/unittests/test_datasource/test_smartos.py 2013-09-19 22:49:50 +0000
696+++ tests/unittests/test_datasource/test_smartos.py 2013-09-27 22:26:03 +0000
697@@ -79,7 +79,6 @@
698 if self.last in self.mockdata:
699 if not self.mocked_out:
700 self.mocked_out = [x for x in self._format_out()]
701- print self.mocked_out
702
703 if len(self.mocked_out) > self.count:
704 self.count += 1
705@@ -275,26 +274,25 @@
706 self.assertIsInstance(cfg['disk_setup'], dict)
707 self.assertIsInstance(cfg['fs_setup'], list)
708
709- def test_override_builtin_ds(self):
710+ def test_override_disk_aliases(self):
711 # Test to make sure that the built-in DS is overriden
712- data = {}
713- data['disk_setup'] = {'test_dev': {}}
714- data['fs_setup'] = [{'label': 'test_dev'}]
715- data['serial_device'] = '/dev/ttyS2'
716- dsrc = self._get_ds(ds_cfg=data)
717- cfg = dsrc.get_config_obj()
718-
719+ builtin = DataSourceSmartOS.BUILTIN_DS_CONFIG
720+
721+ mydscfg = {'disk_aliases': {'FOO': '/dev/bar'}}
722+
723+ # expect that these values are in builtin, or this is pointless
724+ for k in mydscfg:
725+ self.assertIn(k, builtin)
726+
727+ dsrc = self._get_ds(ds_cfg=mydscfg)
728 ret = dsrc.get_data()
729 self.assertTrue(ret)
730
731- assert 'disk_setup' in cfg
732- assert 'fs_setup' in cfg
733- self.assertIsInstance(cfg['disk_setup'], dict)
734- self.assertIsInstance(cfg['fs_setup'], list)
735- assert 'test_dev' in cfg['disk_setup']
736- assert 'test_dev' in cfg['fs_setup'][0]['label']
737+ self.assertEqual(mydscfg['disk_aliases']['FOO'],
738+ dsrc.ds_cfg['disk_aliases']['FOO'])
739
740- self.assertEquals(data['serial_device'], dsrc.seed)
741+ self.assertEqual(dsrc.device_name_to_device('FOO'),
742+ mydscfg['disk_aliases']['FOO'])
743
744
745 def apply_patches(patches):