summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cloudinit/sources/DataSourceEc2.py21
-rw-r--r--doc/rtd/topics/datasources/ec2.rst11
-rw-r--r--tests/unittests/test_datasource/test_ec2.py24
3 files changed, 56 insertions, 0 deletions
diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py
index eb6f27b2..4f2f6ccb 100644
--- a/cloudinit/sources/DataSourceEc2.py
+++ b/cloudinit/sources/DataSourceEc2.py
@@ -19,6 +19,7 @@ from cloudinit import sources
from cloudinit import url_helper as uhelp
from cloudinit import util
from cloudinit import warnings
+from cloudinit.event import EventType
LOG = logging.getLogger(__name__)
@@ -107,6 +108,19 @@ class DataSourceEc2(sources.DataSource):
'dynamic', {}).get('instance-identity', {}).get('document', {})
return True
+ def is_classic_instance(self):
+ """Report if this instance type is Ec2 Classic (non-vpc)."""
+ if not self.metadata:
+ # Can return False on inconclusive as we are also called in
+ # network_config where metadata will be present.
+ # Secondary call site is in packaging postinst script.
+ return False
+ ifaces_md = self.metadata.get('network', {}).get('interfaces', {})
+ for _mac, mac_data in ifaces_md.get('macs', {}).items():
+ if 'vpc-id' in mac_data:
+ return False
+ return True
+
@property
def launch_index(self):
if not self.metadata:
@@ -320,6 +334,13 @@ class DataSourceEc2(sources.DataSource):
if isinstance(net_md, dict):
result = convert_ec2_metadata_network_config(
net_md, macs_to_nics=macs_to_nics, fallback_nic=iface)
+ # RELEASE_BLOCKER: Xenial debian/postinst needs to add
+ # EventType.BOOT on upgrade path for classic.
+
+ # Non-VPC (aka Classic) Ec2 instances need to rewrite the
+ # network config file every boot due to MAC address change.
+ if self.is_classic_instance():
+ self.update_events['network'].add(EventType.BOOT)
else:
LOG.warning("Metadata 'network' key not valid: %s.", net_md)
self._network_config = result
diff --git a/doc/rtd/topics/datasources/ec2.rst b/doc/rtd/topics/datasources/ec2.rst
index 64c325d8..76beca92 100644
--- a/doc/rtd/topics/datasources/ec2.rst
+++ b/doc/rtd/topics/datasources/ec2.rst
@@ -90,4 +90,15 @@ An example configuration with the default values is provided below:
max_wait: 120
timeout: 50
+Notes
+-----
+ * There are 2 types of EC2 instances network-wise: VPC ones (Virtual Private
+ Cloud) and Classic ones (also known as non-VPC). One major difference
+ between them is that Classic instances have their MAC address changed on
+ stop/restart operations, so cloud-init will recreate the network config
+ file for EC2 Classic instances every boot. On VPC instances this file is
+ generated only in the first boot of the instance.
+ The check for the instance type is performed by is_classic_instance()
+ method.
+
.. vi: textwidth=78
diff --git a/tests/unittests/test_datasource/test_ec2.py b/tests/unittests/test_datasource/test_ec2.py
index 1a5956d9..20d59bfd 100644
--- a/tests/unittests/test_datasource/test_ec2.py
+++ b/tests/unittests/test_datasource/test_ec2.py
@@ -401,6 +401,30 @@ class TestEc2(test_helpers.HttprettyTestCase):
ds.metadata = DEFAULT_METADATA
self.assertEqual('my-identity-id', ds.get_instance_id())
+ def test_classic_instance_true(self):
+ """If no vpc-id in metadata, is_classic_instance must return true."""
+ md_copy = copy.deepcopy(DEFAULT_METADATA)
+ ifaces_md = md_copy.get('network', {}).get('interfaces', {})
+ for _mac, mac_data in ifaces_md.get('macs', {}).items():
+ if 'vpc-id' in mac_data:
+ del mac_data['vpc-id']
+
+ ds = self._setup_ds(
+ platform_data=self.valid_platform_data,
+ sys_cfg={'datasource': {'Ec2': {'strict_id': False}}},
+ md={'md': md_copy})
+ self.assertTrue(ds.get_data())
+ self.assertTrue(ds.is_classic_instance())
+
+ def test_classic_instance_false(self):
+ """If vpc-id in metadata, is_classic_instance must return false."""
+ ds = self._setup_ds(
+ platform_data=self.valid_platform_data,
+ sys_cfg={'datasource': {'Ec2': {'strict_id': False}}},
+ md={'md': DEFAULT_METADATA})
+ self.assertTrue(ds.get_data())
+ self.assertFalse(ds.is_classic_instance())
+
@mock.patch('cloudinit.net.dhcp.maybe_perform_dhcp_discovery')
def test_valid_platform_with_strict_true(self, m_dhcp):
"""Valid platform data should return true with strict_id true."""