Support datasource specific instance metadata

This change aligns the instance metadata with cloudinit to
add the support for datasource specific instance metadata.

The datasource specific instance metadata allows more
information to be exposed to the Jinja template for userdata
and script.

Note:
The structure is not standarized but you may refer to
https://cloudinit.readthedocs.io/en/latest/explanation/instancedata.html
to the cloudinit format.

Change-Id: I1ec7e5bdf063709c513b52a02c9251752aafe157
This commit is contained in:
Zhongcheng Lao 2024-09-14 15:40:42 +08:00
parent c10ff6896e
commit dc0367425a
9 changed files with 692 additions and 24 deletions

View File

@ -28,6 +28,9 @@ from cloudbaseinit.utils import encoding
CONF = cloudbaseinit_conf.CONF
LOG = oslo_logging.getLogger(__name__)
EXPERIMENTAL_NOTICE = ("EXPERIMENTAL: The structure and format of content "
"scoped under the 'ds' key may change in subsequent "
"releases of cloud-init.")
class NotExistingMetadataException(Exception):
@ -236,28 +239,52 @@ class BaseMetadataService(object, metaclass=abc.ABCMeta):
The ds namespace can change without prior notice and should not be
used in production.
"""
instance_id = self.get_instance_id()
hostname = self.get_host_name()
v1_data = {
"instance-id": instance_id,
"instance_id": instance_id,
"local-hostname": hostname,
"local_hostname": hostname,
"public_ssh_keys": self.get_public_keys()
}
# Copy the v1 data to the ds.meta_data and add more fields
ds_meta_data = self._get_datasource_instance_meta_data()
if not ds_meta_data:
ds_meta_data = copy.deepcopy(v1_data)
ds_meta_data.update({
"hostname": hostname
})
return {
v1_data["public_ssh_keys"] = self.get_public_keys()
md = {
"v1": v1_data,
"ds": {
"_doc": EXPERIMENTAL_NOTICE,
"meta_data": ds_meta_data,
},
"instance-id": instance_id,
"instance_id": instance_id,
"local-hostname": hostname,
"local_hostname": hostname,
"public_ssh_keys": self.get_public_keys()
}
}
return md
def _get_datasource_instance_meta_data(self):
"""Returns a dictionary with datasource specific instance data
The instance data structure is based on the cloud-init specifications:
https://cloudinit.readthedocs.io/en/latest/explanation/instancedata.html
Datasource-specific metadata crawled for the specific cloud platform.
It should closely represent the structure of the cloud metadata
crawled. The structure of content and details provided are entirely
cloud-dependent.
"""
pass
class BaseHTTPMetadataService(BaseMetadataService):

View File

@ -30,6 +30,9 @@ from cloudbaseinit.utils import serialization
CONF = cloudbaseinit_conf.CONF
LOG = oslo_logging.getLogger(__name__)
DEFAULT_GATEWAY_CIDR_IPV4 = u"0.0.0.0/0"
DEFAULT_GATEWAY_CIDR_IPV6 = u"::/0"
class NoCloudNetworkConfigV1Parser(object):
NETWORK_LINK_TYPE_PHY = 'physical'
@ -280,9 +283,6 @@ class NoCloudNetworkConfigV1Parser(object):
class NoCloudNetworkConfigV2Parser(object):
DEFAULT_GATEWAY_CIDR_IPV4 = u"0.0.0.0/0"
DEFAULT_GATEWAY_CIDR_IPV6 = u"::/0"
NETWORK_LINK_TYPE_ETHERNET = 'ethernet'
NETWORK_LINK_TYPE_BOND = 'bond'
NETWORK_LINK_TYPE_VLAN = 'vlan'
@ -308,11 +308,11 @@ class NoCloudNetworkConfigV2Parser(object):
default_route = None
if gateway6 and netaddr.valid_ipv6(gateway6):
default_route = network_model.Route(
network_cidr=self.DEFAULT_GATEWAY_CIDR_IPV6,
network_cidr=DEFAULT_GATEWAY_CIDR_IPV6,
gateway=gateway6)
elif gateway4 and netaddr.valid_ipv4(gateway4):
default_route = network_model.Route(
network_cidr=self.DEFAULT_GATEWAY_CIDR_IPV4,
network_cidr=DEFAULT_GATEWAY_CIDR_IPV4,
gateway=gateway4)
if default_route:
routes.append(default_route)
@ -324,9 +324,9 @@ class NoCloudNetworkConfigV2Parser(object):
gateway = route_config.get("via")
if network_cidr.lower() == "default":
if netaddr.valid_ipv6(gateway):
network_cidr = self.DEFAULT_GATEWAY_CIDR_IPV6
network_cidr = DEFAULT_GATEWAY_CIDR_IPV6
else:
network_cidr = self.DEFAULT_GATEWAY_CIDR_IPV4
network_cidr = DEFAULT_GATEWAY_CIDR_IPV4
route = network_model.Route(
network_cidr=network_cidr,
gateway=gateway)
@ -547,6 +547,113 @@ class NoCloudNetworkConfigParser(object):
return network_config_parser.parse(network_data)
@staticmethod
def network_details_v1_to_v2(v1_networks):
"""Converts `NetworkDetails` objects to `NetworkDetailsV2` object.
"""
if not v1_networks:
return None
links = []
networks = []
services = []
for nic in v1_networks:
link = network_model.Link(
id=nic.name,
name=nic.name,
type=network_model.LINK_TYPE_PHYSICAL,
mac_address=nic.mac,
enabled=None,
mtu=None,
bond=None,
vlan_link=None,
vlan_id=None,
)
links.append(link)
dns_addresses_v4 = []
dns_addresses_v6 = []
if nic.dnsnameservers:
for ns in nic.dnsnameservers:
if netaddr.valid_ipv6(ns):
dns_addresses_v6.append(ns)
else:
dns_addresses_v4.append(ns)
dns_services_v6 = None
if dns_addresses_v6:
dns_service_v6 = network_model.NameServerService(
addresses=dns_addresses_v6,
search=None,
)
dns_services_v6 = [dns_service_v6]
services.append(dns_service_v6)
dns_services_v4 = None
if dns_addresses_v4:
dns_service_v4 = network_model.NameServerService(
addresses=dns_addresses_v4,
search=None,
)
dns_services_v4 = [dns_service_v4]
services.append(dns_service_v4)
# Note: IPv6 address might be set to IPv4 field
# Not sure if it's a bug
default_route_v6 = None
default_route_v4 = None
if nic.gateway6:
default_route_v6 = network_model.Route(
network_cidr=DEFAULT_GATEWAY_CIDR_IPV6,
gateway=nic.gateway6)
if nic.gateway:
if netaddr.valid_ipv6(nic.gateway):
default_route_v6 = network_model.Route(
network_cidr=DEFAULT_GATEWAY_CIDR_IPV6,
gateway=nic.gateway)
else:
default_route_v4 = network_model.Route(
network_cidr=DEFAULT_GATEWAY_CIDR_IPV4,
gateway=nic.gateway)
routes_v6 = [default_route_v6] if default_route_v6 else []
routes_v4 = [default_route_v4] if default_route_v4 else []
if nic.address6:
net = network_model.Network(
link=link.name,
address_cidr=network_utils.ip_netmask_to_cidr(
nic.address6, nic.netmask6),
routes=routes_v6,
dns_nameservers=dns_services_v6,
)
networks.append(net)
if nic.address:
if netaddr.valid_ipv6(nic.address):
net = network_model.Network(
link=link.name,
address_cidr=network_utils.ip_netmask_to_cidr(
nic.address, nic.netmask),
routes=routes_v6,
dns_nameservers=dns_services_v6,
)
else:
net = network_model.Network(
link=link.name,
address_cidr=network_utils.ip_netmask_to_cidr(
nic.address, nic.netmask),
routes=routes_v4,
dns_nameservers=dns_services_v4,
)
networks.append(net)
return network_model.NetworkDetailsV2(links=links,
networks=networks,
services=services)
class NoCloudConfigDriveService(baseconfigdrive.BaseConfigDriveService):

View File

@ -27,6 +27,7 @@ from cloudbaseinit import exception
from cloudbaseinit.metadata.services import base
from cloudbaseinit.metadata.services import nocloudservice
from cloudbaseinit.osutils import factory as osutils_factory
from cloudbaseinit.utils import network
from cloudbaseinit.utils import serialization
CONF = cloudbaseinit_conf.CONF
@ -239,3 +240,22 @@ class VMwareGuestInfoService(base.BaseMetadataService):
LOG.debug("network data %s", network)
return {"network": network}
def _get_datasource_instance_meta_data(self):
"""Returns a dictionary with datasource specific instance data
The instance data structure is based on the cloud-init specifications:
https://cloudinit.readthedocs.io/en/latest/explanation/instancedata.html
Datasource-specific metadata crawled for the specific cloud platform.
It should closely represent the structure of the cloud metadata
crawled. The structure of content and details provided are entirely
cloud-dependent.
"""
ds = dict()
network_details = self.get_network_details_v2()
host_info = network.get_host_info(self.get_host_name(),
network_details)
ds.update(host_info)
return ds

View File

@ -61,38 +61,63 @@ class TestBase(unittest.TestCase):
result = self._service.get_user_pwd_encryption_key()
self.assertEqual(result, mock_get_public_keys.return_value[0])
@mock.patch('cloudbaseinit.metadata.services.base.'
'BaseMetadataService._get_datasource_instance_meta_data')
@mock.patch('cloudbaseinit.metadata.services.base.'
'BaseMetadataService.get_public_keys')
@mock.patch('cloudbaseinit.metadata.services.base.'
'BaseMetadataService.get_host_name')
@mock.patch('cloudbaseinit.metadata.services.base.'
'BaseMetadataService.get_instance_id')
def test_get_instance_data(self, mock_instance_id, mock_hostname,
mock_public_keys):
def _test_get_instance_data_with_datasource_meta_data(
self, mock_instance_id, mock_hostname, mock_public_keys,
mock_get_datasource_instance_meta_data, datasource_meta_data=None):
fake_instance_id = 'id'
mock_instance_id.return_value = fake_instance_id
fake_hostname = 'host'
mock_hostname.return_value = fake_hostname
fake_keys = ['ssh1', 'ssh2']
mock_public_keys.return_value = fake_keys
mock_get_datasource_instance_meta_data.return_value = \
datasource_meta_data
if datasource_meta_data:
ds_md = datasource_meta_data
else:
ds_md = {
"instance_id": fake_instance_id,
"instance-id": fake_instance_id,
"local_hostname": fake_hostname,
"local-hostname": fake_hostname,
"hostname": fake_hostname
}
expected_response = {
'v1': {
"instance_id": fake_instance_id,
"instance-id": fake_instance_id,
"local_hostname": fake_hostname,
"local-hostname": fake_hostname,
"public_ssh_keys": fake_keys
},
'ds': {
'meta_data': {
"instance_id": fake_instance_id,
"local_hostname": fake_hostname,
"public_ssh_keys": fake_keys,
"hostname": fake_hostname
'_doc': base.EXPERIMENTAL_NOTICE,
'meta_data': ds_md,
},
}
"instance_id": fake_instance_id,
"instance-id": fake_instance_id,
"local_hostname": fake_hostname,
"local-hostname": fake_hostname,
"public_ssh_keys": fake_keys,
}
self.assertEqual(expected_response, self._service.get_instance_data())
def test_get_instance_data(self):
self._test_get_instance_data_with_datasource_meta_data()
def test_get_instance_data_with_datasource_meta_data(self):
self._test_get_instance_data_with_datasource_meta_data(
datasource_meta_data={'fake-data': 'fake-value'})
class TestBaseHTTPMetadataService(unittest.TestCase):

View File

@ -20,10 +20,16 @@ import unittest
import unittest.mock as mock
from cloudbaseinit.metadata.services import base
from cloudbaseinit.metadata.services.nocloudservice import \
NoCloudNetworkConfigParser
from cloudbaseinit.models import network as nm
from cloudbaseinit.tests.metadata import fake_json_response
from cloudbaseinit.tests import testutils
from cloudbaseinit.utils import debiface
from cloudbaseinit.utils import network
from cloudbaseinit.utils import serialization
MODULE_PATH = "cloudbaseinit.metadata.services.nocloudservice"
NOCLOUD_NETWORK_CONFIG_TEST_DATA_V1_EMPTY_CONFIG = """
network:
@ -531,3 +537,98 @@ class TestNoCloudConfigDriveService(unittest.TestCase):
mock_get_cache_data.assert_called_with(
"network-config", decode=True)
def test_to_network_details_v2(self):
date = "2013-04-04"
content = fake_json_response.get_fake_metadata_json(date)
nics = debiface.parse(content["network_config"]["debian_config"])
v2 = NoCloudNetworkConfigParser.network_details_v1_to_v2(nics)
link0 = nm.Link(
id=fake_json_response.NAME0,
name=fake_json_response.NAME0,
type=nm.LINK_TYPE_PHYSICAL,
mac_address=fake_json_response.MAC0.upper(),
enabled=None,
mtu=None,
bond=None,
vlan_link=None,
vlan_id=None,
)
link1 = nm.Link(
id=fake_json_response.NAME1,
name=fake_json_response.NAME1,
type=nm.LINK_TYPE_PHYSICAL,
mac_address=None,
enabled=None,
mtu=None,
bond=None,
vlan_link=None,
vlan_id=None,
)
link2 = nm.Link(
id=fake_json_response.NAME2,
name=fake_json_response.NAME2,
type=nm.LINK_TYPE_PHYSICAL,
mac_address=fake_json_response.MAC2,
enabled=None,
mtu=None,
bond=None,
vlan_link=None,
vlan_id=None,
)
dns_service0 = nm.NameServerService(
addresses=fake_json_response.DNSNS0.split(),
search=None,
)
network0 = nm.Network(
link=fake_json_response.NAME0,
address_cidr=network.ip_netmask_to_cidr(
fake_json_response.ADDRESS0, fake_json_response.NETMASK0),
routes=[nm.Route(
network_cidr=u"0.0.0.0/0",
gateway=fake_json_response.GATEWAY0,
)],
dns_nameservers=[dns_service0],
)
network0_v6 = nm.Network(
link=fake_json_response.NAME0,
address_cidr=network.ip_netmask_to_cidr(
fake_json_response.ADDRESS60, fake_json_response.NETMASK60),
routes=[nm.Route(
network_cidr=u"::/0",
gateway=fake_json_response.GATEWAY60,
)],
dns_nameservers=None,
)
network1 = nm.Network(
link=fake_json_response.NAME1,
address_cidr=network.ip_netmask_to_cidr(
fake_json_response.ADDRESS1, fake_json_response.NETMASK1),
routes=[nm.Route(
network_cidr=u"0.0.0.0/0",
gateway=fake_json_response.GATEWAY1,
)],
dns_nameservers=None,
)
network2 = nm.Network(
link=fake_json_response.NAME2,
address_cidr=network.ip_netmask_to_cidr(
fake_json_response.ADDRESS2, fake_json_response.NETMASK2),
routes=[nm.Route(
network_cidr=u"::/0",
gateway=fake_json_response.GATEWAY2,
)],
dns_nameservers=None,
)
expected = nm.NetworkDetailsV2(
links=[
link0, link1, link2,
],
networks=[
network0_v6, network0, network1, network2,
],
services=[
dns_service0
],
)
self.assertEqual(expected, v2)

View File

@ -292,3 +292,10 @@ class VMwareGuestInfoServiceTest(unittest.TestCase):
mock_get_guestinfo_value.return_value = "no encoding"
self.assertRaises(exception.CloudbaseInitException,
self._service._get_guest_data, 'fake_key')
@mock.patch("cloudbaseinit.utils.network.get_host_info")
def test_get_datasource_instance_meta_data(self, mock_get_host_info):
expect_md = {'network': 'fake_host_info'}
mock_get_host_info.return_value = expect_md
self.assertEqual(expect_md,
self._service._get_datasource_instance_meta_data())

View File

@ -12,10 +12,12 @@
# License for the specific language governing permissions and limitations
# under the License.
import collections
import unittest
import unittest.mock as mock
from cloudbaseinit import conf as cloudbaseinit_conf
from cloudbaseinit.models import network as network_model
from cloudbaseinit.tests import testutils
from cloudbaseinit.utils import network
@ -24,6 +26,89 @@ CONF = cloudbaseinit_conf.CONF
class NetworkUtilsTest(unittest.TestCase):
link0 = network_model.Link(
id="eth0",
name="eth0",
type=network_model.LINK_TYPE_PHYSICAL,
mac_address="ab:cd:ef:ef:cd:ab",
enabled=None,
mtu=None,
bond=None,
vlan_link=None,
vlan_id=None,
)
network_private_default_route = network_model.Network(
link="eth0",
address_cidr="192.168.1.2/24",
routes=[
network_model.Route(
network_cidr=u"0.0.0.0/0",
gateway="192.168.1.1",
),
],
dns_nameservers=[]
)
network_public = network_model.Network(
link="eth0",
address_cidr="2.3.4.2/24",
routes=[
network_model.Route(
network_cidr=u"2.3.4.1/24",
gateway="2.3.4.1",
),
],
dns_nameservers=[]
)
network_public_default_route = network_model.Network(
link="eth0",
address_cidr="2.3.4.2/24",
routes=[
network_model.Route(
network_cidr=u"0.0.0.0/0",
gateway="2.3.4.1",
),
],
dns_nameservers=[]
)
network_private = network_model.Network(
link="eth0",
address_cidr="172.10.1.2/24",
routes=[
network_model.Route(
network_cidr=u"172.10.1.1/24",
gateway="172.10.1.1",
),
],
dns_nameservers=[]
)
network_local = network_model.Network(
link="eth0",
address_cidr="127.0.0.4/24",
routes=[
network_model.Route(
network_cidr=u"127.0.0.4/24",
gateway="127.0.0.2",
),
],
dns_nameservers=[]
)
ipv6_addr = '1a8f:9aaf:2904:858f:1bce:6f85:2b04:f38'
network_v6 = network_model.Network(
link="eth0",
address_cidr=ipv6_addr + '/64',
routes=[network_model.Route(
network_cidr=u"::/0",
gateway="::1",
)],
dns_nameservers=[]
)
ipv6_addr_private = 'fe80::216:3eff:fe16:db54'
network_v6_local = network_model.Network(
link="eth0",
address_cidr=ipv6_addr_private + '/64',
routes=[],
dns_nameservers=[]
)
@mock.patch('urllib.request.urlopen')
def test_check_url(self, mock_url_open):
@ -110,3 +195,152 @@ class NetworkUtilsTest(unittest.TestCase):
fake_netmask = None
self._test_ip_netmask_to_cidr(fake_ip_address, fake_ip_address,
fake_netmask)
def test_get_default_ip_addresses(self):
network_details = network_model.NetworkDetailsV2(
links=[
self.link0
],
networks=[
self.network_private_default_route,
],
services=[
],
)
ipv4, ipv6 = network.get_default_ip_addresses(network_details)
self.assertEqual('192.168.1.2', ipv4)
self.assertIsNone(ipv6)
def test_get_default_ip_addresses_link_local(self):
network_details = network_model.NetworkDetailsV2(
links=[
self.link0
],
networks=[
self.network_private,
],
services=[
],
)
ipv4, ipv6 = network.get_default_ip_addresses(network_details)
self.assertIsNone(ipv4)
self.assertIsNone(ipv6)
def test_get_default_ip_addresses_public_default_route(self):
network_details = network_model.NetworkDetailsV2(
links=[
self.link0
],
networks=[
self.network_public_default_route,
],
services=[
],
)
ipv4, ipv6 = network.get_default_ip_addresses(network_details)
self.assertEqual("2.3.4.2", ipv4)
self.assertIsNone(ipv6)
def test_get_default_ip_addresses_v6(self):
network_details = network_model.NetworkDetailsV2(
links=[
self.link0
],
networks=[
self.network_v6,
],
services=[
],
)
ipv4, ipv6 = network.get_default_ip_addresses(network_details)
self.assertIsNone(ipv4)
self.assertEqual(self.ipv6_addr, ipv6)
def test_get_default_ip_addresses_v6_local(self):
network_details = network_model.NetworkDetailsV2(
links=[
self.link0
],
networks=[
self.network_v6_local,
],
services=[
],
)
ipv4, ipv6 = network.get_default_ip_addresses(network_details)
self.assertIsNone(ipv4)
self.assertIsNone(ipv6)
def test_get_default_ip_addresses_dual_stack(self):
network_details = network_model.NetworkDetailsV2(
links=[
self.link0
],
networks=[
self.network_private_default_route,
self.network_public,
self.network_v6,
],
services=[
],
)
ipv4, ipv6 = network.get_default_ip_addresses(network_details)
self.assertEqual('192.168.1.2', ipv4)
self.assertEqual(self.ipv6_addr, ipv6)
def test_get_host_info(self):
network_details = network_model.NetworkDetailsV2(
links=[
self.link0
],
networks=[
self.network_private_default_route,
self.network_public, self.network_v6,
],
services=[
],
)
expect = {
'hostname': 'fake_host',
'local-hostname': 'fake_host',
'local-ipv4': '192.168.1.2',
'local_ipv4': '192.168.1.2',
'local-ipv6': '1a8f:9aaf:2904:858f:1bce:6f85:2b04:f38',
'local_ipv6': '1a8f:9aaf:2904:858f:1bce:6f85:2b04:f38',
'local_hostname': 'fake_host',
'network': {'interfaces': {
'by-ipv4': collections.OrderedDict([
('192.168.1.2',
{'broadcast': '192.168.1.255',
'mac': 'ab:cd:ef:ef:cd:ab',
'netmask': '255.255.255.0'}
),
('2.3.4.2',
{'broadcast': '2.3.4.255',
'mac': 'ab:cd:ef:ef:cd:ab',
'netmask': '255.255.255.0'}
)
]),
'by-ipv6': collections.OrderedDict([
('1a8f:9aaf:2904:858f:1bce:6f85:2b04:f38',
{'broadcast': '1a8f:9aaf:2904:858f:ffff:ffff:ffff:ffff',
'mac': 'ab:cd:ef:ef:cd:ab',
}
)
]),
'by-mac': collections.OrderedDict([
('ab:cd:ef:ef:cd:ab',
{'ipv4': {'addr': '2.3.4.2',
'broadcast': '2.3.4.255',
'netmask': '255.255.255.0'},
'ipv6': {
'addr': '1a8f:9aaf:2904:858f:1bce:6f85:2b04:f38',
'broadcast': '1a8f:9aaf:2904:858f:'
'ffff:ffff:ffff:ffff'}
}
),
])
}}
}
host_info = network.get_host_info('fake_host', network_details)
self.assertEqual(expect, host_info)

View File

@ -14,6 +14,9 @@
import binascii
import collections
import ipaddress
import netaddr
import socket
import struct
@ -28,6 +31,10 @@ from cloudbaseinit.osutils import factory as osutils_factory
LOG = oslo_logging.getLogger(__name__)
MAX_URL_CHECK_RETRIES = 3
DEFAULT_GATEWAY_CIDR_IPV4 = u"0.0.0.0/0"
DEFAULT_GATEWAY_CIDR_IPV6 = u"::/0"
LOCAL_IPV4 = "local-ipv4"
LOCAL_IPV6 = "local-ipv6"
def get_local_ip(address=None):
@ -98,3 +105,135 @@ def ip_netmask_to_cidr(ip_address, netmask):
prefix_len = netaddr.IPNetwork(
u"%s/%s" % (ip_address, netmask)).prefixlen
return u"%s/%s" % (ip_address, prefix_len)
def get_default_ip_addresses(network_details):
ipv4_address = None
ipv6_address = None
if network_details:
for net in network_details.networks:
ip_net = netaddr.IPNetwork(net.address_cidr)
addr = ip_net.ip
default_route = False
for route in net.routes:
if addr.version == 6 and \
route.network_cidr == DEFAULT_GATEWAY_CIDR_IPV6:
default_route = True
elif addr.version == 4 and \
route.network_cidr == DEFAULT_GATEWAY_CIDR_IPV4:
default_route = True
if not default_route:
continue
if not ipv6_address and addr.version == 6:
v6_addr = ipaddress.IPv6Address(addr)
if v6_addr.is_private or v6_addr.is_global:
ipv6_address = str(v6_addr)
if not ipv4_address and addr.version == 4:
v4_addr = ipaddress.IPv4Address(addr)
if v4_addr.is_private or v4_addr.is_global:
ipv4_address = str(v4_addr)
return ipv4_address, ipv6_address
def get_host_info(hostname, network_details):
"""Returns host information such as the host name and network interfaces.
"""
host_info = {
"network": {
"interfaces": {
"by-mac": collections.OrderedDict(),
"by-ipv4": collections.OrderedDict(),
"by-ipv6": collections.OrderedDict(),
},
},
}
if hostname:
host_info["hostname"] = hostname
host_info["local-hostname"] = hostname
host_info["local_hostname"] = hostname
by_mac = host_info["network"]["interfaces"]["by-mac"]
by_ipv4 = host_info["network"]["interfaces"]["by-ipv4"]
by_ipv6 = host_info["network"]["interfaces"]["by-ipv6"]
if not network_details:
return host_info
default_ipv4, default_ipv6 = get_default_ip_addresses(network_details)
if default_ipv4:
host_info[LOCAL_IPV4] = default_ipv4
host_info[LOCAL_IPV4.replace('-', '_')] = default_ipv4
if default_ipv6:
host_info[LOCAL_IPV6] = default_ipv6
host_info[LOCAL_IPV6.replace('-', '_')] = default_ipv6
"""
IPv4: {
'bcast': '',
'ip': '127.0.0.1',
'mask': '255.0.0.0',
'scope': 'host',
}
IPv6: {
'ip': '::1/128',
'scope6': 'host'
}
"""
mac_by_link_names = {}
for link in network_details.links:
mac_by_link_names[link.name] = link.mac_address
for net in network_details.networks:
mac = mac_by_link_names[net.link]
# Do not bother recording localhost
if mac == "00:00:00:00:00:00":
continue
ip_net = netaddr.IPNetwork(net.address_cidr)
addr = ip_net.ip
is_v6 = addr.version == 6
is_v4 = addr.version == 4
if mac:
if mac not in by_mac:
val = {}
else:
val = by_mac[mac]
key = None
if is_v4:
key = 'ipv4'
val[key] = {
'addr': str(addr),
'netmask': str(ip_net.netmask),
'broadcast': str(ip_net.broadcast),
}
elif is_v6:
key = 'ipv6'
val[key] = {
'addr': str(addr),
'broadcast': str(ip_net.broadcast),
}
if key:
by_mac[mac] = val
if is_v4:
by_ipv4[str(addr)] = {
'mac': mac,
'netmask': str(ip_net.netmask),
'broadcast': str(ip_net.broadcast),
}
if is_v6:
by_ipv6[str(addr)] = {
'mac': mac,
'broadcast': str(ip_net.broadcast),
}
return host_info

View File

@ -99,6 +99,9 @@ The following cloud-config directives are supported:
1. path - Absolute path on disk where the content should be written.
2. content - The content which will be written in the given file.
Instance metadata can be used as template variables.
You may refer to https://cloudinit.readthedocs.io/en/latest/explanation/instancedata.html
for the example of instance data.
3. permissions - Integer representing file permissions.
4. encoding - The encoding of the data in content. Supported encodings
are: b64, base64 for base64-encoded content, gz,
@ -133,6 +136,11 @@ The following cloud-config directives are supported:
H4sIAGUfoFQC/zMxAgCIsCQyAgAAAA==
path: C:\gzip
permissions: '0644'
- path: C:\run\node-config.yaml
permissions: '0640'
content: |
---
node_ip: '{{ ds.meta_data.local_ipv4 }}'
* set_timezone - Change the underlying timezone.