nocloud: support for cloud-init network version 1
Added support for NoCloud service with Networking Config Version 1: https://cloudinit.readthedocs.io/en/latest/topics/network-config-format-v1.html Supported configuration types: Physical, Bond, Vlan and Nameserver. Unsupported configuration types: Bridge, Route. NoCloud metadata folder should contain a file network-config. If no file is found, it falls back to using the contents for network-interfaces key from the metadata file. Example: ```yaml version: 1 config: - type: physical name: interface0 mac_address: "52:54:00:12:34:00" mtu: 1450 subnets: - type: static address: 192.168.1.10 netmask: 255.255.255.0 dns_nameservers: - 192.168.1.11 - type: bond name: bond0 bond_interfaces: - gbe0 - gbe1 mac_address: "52:54:00:12:34:00" params: bond-mode: active-backup bond-lacp-rate: false mtu: 1450 subnets: - type: static address: 192.168.1.10 netmask: 255.255.255.0 dns_nameservers: - 192.168.1.11 - type: vlan name: vlan0 vlan_link: eth1 vlan_id: 150 mac_address: "52:54:00:12:34:00" mtu: 1450 subnets: - type: static address: 192.168.1.10 netmask: 255.255.255.0 dns_nameservers: - 192.168.1.11 - type: nameserver address: - 192.168.23.2 - 8.8.8.8 search: acme.local ``` Change-Id: Idc413f6f9f001b2327c33a796e9ed494be28ce26
This commit is contained in:
parent
5c3979e1ae
commit
246db50591
@ -11,13 +11,17 @@
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import netaddr
|
||||
|
||||
from oslo_log import log as oslo_logging
|
||||
|
||||
from cloudbaseinit import conf as cloudbaseinit_conf
|
||||
from cloudbaseinit import exception
|
||||
from cloudbaseinit.metadata.services import base
|
||||
from cloudbaseinit.metadata.services import baseconfigdrive
|
||||
from cloudbaseinit.models import network as network_model
|
||||
from cloudbaseinit.utils import debiface
|
||||
from cloudbaseinit.utils import network as network_utils
|
||||
from cloudbaseinit.utils import serialization
|
||||
|
||||
|
||||
@ -25,6 +29,249 @@ CONF = cloudbaseinit_conf.CONF
|
||||
LOG = oslo_logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NoCloudNetworkConfigV1Parser(object):
|
||||
NETWORK_LINK_TYPE_PHY = 'physical'
|
||||
NETWORK_LINK_TYPE_BOND = 'bond'
|
||||
NETWORK_LINK_TYPE_VLAN = 'vlan'
|
||||
NETWORK_SERVICE_NAMESERVER = 'nameserver'
|
||||
|
||||
SUPPORTED_NETWORK_CONFIG_TYPES = [
|
||||
NETWORK_LINK_TYPE_PHY,
|
||||
NETWORK_LINK_TYPE_BOND,
|
||||
NETWORK_LINK_TYPE_VLAN,
|
||||
NETWORK_SERVICE_NAMESERVER
|
||||
]
|
||||
|
||||
def _parse_subnets(self, subnets, link_name):
|
||||
networks = []
|
||||
|
||||
if not subnets or not isinstance(subnets, list):
|
||||
LOG.warning("Subnets '%s' is empty or not a list.",
|
||||
subnets)
|
||||
return networks
|
||||
|
||||
for subnet in subnets:
|
||||
if not isinstance(subnet, dict):
|
||||
LOG.warning("Subnet '%s' is not a dictionary",
|
||||
subnet)
|
||||
continue
|
||||
|
||||
if subnet.get("type") in ["dhcp", "dhcp6"]:
|
||||
continue
|
||||
|
||||
routes = []
|
||||
for route_data in subnet.get("routes", []):
|
||||
route_netmask = route_data.get("netmask")
|
||||
route_network = route_data.get("network")
|
||||
route_network_cidr = network_utils.ip_netmask_to_cidr(
|
||||
route_network, route_netmask)
|
||||
|
||||
route_gateway = route_data.get("gateway")
|
||||
route = network_model.Route(
|
||||
network_cidr=route_network_cidr,
|
||||
gateway=route_gateway
|
||||
)
|
||||
routes.append(route)
|
||||
|
||||
address_cidr = subnet.get("address")
|
||||
netmask = subnet.get("netmask")
|
||||
if netmask:
|
||||
address_cidr = network_utils.ip_netmask_to_cidr(
|
||||
address_cidr, netmask)
|
||||
|
||||
gateway = subnet.get("gateway")
|
||||
if gateway:
|
||||
# Map the gateway as a default route, depending on the
|
||||
# IP family / version (4 or 6)
|
||||
gateway_net_cidr = "0.0.0.0/0"
|
||||
if netaddr.valid_ipv6(gateway):
|
||||
gateway_net_cidr = "::/0"
|
||||
|
||||
routes.append(
|
||||
network_model.Route(
|
||||
network_cidr=gateway_net_cidr,
|
||||
gateway=gateway
|
||||
)
|
||||
)
|
||||
|
||||
networks.append(network_model.Network(
|
||||
link=link_name,
|
||||
address_cidr=address_cidr,
|
||||
dns_nameservers=subnet.get("dns_nameservers"),
|
||||
routes=routes
|
||||
))
|
||||
|
||||
return networks
|
||||
|
||||
def _parse_physical_config_item(self, item):
|
||||
if not item.get('name'):
|
||||
LOG.warning("Physical NIC does not have a name.")
|
||||
return
|
||||
|
||||
link = network_model.Link(
|
||||
id=item.get('name'),
|
||||
name=item.get('name'),
|
||||
type=network_model.LINK_TYPE_PHYSICAL,
|
||||
enabled=True,
|
||||
mac_address=item.get('mac_address'),
|
||||
mtu=item.get('mtu'),
|
||||
bond=None,
|
||||
vlan_link=None,
|
||||
vlan_id=None
|
||||
)
|
||||
|
||||
return network_model.NetworkDetailsV2(
|
||||
links=[link],
|
||||
networks=self._parse_subnets(item.get("subnets"), link.name),
|
||||
services=[]
|
||||
)
|
||||
|
||||
def _parse_bond_config_item(self, item):
|
||||
if not item.get('name'):
|
||||
LOG.warning("Bond does not have a name.")
|
||||
return
|
||||
|
||||
bond_params = item.get('params')
|
||||
if not bond_params:
|
||||
LOG.warning("Bond does not have parameters")
|
||||
return
|
||||
|
||||
bond_mode = bond_params.get('bond-mode')
|
||||
if bond_mode not in network_model.AVAILABLE_BOND_TYPES:
|
||||
raise exception.CloudbaseInitException(
|
||||
"Unsupported bond mode: %s" % bond_mode)
|
||||
|
||||
bond_lacp_rate = None
|
||||
if bond_mode == network_model.BOND_TYPE_8023AD:
|
||||
bond_lacp_rate = bond_params.get('bond-lacp-rate')
|
||||
if (bond_lacp_rate and bond_lacp_rate not in
|
||||
network_model.AVAILABLE_BOND_LACP_RATES):
|
||||
raise exception.CloudbaseInitException(
|
||||
"Unsupported bond lacp rate: %s" % bond_lacp_rate)
|
||||
|
||||
bond_xmit_hash_policy = bond_params.get('xmit_hash_policy')
|
||||
if (bond_xmit_hash_policy and bond_xmit_hash_policy not in
|
||||
network_model.AVAILABLE_BOND_LB_ALGORITHMS):
|
||||
raise exception.CloudbaseInitException(
|
||||
"Unsupported bond hash policy: %s" %
|
||||
bond_xmit_hash_policy)
|
||||
|
||||
bond_interfaces = item.get('bond_interfaces')
|
||||
|
||||
bond = network_model.Bond(
|
||||
members=bond_interfaces,
|
||||
type=bond_mode,
|
||||
lb_algorithm=bond_xmit_hash_policy,
|
||||
lacp_rate=bond_lacp_rate,
|
||||
)
|
||||
|
||||
link = network_model.Link(
|
||||
id=item.get('name'),
|
||||
name=item.get('name'),
|
||||
type=network_model.LINK_TYPE_BOND,
|
||||
enabled=True,
|
||||
mac_address=item.get('mac_address'),
|
||||
mtu=item.get('mtu'),
|
||||
bond=bond,
|
||||
vlan_link=None,
|
||||
vlan_id=None
|
||||
)
|
||||
|
||||
return network_model.NetworkDetailsV2(
|
||||
links=[link],
|
||||
networks=self._parse_subnets(item.get("subnets"), link.name),
|
||||
services=[]
|
||||
)
|
||||
|
||||
def _parse_vlan_config_item(self, item):
|
||||
if not item.get('name'):
|
||||
LOG.warning("VLAN NIC does not have a name.")
|
||||
return
|
||||
|
||||
link = network_model.Link(
|
||||
id=item.get('name'),
|
||||
name=item.get('name'),
|
||||
type=network_model.LINK_TYPE_VLAN,
|
||||
enabled=True,
|
||||
mac_address=item.get('mac_address'),
|
||||
mtu=item.get('mtu'),
|
||||
bond=None,
|
||||
vlan_link=item.get('vlan_link'),
|
||||
vlan_id=item.get('vlan_id')
|
||||
)
|
||||
|
||||
return network_model.NetworkDetailsV2(
|
||||
links=[link],
|
||||
networks=self._parse_subnets(item.get("subnets"), link.name),
|
||||
services=[]
|
||||
)
|
||||
|
||||
def _parse_nameserver_config_item(self, item):
|
||||
return network_model.NetworkDetailsV2(
|
||||
links=[],
|
||||
networks=[],
|
||||
services=[network_model.NameServerService(
|
||||
addresses=item.get('address', []),
|
||||
search=item.get('search')
|
||||
)]
|
||||
)
|
||||
|
||||
def _get_network_config_parser(self, parser_type):
|
||||
parsers = {
|
||||
self.NETWORK_LINK_TYPE_PHY: self._parse_physical_config_item,
|
||||
self.NETWORK_LINK_TYPE_BOND: self._parse_bond_config_item,
|
||||
self.NETWORK_LINK_TYPE_VLAN: self._parse_vlan_config_item,
|
||||
self.NETWORK_SERVICE_NAMESERVER: self._parse_nameserver_config_item
|
||||
}
|
||||
parser = parsers.get(parser_type)
|
||||
if not parser:
|
||||
raise exception.CloudbaseInitException(
|
||||
"Network config parser '%s' does not exist",
|
||||
parser_type)
|
||||
return parser
|
||||
|
||||
def parse(self, network_config):
|
||||
links = []
|
||||
networks = []
|
||||
services = []
|
||||
|
||||
if not network_config:
|
||||
LOG.warning("Network configuration is empty")
|
||||
return
|
||||
|
||||
if not isinstance(network_config, list):
|
||||
LOG.warning("Network config '%s' is not a list.",
|
||||
network_config)
|
||||
return
|
||||
|
||||
for network_config_item in network_config:
|
||||
if not isinstance(network_config_item, dict):
|
||||
LOG.warning("Network config item '%s' is not a dictionary",
|
||||
network_config_item)
|
||||
continue
|
||||
|
||||
net_conf_type = network_config_item.get("type")
|
||||
if net_conf_type not in self.SUPPORTED_NETWORK_CONFIG_TYPES:
|
||||
LOG.warning("Network config type '%s' is not supported",
|
||||
net_conf_type)
|
||||
continue
|
||||
|
||||
net_details = (
|
||||
self._get_network_config_parser(net_conf_type)
|
||||
(network_config_item))
|
||||
|
||||
if net_details:
|
||||
links += net_details.links
|
||||
networks += net_details.networks
|
||||
services += net_details.services
|
||||
|
||||
return network_model.NetworkDetailsV2(
|
||||
links=links,
|
||||
networks=networks,
|
||||
services=services
|
||||
)
|
||||
|
||||
|
||||
class NoCloudConfigDriveService(baseconfigdrive.BaseConfigDriveService):
|
||||
|
||||
def __init__(self):
|
||||
@ -43,7 +290,7 @@ class NoCloudConfigDriveService(baseconfigdrive.BaseConfigDriveService):
|
||||
try:
|
||||
self._meta_data = (
|
||||
serialization.parse_json_yaml(raw_meta_data))
|
||||
except base.YamlParserConfigError as ex:
|
||||
except serialization.YamlParserConfigError as ex:
|
||||
LOG.error("Metadata could not be parsed")
|
||||
LOG.exception(ex)
|
||||
|
||||
@ -68,3 +315,30 @@ class NoCloudConfigDriveService(baseconfigdrive.BaseConfigDriveService):
|
||||
return None
|
||||
|
||||
return debiface.parse(debian_net_config)
|
||||
|
||||
def get_network_details_v2(self):
|
||||
try:
|
||||
raw_network_data = self._get_cache_data("network-config",
|
||||
decode=True)
|
||||
network_data = serialization.parse_json_yaml(raw_network_data)
|
||||
if not network_data:
|
||||
LOG.info("V2 network metadata is empty")
|
||||
return
|
||||
if not isinstance(network_data, dict):
|
||||
LOG.warning("V2 network metadata is not a dictionary")
|
||||
return
|
||||
except base.NotExistingMetadataException:
|
||||
LOG.info("V2 network metadata not found")
|
||||
return
|
||||
except serialization.YamlParserConfigError:
|
||||
LOG.exception("V2 network metadata could not be deserialized")
|
||||
return
|
||||
|
||||
network_data_version = network_data.get("version")
|
||||
if network_data_version != 1:
|
||||
LOG.error("Network data version '%s' is not supported",
|
||||
network_data_version)
|
||||
return
|
||||
|
||||
network_config_parser = NoCloudNetworkConfigV1Parser()
|
||||
return network_config_parser.parse(network_data.get("config"))
|
||||
|
@ -54,6 +54,10 @@ AVAILABLE_BOND_LB_ALGORITHMS = [
|
||||
BOND_LACP_RATE_SLOW = "slow"
|
||||
BOND_LACP_RATE_FAST = "fast"
|
||||
|
||||
AVAILABLE_BOND_LACP_RATES = [
|
||||
BOND_LACP_RATE_SLOW,
|
||||
BOND_LACP_RATE_FAST
|
||||
]
|
||||
|
||||
NetworkDetails = collections.namedtuple(
|
||||
"NetworkDetails",
|
||||
|
@ -12,7 +12,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
import ddt
|
||||
import importlib
|
||||
import os
|
||||
import unittest
|
||||
@ -22,11 +22,169 @@ try:
|
||||
except ImportError:
|
||||
import mock
|
||||
|
||||
from cloudbaseinit.metadata.services import base
|
||||
from cloudbaseinit.models import network as nm
|
||||
from cloudbaseinit.tests import testutils
|
||||
from cloudbaseinit.utils import serialization
|
||||
|
||||
MODULE_PATH = "cloudbaseinit.metadata.services.nocloudservice"
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestNoCloudNetworkConfigV1Parser(unittest.TestCase):
|
||||
def setUp(self):
|
||||
module = importlib.import_module(MODULE_PATH)
|
||||
self._parser = module.NoCloudNetworkConfigV1Parser()
|
||||
self.snatcher = testutils.LogSnatcher(MODULE_PATH)
|
||||
|
||||
@ddt.data(('', ('Network configuration is empty', None)),
|
||||
('{t: 1}',
|
||||
("Network config '{'t': 1}' is not a list", None)),
|
||||
('["1"]',
|
||||
("Network config item '1' is not a dictionary",
|
||||
nm.NetworkDetailsV2(links=[], networks=[], services=[]))),
|
||||
('[{"type": "router"}]',
|
||||
("Network config type 'router' is not supported",
|
||||
nm.NetworkDetailsV2(links=[], networks=[], services=[]))))
|
||||
@ddt.unpack
|
||||
def test_parse_empty_result(self, input, expected_result):
|
||||
|
||||
with self.snatcher:
|
||||
result = self._parser.parse(serialization.parse_json_yaml(input))
|
||||
|
||||
self.assertEqual(True, expected_result[0] in self.snatcher.output[0])
|
||||
self.assertEqual(result, expected_result[1])
|
||||
|
||||
def test_network_details_v2(self):
|
||||
expected_bond = nm.Bond(
|
||||
members=["gbe0", "gbe1"],
|
||||
type=nm.BOND_TYPE_ACTIVE_BACKUP,
|
||||
lb_algorithm=None,
|
||||
lacp_rate=None,
|
||||
)
|
||||
expected_link_bond = nm.Link(
|
||||
id='bond0',
|
||||
name='bond0',
|
||||
type=nm.LINK_TYPE_BOND,
|
||||
enabled=True,
|
||||
mac_address="52:54:00:12:34:00",
|
||||
mtu=1450,
|
||||
bond=expected_bond,
|
||||
vlan_link=None,
|
||||
vlan_id=None,
|
||||
)
|
||||
expected_link = nm.Link(
|
||||
id='interface0',
|
||||
name='interface0',
|
||||
type=nm.LINK_TYPE_PHYSICAL,
|
||||
enabled=True,
|
||||
mac_address="52:54:00:12:34:00",
|
||||
mtu=1450,
|
||||
bond=None,
|
||||
vlan_link=None,
|
||||
vlan_id=None,
|
||||
)
|
||||
expected_link_vlan = nm.Link(
|
||||
id='vlan0',
|
||||
name='vlan0',
|
||||
type=nm.LINK_TYPE_VLAN,
|
||||
enabled=True,
|
||||
mac_address="52:54:00:12:34:00",
|
||||
mtu=1450,
|
||||
bond=None,
|
||||
vlan_link='eth1',
|
||||
vlan_id=150,
|
||||
)
|
||||
expected_network = nm.Network(
|
||||
link='interface0',
|
||||
address_cidr='192.168.1.10/24',
|
||||
dns_nameservers=['192.168.1.11'],
|
||||
routes=[
|
||||
nm.Route(network_cidr='0.0.0.0/0',
|
||||
gateway="192.168.1.1")
|
||||
]
|
||||
)
|
||||
|
||||
expected_network_bond = nm.Network(
|
||||
link='bond0',
|
||||
address_cidr='192.168.1.10/24',
|
||||
dns_nameservers=['192.168.1.11'],
|
||||
routes=[],
|
||||
)
|
||||
|
||||
expected_network_vlan = nm.Network(
|
||||
link='vlan0',
|
||||
address_cidr='192.168.1.10/24',
|
||||
dns_nameservers=['192.168.1.11'],
|
||||
routes=[],
|
||||
)
|
||||
expected_nameservers = nm.NameServerService(
|
||||
addresses=['192.168.23.2', '8.8.8.8'],
|
||||
search='acme.local')
|
||||
|
||||
parser_data = """
|
||||
- type: physical
|
||||
name: interface0
|
||||
mac_address: "52:54:00:12:34:00"
|
||||
mtu: 1450
|
||||
subnets:
|
||||
- type: static
|
||||
address: 192.168.1.10
|
||||
netmask: 255.255.255.0
|
||||
gateway: 192.168.1.1
|
||||
dns_nameservers:
|
||||
- 192.168.1.11
|
||||
- type: bond
|
||||
name: bond0
|
||||
bond_interfaces:
|
||||
- gbe0
|
||||
- gbe1
|
||||
mac_address: "52:54:00:12:34:00"
|
||||
params:
|
||||
bond-mode: active-backup
|
||||
bond-lacp-rate: false
|
||||
mtu: 1450
|
||||
subnets:
|
||||
- type: static
|
||||
address: 192.168.1.10
|
||||
netmask: 255.255.255.0
|
||||
dns_nameservers:
|
||||
- 192.168.1.11
|
||||
- type: vlan
|
||||
name: vlan0
|
||||
vlan_link: eth1
|
||||
vlan_id: 150
|
||||
mac_address: "52:54:00:12:34:00"
|
||||
mtu: 1450
|
||||
subnets:
|
||||
- type: static
|
||||
address: 192.168.1.10
|
||||
netmask: 255.255.255.0
|
||||
dns_nameservers:
|
||||
- 192.168.1.11
|
||||
- type: nameserver
|
||||
address:
|
||||
- 192.168.23.2
|
||||
- 8.8.8.8
|
||||
search: acme.local
|
||||
"""
|
||||
|
||||
result = self._parser.parse(
|
||||
serialization.parse_json_yaml(parser_data))
|
||||
|
||||
self.assertEqual(result.links[0], expected_link)
|
||||
self.assertEqual(result.networks[0], expected_network)
|
||||
|
||||
self.assertEqual(result.links[1], expected_link_bond)
|
||||
self.assertEqual(result.networks[1], expected_network_bond)
|
||||
|
||||
self.assertEqual(result.links[2], expected_link_vlan)
|
||||
self.assertEqual(result.networks[2], expected_network_vlan)
|
||||
|
||||
self.assertEqual(result.services[0], expected_nameservers)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestNoCloudConfigDriveService(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
@ -88,3 +246,27 @@ class TestNoCloudConfigDriveService(unittest.TestCase):
|
||||
}
|
||||
result = self._config_drive.get_public_keys()
|
||||
self.assertEqual(result, expected_result)
|
||||
|
||||
@ddt.data(('', ('V2 network metadata is empty', None)),
|
||||
('1', ('V2 network metadata is not a dictionary', None)),
|
||||
('{}', ('V2 network metadata is empty', None)),
|
||||
('{}}', ('V2 network metadata could not be deserialized', None)),
|
||||
('{version: 2}', ("Network data version '2' is not supported",
|
||||
None)),
|
||||
(base.NotExistingMetadataException('exc'),
|
||||
('V2 network metadata not found', True)))
|
||||
@ddt.unpack
|
||||
@mock.patch(MODULE_PATH + '.NoCloudConfigDriveService._get_cache_data')
|
||||
def test_network_details_v2_empty_result(self, input, expected_result,
|
||||
mock_get_cache_data):
|
||||
if expected_result[1]:
|
||||
mock_get_cache_data.side_effect = [input]
|
||||
else:
|
||||
mock_get_cache_data.return_value = input
|
||||
with self.snatcher:
|
||||
result = self._config_drive.get_network_details_v2()
|
||||
self.assertEqual(True, expected_result[0] in self.snatcher.output[0])
|
||||
self.assertEqual(result, None)
|
||||
|
||||
mock_get_cache_data.assert_called_with(
|
||||
"network-config", decode=True)
|
||||
|
@ -138,7 +138,9 @@ Capabilities:
|
||||
* instance id
|
||||
* hostname
|
||||
* public keys
|
||||
* static network configuration (Debian format)
|
||||
* static network configuration (Debian and `network config v1
|
||||
<https://cloudinit.readthedocs.io/en/latest/topics/network-config-format-v1.html>`_
|
||||
formats)
|
||||
* user data
|
||||
|
||||
Config options for `config_drive` section:
|
||||
@ -164,6 +166,67 @@ Example metadata:
|
||||
hwaddress ether 00:11:22:33:44:55
|
||||
hostname: windowshost1
|
||||
|
||||
Cloud-init's `network config v1
|
||||
<https://cloudinit.readthedocs.io/en/latest/topics/network-config-format-v1.html>`_
|
||||
format can be used to configure static network configuration.
|
||||
The configuration file should be named `network-config` and should be present
|
||||
at the same folder level with the `meta-data` and `user-data` file.
|
||||
If no `network-config` is found, cloudbase-init will use the `network-interfaces`
|
||||
value from the metadata (if any).
|
||||
|
||||
The following network config types are implemented: physical, bond, vlan and
|
||||
nameserver.
|
||||
Unsupported config types: bridge and route.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
version: 1
|
||||
config:
|
||||
- type: physical
|
||||
name: interface0
|
||||
mac_address: "52:54:00:12:34:00"
|
||||
mtu: 1450
|
||||
subnets:
|
||||
- type: static
|
||||
address: 192.168.1.10
|
||||
netmask: 255.255.255.0
|
||||
dns_nameservers:
|
||||
- 192.168.1.11
|
||||
- type: bond
|
||||
name: bond0
|
||||
bond_interfaces:
|
||||
- gbe0
|
||||
- gbe1
|
||||
mac_address: "52:54:00:12:34:00"
|
||||
params:
|
||||
bond-mode: active-backup
|
||||
bond-lacp-rate: false
|
||||
mtu: 1450
|
||||
subnets:
|
||||
- type: static
|
||||
address: 192.168.1.10
|
||||
netmask: 255.255.255.0
|
||||
dns_nameservers:
|
||||
- 192.168.1.11
|
||||
- type: vlan
|
||||
name: vlan0
|
||||
vlan_link: eth1
|
||||
vlan_id: 150
|
||||
mac_address: "52:54:00:12:34:00"
|
||||
mtu: 1450
|
||||
subnets:
|
||||
- type: static
|
||||
address: 192.168.1.10
|
||||
netmask: 255.255.255.0
|
||||
dns_nameservers:
|
||||
- 192.168.1.11
|
||||
- type: nameserver
|
||||
address:
|
||||
- 192.168.23.2
|
||||
- 8.8.8.8
|
||||
search: acme.local
|
||||
|
||||
More information on the NoCloud metadata service specifications can be found
|
||||
`here <https://cloudinit.readthedocs.io/en/latest/topics/datasources/nocloud.html>`_.
|
||||
|
Loading…
x
Reference in New Issue
Block a user