diff -Nru cloud-init-0.6.3/debian/changelog cloud-init-0.6.3/debian/changelog --- cloud-init-0.6.3/debian/changelog 2015-07-17 17:22:15.000000000 +0000 +++ cloud-init-0.6.3/debian/changelog 2015-08-14 17:21:22.000000000 +0000 @@ -1,3 +1,16 @@ +cloud-init (0.6.3-0ubuntu1.19) precise; urgency=medium + + * debian/patches/lp-1411582-azure-udev-ephemeral-disks.patch: + - Use udev rules to discover ephemeral disk locations rather than + hard-coded device names (LP: #1411582). + * debian/patches/lp-1470880-fix-gce-az-determination.patch: + - Correctly parse GCE's availability zones (LP: #1470880). + * d/patches/lp-1470890-include-regions-in-dynamic-mirror-discovery.patch: + - Make %(region)s a valid substitution in mirror discovery + (LP: #1470890). + + -- Daniel Watkins Fri, 14 Aug 2015 14:38:48 +0100 + cloud-init (0.6.3-0ubuntu1.18) precise; urgency=medium * d/patches/lp-1456684-eu-central-1.patch: diff -Nru cloud-init-0.6.3/debian/patches/lp-1411582-azure-udev-ephemeral-disks.patch cloud-init-0.6.3/debian/patches/lp-1411582-azure-udev-ephemeral-disks.patch --- cloud-init-0.6.3/debian/patches/lp-1411582-azure-udev-ephemeral-disks.patch 1970-01-01 00:00:00.000000000 +0000 +++ cloud-init-0.6.3/debian/patches/lp-1411582-azure-udev-ephemeral-disks.patch 2015-08-14 17:21:22.000000000 +0000 @@ -0,0 +1,406 @@ +Description: Use udev rules to find Azure ephemeral disks +Author: Daniel Watkins +Origin: upstream, http://bazaar.launchpad.net/~cloud-init-dev/cloud-init/trunk/revision/1127 +Bug: https://bugs.launchpad.net/cloud-init/+bug/1411582 +--- a/cloudinit/CloudConfig/cc_disk_setup.py ++++ b/cloudinit/CloudConfig/cc_disk_setup.py +@@ -579,6 +579,8 @@ + table_type: Which partition table to use, defaults to MBR + device: the device to work on. + """ ++ # ensure that we get a real device rather than a symbolic link ++ device = os.path.realpath(device) + + LOG.debug("Checking values for %s definition" % device) + overwrite = definition.get('overwrite', False) +@@ -676,6 +678,9 @@ + fs_replace = fs_cfg.get('replace_fs', False) + overwrite = fs_cfg.get('overwrite', False) + ++ # ensure that we get a real device rather than a symbolic link ++ device = os.path.realpath(device) ++ + # This allows you to define the default ephemeral or swap + LOG.debug("Checking %s against default devices", device) + +--- a/cloudinit/CloudConfig/cc_mounts.py ++++ b/cloudinit/CloudConfig/cc_mounts.py +@@ -29,15 +29,15 @@ + from cloudinit import util + + # Shortname matches 'sda', 'sda1', 'xvda', 'hda', 'sdb', xvdb, vda, vdd1, sr0 +-SHORTNAME_FILTER = r"^([x]{0,1}[shv]d[a-z][0-9]*|sr[0-9]+)$" +-SHORTNAME = re.compile(SHORTNAME_FILTER) ++DEVICE_NAME_FILTER = r"^([x]{0,1}[shv]d[a-z][0-9]*|sr[0-9]+)$" ++DEVICE_NAME_RE = re.compile(DEVICE_NAME_FILTER) + WS = re.compile("[%s]+" % (whitespace)) + FSTAB_PATH = "/etc/fstab" + + LOG = logging.getLogger(__name__) + + +-def is_mdname(name): ++def is_meta_device_name(name): + # return true if this is a metadata service name + if name in ["ami", "root", "swap"]: + return True +@@ -49,6 +49,25 @@ + return False + + ++def _get_nth_partition_for_device(device_path, partition_number): ++ potential_suffixes = [str(partition_number), 'p%s' % (partition_number,), ++ '-part%s' % (partition_number,)] ++ for suffix in potential_suffixes: ++ potential_partition_device = '%s%s' % (device_path, suffix) ++ if os.path.exists(potential_partition_device): ++ return potential_partition_device ++ return None ++ ++ ++def _is_block_device(device_path, partition_path=None): ++ device_name = os.path.realpath(device_path).split('/')[-1] ++ sys_path = os.path.join('/sys/block/', device_name) ++ if partition_path is not None: ++ sys_path = os.path.join( ++ sys_path, os.path.realpath(partition_path).split('/')[-1]) ++ return os.path.exists(sys_path) ++ ++ + def sanitize_devname(startname, transformer, log): + log.debug("Attempting to determine the real name of %s", startname) + +@@ -59,21 +78,34 @@ + devname = "ephemeral0" + log.debug("Adjusted mount option from ephemeral to ephemeral0") + +- (blockdev, part) = futil.expand_dotted_devname(devname) ++ device_path, partition_number = futil.expand_dotted_devname(devname) + +- if is_mdname(blockdev): +- orig = blockdev +- blockdev = transformer(blockdev) +- if not blockdev: ++ if is_meta_device_name(device_path): ++ orig = device_path ++ device_path = transformer(device_path) ++ if not device_path: + return None +- if not blockdev.startswith("/"): +- blockdev = "/dev/%s" % blockdev +- log.debug("Mapped metadata name %s to %s", orig, blockdev) ++ if not device_path.startswith("/"): ++ device_path = "/dev/%s" % (device_path,) ++ log.debug("Mapped metadata name %s to %s", orig, device_path) + else: +- if SHORTNAME.match(startname): +- blockdev = "/dev/%s" % blockdev ++ if DEVICE_NAME_RE.match(startname): ++ device_path = "/dev/%s" % (device_path,) + +- return devnode_for_dev_part(blockdev, part) ++ partition_path = None ++ if partition_number is None: ++ partition_path = _get_nth_partition_for_device(device_path, 1) ++ else: ++ partition_path = _get_nth_partition_for_device(device_path, ++ partition_number) ++ if partition_path is None: ++ return None ++ ++ if _is_block_device(device_path, partition_path): ++ if partition_path is not None: ++ return partition_path ++ return device_path ++ return None + + + def handle(_name, cfg, cloud, log, _args): +@@ -211,50 +243,3 @@ + futil.subp(("mount", "-a")) + except: + log.warn("Activating mounts via 'mount -a' failed") +- +- +-def devnode_for_dev_part(device, partition): +- """ +- Find the name of the partition. While this might seem rather +- straight forward, its not since some devices are '' +- while others are 'p'. For example, /dev/xvda3 on EC2 +- will present as /dev/xvda3p1 for the first partition since /dev/xvda3 is +- a block device. +- """ +- if not os.path.exists(device): +- return None +- +- short_name = os.path.basename(device) +- sys_path = "/sys/block/%s" % short_name +- +- if not os.path.exists(sys_path): +- LOG.debug("did not find entry for %s in /sys/block", short_name) +- return None +- +- sys_long_path = sys_path + "/" + short_name +- +- if partition is not None: +- partition = str(partition) +- +- if partition is None: +- valid_mappings = [sys_long_path + "1", +- sys_long_path + "p1"] +- elif partition != "0": +- valid_mappings = [sys_long_path + "%s" % partition, +- sys_long_path + "p%s" % partition] +- else: +- valid_mappings = [] +- +- for cdisk in valid_mappings: +- if not os.path.exists(cdisk): +- continue +- +- dev_path = "/dev/%s" % os.path.basename(cdisk) +- if os.path.exists(dev_path): +- return dev_path +- +- if partition is None or partition == "0": +- return device +- +- LOG.debug("Did not fine partition %s for device %s", partition, device) +- return None +--- a/cloudinit/DataSourceAzure.py ++++ b/cloudinit/DataSourceAzure.py +@@ -207,7 +207,7 @@ + + self.metadata['public-keys'] = pubkeys + +- found_ephemeral = find_ephemeral_disk() ++ found_ephemeral = find_fabric_formatted_ephemeral_disk() + if found_ephemeral: + self.ds_cfg['disk_aliases']['ephemeral0'] = found_ephemeral + LOG.debug("using detected ephemeral0 of %s", found_ephemeral) +@@ -227,30 +227,33 @@ + def count_files(mp): + return len(fnmatch.filter(os.listdir(mp), '*[!cdrom]*')) + +-def find_ephemeral_part(): ++def find_fabric_formatted_ephemeral_part(): + """ +- Locate the default ephmeral0.1 device. This will be the first device +- that has a LABEL of DEF_EPHEMERAL_LABEL and is a NTFS device. If Azure +- gets more ephemeral devices, this logic will only identify the first +- such device. +- """ +- c_label_devs = util.find_devs_with("LABEL=%s" % DEF_EPHEMERAL_LABEL) +- c_fstype_devs = util.find_devs_with("TYPE=ntfs") +- for dev in c_label_devs: +- if dev in c_fstype_devs: +- return dev ++ Locate the first fabric formatted ephemeral device. ++ """ ++ potential_locations = ['/dev/disk/cloud/azure_resource-part1', ++ '/dev/disk/azure/resource-part1'] ++ device_location = None ++ for potential_location in potential_locations: ++ if os.path.exists(potential_location): ++ device_location = potential_location ++ break ++ if device_location is None: ++ return None ++ ntfs_devices = util.find_devs_with("TYPE=ntfs") ++ real_device = os.path.realpath(device_location) ++ if real_device in ntfs_devices: ++ return device_location + return None + + +-def find_ephemeral_disk(): ++def find_fabric_formatted_ephemeral_disk(): + """ + Get the ephemeral disk. + """ +- part_dev = find_ephemeral_part() +- if part_dev and str(part_dev[-1]).isdigit(): +- return part_dev[:-1] +- elif part_dev: +- return part_dev ++ part_dev = find_fabric_formatted_ephemeral_part() ++ if part_dev: ++ return part_dev.split('-')[0] + return None + + def support_new_ephemeral(cfg): +@@ -263,7 +266,7 @@ + new ephemeral device is detected, cloud-init overrides the default + frequency for both disk-setup and mounts for the current boot only. + """ +- device = find_ephemeral_part() ++ device = find_fabric_formatted_ephemeral_part() + if not device: + LOG.debug("no default fabric formated ephemeral0.1 found") + return None +--- /dev/null ++++ b/tests/unittests/test_handler/test_handler_mounts.py +@@ -0,0 +1,133 @@ ++import os.path ++import shutil ++import tempfile ++ ++from cloudinit.config import cc_mounts ++ ++from .. import helpers as test_helpers ++ ++try: ++ from unittest import mock ++except ImportError: ++ import mock ++ ++ ++class TestSanitizeDevname(test_helpers.FilesystemMockingTestCase): ++ ++ def setUp(self): ++ super(TestSanitizeDevname, self).setUp() ++ self.new_root = tempfile.mkdtemp() ++ self.addCleanup(shutil.rmtree, self.new_root) ++ self.patchOS(self.new_root) ++ ++ def _touch(self, path): ++ path = os.path.join(self.new_root, path.lstrip('/')) ++ basedir = os.path.dirname(path) ++ if not os.path.exists(basedir): ++ os.makedirs(basedir) ++ open(path, 'a').close() ++ ++ def _makedirs(self, directory): ++ directory = os.path.join(self.new_root, directory.lstrip('/')) ++ if not os.path.exists(directory): ++ os.makedirs(directory) ++ ++ def mock_existence_of_disk(self, disk_path): ++ self._touch(disk_path) ++ self._makedirs(os.path.join('/sys/block', disk_path.split('/')[-1])) ++ ++ def mock_existence_of_partition(self, disk_path, partition_number): ++ self.mock_existence_of_disk(disk_path) ++ self._touch(disk_path + str(partition_number)) ++ disk_name = disk_path.split('/')[-1] ++ self._makedirs(os.path.join('/sys/block', ++ disk_name, ++ disk_name + str(partition_number))) ++ ++ def test_existent_full_disk_path_is_returned(self): ++ disk_path = '/dev/sda' ++ self.mock_existence_of_disk(disk_path) ++ self.assertEqual(disk_path, ++ cc_mounts.sanitize_devname(disk_path, ++ lambda x: None, ++ mock.Mock())) ++ ++ def test_existent_disk_name_returns_full_path(self): ++ disk_name = 'sda' ++ disk_path = '/dev/' + disk_name ++ self.mock_existence_of_disk(disk_path) ++ self.assertEqual(disk_path, ++ cc_mounts.sanitize_devname(disk_name, ++ lambda x: None, ++ mock.Mock())) ++ ++ def test_existent_meta_disk_is_returned(self): ++ actual_disk_path = '/dev/sda' ++ self.mock_existence_of_disk(actual_disk_path) ++ self.assertEqual( ++ actual_disk_path, ++ cc_mounts.sanitize_devname('ephemeral0', ++ lambda x: actual_disk_path, ++ mock.Mock())) ++ ++ def test_existent_meta_partition_is_returned(self): ++ disk_name, partition_part = '/dev/sda', '1' ++ actual_partition_path = disk_name + partition_part ++ self.mock_existence_of_partition(disk_name, partition_part) ++ self.assertEqual( ++ actual_partition_path, ++ cc_mounts.sanitize_devname('ephemeral0.1', ++ lambda x: disk_name, ++ mock.Mock())) ++ ++ def test_existent_meta_partition_with_p_is_returned(self): ++ disk_name, partition_part = '/dev/sda', 'p1' ++ actual_partition_path = disk_name + partition_part ++ self.mock_existence_of_partition(disk_name, partition_part) ++ self.assertEqual( ++ actual_partition_path, ++ cc_mounts.sanitize_devname('ephemeral0.1', ++ lambda x: disk_name, ++ mock.Mock())) ++ ++ def test_first_partition_returned_if_existent_disk_is_partitioned(self): ++ disk_name, partition_part = '/dev/sda', '1' ++ actual_partition_path = disk_name + partition_part ++ self.mock_existence_of_partition(disk_name, partition_part) ++ self.assertEqual( ++ actual_partition_path, ++ cc_mounts.sanitize_devname('ephemeral0', ++ lambda x: disk_name, ++ mock.Mock())) ++ ++ def test_nth_partition_returned_if_requested(self): ++ disk_name, partition_part = '/dev/sda', '3' ++ actual_partition_path = disk_name + partition_part ++ self.mock_existence_of_partition(disk_name, partition_part) ++ self.assertEqual( ++ actual_partition_path, ++ cc_mounts.sanitize_devname('ephemeral0.3', ++ lambda x: disk_name, ++ mock.Mock())) ++ ++ def test_transformer_returning_none_returns_none(self): ++ self.assertIsNone( ++ cc_mounts.sanitize_devname( ++ 'ephemeral0', lambda x: None, mock.Mock())) ++ ++ def test_missing_device_returns_none(self): ++ self.assertIsNone( ++ cc_mounts.sanitize_devname('/dev/sda', None, mock.Mock())) ++ ++ def test_missing_sys_returns_none(self): ++ disk_path = '/dev/sda' ++ self._makedirs(disk_path) ++ self.assertIsNone( ++ cc_mounts.sanitize_devname(disk_path, None, mock.Mock())) ++ ++ def test_existent_disk_but_missing_partition_returns_none(self): ++ disk_path = '/dev/sda' ++ self.mock_existence_of_disk(disk_path) ++ self.assertIsNone( ++ cc_mounts.sanitize_devname( ++ 'ephemeral0.1', lambda x: disk_path, mock.Mock())) +--- /dev/null ++++ b/udev/66-azure-ephemeral.rules +@@ -0,0 +1,18 @@ ++# Azure specific rules ++ACTION!="add|change", GOTO="cloud_init_end" ++SUBSYSTEM!="block", GOTO="cloud_init_end" ++ATTRS{ID_VENDOR}!="Msft", GOTO="cloud_init_end" ++ATTRS{ID_MODEL}!="Virtual_Disk", GOTO="cloud_init_end" ++ ++# Root has a GUID of 0000 as the second value ++# The resource/resource has GUID of 0001 as the second value ++ATTRS{device_id}=="?00000000-0000-*", ENV{fabric_name}="azure_root", GOTO="ci_azure_names" ++ATTRS{device_id}=="?00000000-0001-*", ENV{fabric_name}="azure_resource", GOTO="ci_azure_names" ++GOTO="cloud_init_end" ++ ++# Create the symlinks ++LABEL="ci_azure_names" ++ENV{DEVTYPE}=="disk", SYMLINK+="disk/cloud/$env{fabric_name}" ++ENV{DEVTYPE}=="partition", SYMLINK+="disk/cloud/$env{fabric_name}-part%n" ++ ++LABEL="cloud_init_end" +--- a/setup.py ++++ b/setup.py +@@ -48,5 +48,6 @@ + ('/usr/share/doc/cloud-init/examples', filter(is_f,glob('doc/examples/*'))), + ('/usr/share/doc/cloud-init/examples/seed', filter(is_f,glob('doc/examples/seed/*'))), + ('/etc/profile.d', ['tools/Z99-cloud-locale-test.sh']), ++ ('/lib/udev/rules.d', ['udev/66-azure-ephemeral.rules']), + ], + ) diff -Nru cloud-init-0.6.3/debian/patches/lp-1470880-fix-gce-az-determination.patch cloud-init-0.6.3/debian/patches/lp-1470880-fix-gce-az-determination.patch --- cloud-init-0.6.3/debian/patches/lp-1470880-fix-gce-az-determination.patch 1970-01-01 00:00:00.000000000 +0000 +++ cloud-init-0.6.3/debian/patches/lp-1470880-fix-gce-az-determination.patch 2015-08-14 17:21:22.000000000 +0000 @@ -0,0 +1,17 @@ +Description: Correctly parse GCE's availability zones +Author: Daniel Watkins +Origin: upstream, http://bazaar.launchpad.net/~cloud-init-dev/cloud-init/trunk/revision/1121 +Bug: https://bugs.launchpad.net/cloud-init/+bug/1470880 +--- a/cloudinit/DataSourceGCE.py ++++ b/cloudinit/DataSourceGCE.py +@@ -124,6 +124,10 @@ + lines = self.metadata['public-keys'].splitlines() + self.metadata['public-keys'] = [self._trim_key(k) for k in lines] + ++ if self.metadata['availability-zone']: ++ self.metadata['availability-zone'] = self.metadata[ ++ 'availability-zone'].split('/')[-1] ++ + encoding = self.metadata.get('user-data-encoding') + if encoding: + if encoding == 'base64': diff -Nru cloud-init-0.6.3/debian/patches/lp-1470890-include-regions-in-dynamic-mirror-discovery.patch cloud-init-0.6.3/debian/patches/lp-1470890-include-regions-in-dynamic-mirror-discovery.patch --- cloud-init-0.6.3/debian/patches/lp-1470890-include-regions-in-dynamic-mirror-discovery.patch 1970-01-01 00:00:00.000000000 +0000 +++ cloud-init-0.6.3/debian/patches/lp-1470890-include-regions-in-dynamic-mirror-discovery.patch 2015-08-14 17:21:22.000000000 +0000 @@ -0,0 +1,119 @@ +Description: Enable %(region)s as a dynamic mirror substitution +Author: Daniel Watkins +Origin: upstream, http://bazaar.launchpad.net/~cloud-init-dev/cloud-init/trunk/revision/1126 +Bug: https://bugs.launchpad.net/cloud-init/+bug/1470890 +--- a/cloudinit/CloudConfig/cc_apt_update_upgrade.py ++++ b/cloudinit/CloudConfig/cc_apt_update_upgrade.py +@@ -271,9 +271,8 @@ + + try: + pmirrors = cfg['system_info']['package_mirrors'] +- az = cloud.datasource.get_availability_zone() + mirror_info = get_package_mirror_info(package_mirrors=pmirrors, +- availability_zone=az) ++ data_source=cloud.datasource) + except Exception as e: + util.logexc(log) + log.warn("Failed to get mirror info, falling back to default" % +@@ -292,13 +291,13 @@ + ## put together from trunk's cloudinit/distros/__init__.py and + ## cloudinit/sources/__init__.py + def get_package_mirror_info(package_mirrors, +- availability_zone=None, arch=None): ++ data_source=None, arch=None): + if arch == None: + arch = get_primary_arch() + arch_info = _get_arch_package_mirror_info(package_mirrors, arch) + + info = _get_package_mirror_info(mirror_info=arch_info, +- availability_zone=availability_zone) ++ data_source=data_source) + return info + + ## taken from trunk's cloudinit/distros/debian.py (Distro) +@@ -307,7 +306,7 @@ + return str(arch).strip() + + ## taken from trunk's cloudinit/distros/__init__.py ## +-def _get_package_mirror_info(mirror_info, availability_zone=None, ++def _get_package_mirror_info(mirror_info, data_source=None, + mirror_filter=util.search_for_mirror): + # given a arch specific 'mirror_info' entry (from package_mirrors) + # search through the 'search' entries, and fallback appropriately +@@ -321,11 +320,15 @@ + ec2_az_re = ("^[a-z][a-z]-(%s)-[1-9][0-9]*[a-z]$" % directions_re) + + subst = {} +- if availability_zone: +- subst['availability_zone'] = availability_zone ++ if data_source and data_source.get_availability_zone(): ++ subst['availability_zone'] = data_source.get_availability_zone() + +- if availability_zone and re.match(ec2_az_re, availability_zone): +- subst['ec2_region'] = "%s" % availability_zone[0:-1] ++ if re.match(ec2_az_re, data_source.get_availability_zone()): ++ subst['ec2_region'] = "%s" % ( ++ data_source.get_availability_zone()[0:-1],) ++ ++ if data_source and data_source.region: ++ subst['region'] = data_source.region + + results = {} + for (name, mirror) in mirror_info.get('failsafe', {}).iteritems(): +--- a/cloudinit/DataSourceEc2.py ++++ b/cloudinit/DataSourceEc2.py +@@ -186,6 +186,13 @@ + return True + return False + ++ @property ++ def region(self): ++ az = self.get_availability_zone() ++ if az is not None: ++ return az[:-1] ++ return None ++ + + datasources = [ + (DataSourceEc2, (DataSource.DEP_FILESYSTEM, DataSource.DEP_NETWORK)), +--- a/cloudinit/DataSourceGCE.py ++++ b/cloudinit/DataSourceGCE.py +@@ -156,10 +156,13 @@ + def userdata_raw(self): + return self.metadata['user-data'] or '' + +- @property +- def availability_zone(self): ++ def get_availability_zone(self): + return self.metadata['availability-zone'] + ++ @property ++ def region(self): ++ return self.get_availability_zone().rsplit('-', 1)[0] ++ + # Used to match classes to dependencies + datasources = [ + (DataSourceGCE, (sources.DEP_FILESYSTEM, sources.DEP_NETWORK)), +--- a/cloudinit/DataSource.py ++++ b/cloudinit/DataSource.py +@@ -102,6 +102,10 @@ + def get_local_mirror(self): + return None + ++ @property ++ def region(self): ++ return self.metadata.get('region') ++ + def get_instance_id(self): + if 'instance-id' not in self.metadata: + return "iid-datasource" +--- a/config/cloud.cfg ++++ b/config/cloud.cfg +@@ -52,6 +52,7 @@ + primary: + - http://%(ec2_region)s.ec2.archive.ubuntu.com/ubuntu/ + - http://%(availability_zone)s.clouds.archive.ubuntu.com/ubuntu/ ++ - http://%(region)s.clouds.archive.ubuntu.com/ubuntu/ + security: [] + - arches: [armhf, armel, default] + failsafe: diff -Nru cloud-init-0.6.3/debian/patches/series cloud-init-0.6.3/debian/patches/series --- cloud-init-0.6.3/debian/patches/series 2015-07-17 17:22:15.000000000 +0000 +++ cloud-init-0.6.3/debian/patches/series 2015-08-14 17:21:22.000000000 +0000 @@ -30,3 +30,6 @@ lp-1422388-cloudstack-passwords.patch lp-1456684-eu-central-1.patch lp-1464253-handle-new-cloudstack-passwords.patch +lp-1411582-azure-udev-ephemeral-disks.patch +lp-1470880-fix-gce-az-determination.patch +lp-1470890-include-regions-in-dynamic-mirror-discovery.patch