cloud-init-dev team mailing list archive
-
cloud-init-dev team
-
Mailing list archive
-
Message #02355
[Merge] ~smoser/cloud-init:cleanup/mask2cidr into cloud-init:master
Scott Moser has proposed merging ~smoser/cloud-init:cleanup/mask2cidr into cloud-init:master.
Commit message:
place holder
Requested reviews:
cloud-init commiters (cloud-init-dev)
For more details, see:
https://code.launchpad.net/~smoser/cloud-init/+git/cloud-init/+merge/324677
--
Your team cloud-init commiters is requested to review the proposed merge of ~smoser/cloud-init:cleanup/mask2cidr into cloud-init:master.
diff --git a/cloudinit/net/eni.py b/cloudinit/net/eni.py
index 9819d4f..6d02d22 100644
--- a/cloudinit/net/eni.py
+++ b/cloudinit/net/eni.py
@@ -46,6 +46,10 @@ def _iface_add_subnet(iface, subnet):
'dns_nameservers',
]
for key, value in subnet.items():
+ if key == 'netmask':
+ continue
+ if key == 'address':
+ value = "%s/%s" % (subnet['address'], subnet['prefix'])
if value and key in valid_map:
if type(value) == list:
value = " ".join(value)
diff --git a/cloudinit/net/netplan.py b/cloudinit/net/netplan.py
index d7ddf0c..6754330 100644
--- a/cloudinit/net/netplan.py
+++ b/cloudinit/net/netplan.py
@@ -119,8 +119,8 @@ def _extract_addresses(config, entry):
entry.update({sn_type: True})
elif sn_type in ['static']:
addr = "%s" % subnet.get('address')
- if 'netmask' in subnet:
- addr += "/%s" % subnet.get('netmask')
+ if 'prefix' in subnet:
+ addr += "/%d" % subnet.get('prefix')
if 'gateway' in subnet and subnet.get('gateway'):
gateway = subnet.get('gateway')
if ":" in gateway:
@@ -138,7 +138,7 @@ def _extract_addresses(config, entry):
entry.update({mtukey: subnet.get('mtu')})
for route in subnet.get('routes', []):
to_net = "%s/%s" % (route.get('network'),
- route.get('netmask'))
+ route.get('prefix'))
route = {
'via': route.get('gateway'),
'to': to_net,
diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py
index db3c357..9def76d 100644
--- a/cloudinit/net/network_state.py
+++ b/cloudinit/net/network_state.py
@@ -289,19 +289,16 @@ class NetworkStateInterpreter(object):
iface.update({param: val})
# convert subnet ipv6 netmask to cidr as needed
- subnets = command.get('subnets')
- if subnets:
+ subnets = _normalize_subnets(command.get('subnets'))
+ print("normalized subnets: %s" % subnets)
+
+ # automatically set 'use_ipv6' if any addresses are ipv6
+ if not self.use_ipv6:
for subnet in subnets:
- if subnet['type'] == 'static':
- if ':' in subnet['address']:
- self.use_ipv6 = True
- if 'netmask' in subnet and ':' in subnet['address']:
- subnet['netmask'] = mask2cidr(subnet['netmask'])
- for route in subnet.get('routes', []):
- if 'netmask' in route:
- route['netmask'] = mask2cidr(route['netmask'])
- elif subnet['type'].endswith('6'):
+ if (subnet.get('type').endswith('6') or
+ is_ipv6_addr(subnet.get('address'))):
self.use_ipv6 = True
+ break
iface.update({
'name': command.get('name'),
@@ -692,6 +689,122 @@ class NetworkStateInterpreter(object):
return subnets
+def _normalize_subnet(subnet):
+ # Prune all keys with None values.
+ subnet = copy.deepcopy(subnet)
+ normal_subnet = dict((k, v) for k, v in subnet.items() if v)
+
+ if subnet.get('type') == 'static':
+ normal_subnet.update(
+ _normalize_net_keys(normal_subnet, address_keys=('address',)))
+ normal_subnet['routes'] = [_normalize_route(r)
+ for r in subnet.get('routes', [])]
+ return normal_subnet
+
+
+def _normalize_net_keys(network, address_keys=()):
+
+ """Normalize dictionary network keys returning prefix and address keys.
+
+ @param network: A dict of network-related definition containing prefix,
+ netmask and address_keys.
+ @param address_keys: A tuple of keys to search for representing the address
+ or cidr. The first address_key discovered will be used for
+ normalization.
+
+ @returns: A dict containing normalized prefix and matching addr_key.
+ """
+ network = copy.deepcopy(network)
+ net = dict((k, v) for k, v in network.items() if v)
+ addr_key = None
+ for key in address_keys:
+ if net.get(key):
+ addr_key = key
+ break
+ if not addr_key:
+ message = (
+ 'No config network address keys [%s] found in %s' %
+ (','.join(address_keys), network))
+ LOG.error(message)
+ raise ValueError(message)
+
+ addr = net.get(addr_key)
+ ipv6 = is_ipv6_addr(addr)
+ netmask = net.get('netmask')
+ if "/" in addr:
+ toks = addr.split("/", 2)
+ net[addr_key] = toks[0]
+ # If prefix is defined by the user but addr_key is a CIDR, we
+ # silently overwrite the user's original prefix here. We should
+ # log a warning.
+ net['prefix'] = toks[1]
+ try:
+ net['prefix'] = int(toks[1])
+ except ValueError:
+ # this supports input of <address>/255.255.255.0
+ net['prefix'] = mask_to_net_prefix(toks[1])
+
+ elif netmask:
+ net['prefix'] = mask_to_net_prefix(netmask)
+ else:
+ net['prefix'] = 64 if ipv6 else 24
+
+ if ipv6:
+ if 'netmask' in net:
+ del net['netmask']
+ else:
+ net['netmask'] = net_prefix_to_ipv4_mask(net['prefix'])
+
+ prefix = net['prefix']
+ if prefix:
+ try:
+ net['prefix'] = int(prefix)
+ except ValueError:
+ raise TypeError(
+ 'Network config prefix {} is not an integer'.format(prefix))
+ return net
+
+
+def _normalize_route(route):
+ """normalize a route.
+ return a dictionary with only:
+ 'type': 'route' (only present if it was present in input)
+ 'network': the network portion of the route as a string.
+ 'prefix': the network prefix for address as an integer.
+ 'metric': integer metric (only if present in input).
+ 'netmask': netmask (string) equivalent to prefix if ipv4.
+ """
+ route = copy.deepcopy(route)
+ print("normalizing %s" % route)
+ # Prune None-value keys
+ normal_route = dict((k, v) for k, v in route.items() if v)
+ normal_route.update(
+ _normalize_net_keys(
+ normal_route, address_keys=('network', 'destination')))
+
+ metric = normal_route.get('metric')
+ if metric:
+ try:
+ normal_route['metric'] = int(metric)
+ except ValueError:
+ raise TypeError(
+ 'Route config metric {} is not an integer'.format(metric))
+ print("normaled to %s" % normal_route)
+ return normal_route
+
+
+def _normalize_subnets(subnets):
+ if not subnets:
+ subnets = []
+ return [_normalize_subnet(s) for s in subnets]
+
+
+def is_ipv6_addr(address):
+ if not address:
+ return False
+ return ":" in address
+
+
def subnet_is_ipv6(subnet):
"""Common helper for checking network_state subnets for ipv6."""
# 'static6' or 'dhcp6'
@@ -703,22 +816,45 @@ def subnet_is_ipv6(subnet):
return False
-def cidr2mask(cidr):
+def net_prefix_to_ipv4_mask(prefix):
+ """Convert a network prefix to an ipv4 netmask.
+
+ This is the inverse of ipv4_mask_to_net_prefix.
+ 24 -> "255.255.255.0"
+ Also supports input as a string."""
+
mask = [0, 0, 0, 0]
- for i in list(range(0, cidr)):
+ for i in list(range(0, int(prefix))):
idx = int(i / 8)
mask[idx] = mask[idx] + (1 << (7 - i % 8))
return ".".join([str(x) for x in mask])
-def ipv4mask2cidr(mask):
+def ipv4_mask_to_net_prefix(mask, strict=True):
+ """Convert an ipv4 netmask into a network prefix length.
+ "255.255.255.0" => 24
+ """
+ if not isinstance(mask, six.string_types):
+ raise TypeError("netmask '%s' is not a string")
+
if '.' not in mask:
+ if strict:
+ raise ValueError("netmask '%s' does not contain a '.'" % mask)
return mask
- return sum([bin(int(x)).count('1') for x in mask.split('.')])
+ toks = mask.split(".")
+ if len(toks) != 4:
+ raise ValueError("netmask '%s' had only %d parts" % (mask, len(toks)))
+ return sum([bin(int(x)).count('1') for x in toks])
-def ipv6mask2cidr(mask):
+
+def ipv6_mask_to_net_prefix(mask, strict=True):
+ """Convert an ipv6 netmask (very uncommon) into a network prefix length."""
+ if not isinstance(mask, six.string_types):
+ raise TypeError("mask '%s' is not a string")
if ':' not in mask:
+ if strict:
+ raise ValueError("mask '%s' does not have a ':'")
return mask
bitCount = [0, 0x8000, 0xc000, 0xe000, 0xf000, 0xf800, 0xfc00, 0xfe00,
@@ -733,6 +869,28 @@ def ipv6mask2cidr(mask):
return cidr
+def mask_to_net_prefix(mask):
+ """Return the network prefix for the netmask provided.
+
+ Supports ipv4 or ipv6 netmasks."""
+ if ':' in str(mask):
+ return ipv6_mask_to_net_prefix(mask)
+ elif '.' in str(mask):
+ return ipv4_mask_to_net_prefix(mask)
+
+
+def ipv4mask2cidr(mask):
+ return ipv4_mask_to_net_prefix(mask, strict=False)
+
+
+def cidr2mask(cidr):
+ return net_prefix_to_ipv4_mask(cidr)
+
+
+def ipv6mask2cidr(mask):
+ return ipv6_mask_to_net_prefix(mask, strict=False)
+
+
def mask2cidr(mask):
if ':' in mask:
return ipv6mask2cidr(mask)
@@ -741,4 +899,5 @@ def mask2cidr(mask):
else:
return mask
+
# vi: ts=4 expandtab
diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py
index 58c5713..3f917ed 100644
--- a/cloudinit/net/sysconfig.py
+++ b/cloudinit/net/sysconfig.py
@@ -9,7 +9,7 @@ from cloudinit.distros.parsers import resolv_conf
from cloudinit import util
from . import renderer
-from .network_state import subnet_is_ipv6
+from .network_state import subnet_is_ipv6, net_prefix_to_ipv4_mask
def _make_header(sep='#'):
@@ -26,11 +26,9 @@ def _make_header(sep='#'):
def _is_default_route(route):
- if route['network'] == '::' and route['netmask'] == 0:
- return True
- if route['network'] == '0.0.0.0' and route['netmask'] == '0.0.0.0':
- return True
- return False
+ print("checking idr route=%s" % route)
+ default_nets = ('::', '0.0.0.0')
+ return route['prefix'] == 0 and route['network'] in default_nets
def _quote_value(value):
@@ -323,22 +321,18 @@ class Renderer(renderer.Renderer):
" " + ipv6_cidr)
else:
ipv4_index = ipv4_index + 1
- if ipv4_index == 0:
- iface_cfg['IPADDR'] = subnet['address']
- if 'netmask' in subnet:
- iface_cfg['NETMASK'] = subnet['netmask']
- else:
- iface_cfg['IPADDR' + str(ipv4_index)] = \
- subnet['address']
- if 'netmask' in subnet:
- iface_cfg['NETMASK' + str(ipv4_index)] = \
- subnet['netmask']
+ suff = "" if ipv4_index == 0 else str(ipv4_index)
+ iface_cfg['IPADDR' + suff] = subnet['address']
+ iface_cfg['NETMASK' + suff] = \
+ net_prefix_to_ipv4_mask(subnet['prefix'])
@classmethod
def _render_subnet_routes(cls, iface_cfg, route_cfg, subnets):
for i, subnet in enumerate(subnets, start=len(iface_cfg.children)):
+ print("subnet=%s" % subnet)
for route in subnet.get('routes', []):
is_ipv6 = subnet.get('ipv6')
+ print("route=%s" % route)
if _is_default_route(route):
if (
diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py
index fd7c051..83580cc 100644
--- a/tests/unittests/test_distros/test_netconfig.py
+++ b/tests/unittests/test_distros/test_netconfig.py
@@ -92,10 +92,9 @@ iface lo inet loopback
auto eth0
iface eth0 inet static
- address 192.168.1.5
+ address 192.168.1.5/24
broadcast 192.168.1.0
gateway 192.168.1.254
- netmask 255.255.255.0
auto eth1
iface eth1 inet dhcp
@@ -127,7 +126,7 @@ network:
ethernets:
eth0:
addresses:
- - 192.168.1.5/255.255.255.0
+ - 192.168.1.5/24
gateway4: 192.168.1.254
eth1:
dhcp4: true
@@ -156,7 +155,7 @@ network:
ethernets:
eth7:
addresses:
- - 192.168.1.5/255.255.255.0
+ - 192.168.1.5/24
gateway4: 192.168.1.254
eth9:
dhcp4: true
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
index 5169821..a5a43bd 100644
--- a/tests/unittests/test_net.py
+++ b/tests/unittests/test_net.py
@@ -341,17 +341,15 @@ iface lo inet loopback
auto eth0
iface eth0 inet static
- address 1.2.3.12
+ address 1.2.3.12/29
broadcast 1.2.3.15
dns-nameservers 69.9.160.191 69.9.191.4
gateway 1.2.3.9
- netmask 255.255.255.248
auto eth1
iface eth1 inet static
- address 10.248.2.4
+ address 10.248.2.4/29
broadcast 10.248.2.7
- netmask 255.255.255.248
""".lstrip()
NETWORK_CONFIGS = {
@@ -406,7 +404,7 @@ NETWORK_CONFIGS = {
- sach.maas
- wark.maas
routes:
- - to: 0.0.0.0/0.0.0.0
+ - to: 0.0.0.0/0
via: 65.61.151.37
set-name: eth99
""").rstrip(' '),
@@ -890,6 +888,7 @@ USERCTL=no
macs = {'fa:16:3e:ed:9a:59': 'eth0'}
render_dir = self.tmp_dir()
network_cfg = openstack.convert_net_json(net_json, known_macs=macs)
+ print("network_cfg=%s" % network_cfg)
ns = network_state.parse_net_config_data(network_cfg,
skip_broken=False)
renderer = sysconfig.Renderer()
Follow ups
-
Re: [Merge] ~smoser/cloud-init:cleanup/mask2cidr into cloud-init:master
From: Server Team CI bot, 2017-06-08
-
Re: [Merge] ~smoser/cloud-init:cleanup/mask2cidr into cloud-init:master
From: Scott Moser, 2017-06-06
-
Re: [Merge] ~smoser/cloud-init:cleanup/mask2cidr into cloud-init:master
From: Server Team CI bot, 2017-06-02
-
Re: [Merge] ~smoser/cloud-init:cleanup/mask2cidr into cloud-init:master
From: Scott Moser, 2017-06-02
-
Re: [Merge] ~smoser/cloud-init:cleanup/mask2cidr into cloud-init:master
From: Ryan Harper, 2017-06-02
-
[Merge] ~smoser/cloud-init:cleanup/mask2cidr into cloud-init:master
From: Scott Moser, 2017-06-01
-
Re: [Merge] ~smoser/cloud-init:cleanup/mask2cidr into cloud-init:master
From: Server Team CI bot, 2017-06-01
-
Re: [Merge] ~smoser/cloud-init:cleanup/mask2cidr into cloud-init:master
From: Scott Moser, 2017-06-01
-
Re: [Merge] ~smoser/cloud-init:cleanup/mask2cidr into cloud-init:master
From: Server Team CI bot, 2017-06-01
-
Re: [Merge] ~smoser/cloud-init:cleanup/mask2cidr into cloud-init:master
From: Scott Moser, 2017-06-01
-
Re: [Merge] ~smoser/cloud-init:cleanup/mask2cidr into cloud-init:master
From: Ryan Harper, 2017-06-01
-
Re: [Merge] ~smoser/cloud-init:cleanup/mask2cidr into cloud-init:master
From: Scott Moser, 2017-06-01
-
Re: [Merge] ~smoser/cloud-init:cleanup/mask2cidr into cloud-init:master
From: Scott Moser, 2017-06-01
-
Re: [Merge] ~smoser/cloud-init:cleanup/mask2cidr into cloud-init:master
From: Scott Moser, 2017-06-01
-
Re: [Merge] ~smoser/cloud-init:cleanup/mask2cidr into cloud-init:master
From: Chad Smith, 2017-05-31
-
Re: [Merge] ~smoser/cloud-init:cleanup/mask2cidr into cloud-init:master
From: Server Team CI bot, 2017-05-26
-
Re: [Merge] ~smoser/cloud-init:cleanup/mask2cidr into cloud-init:master
From: Server Team CI bot, 2017-05-26
-
Re: [Merge] ~smoser/cloud-init:cleanup/mask2cidr into cloud-init:master
From: Ryan Harper, 2017-05-26
-
Re: [Merge] ~smoser/cloud-init:cleanup/mask2cidr into cloud-init:master
From: Server Team CI bot, 2017-05-26