Skip to content

Commit 4c1550b

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Add network config support to VMware guest info service"
2 parents 1adf31b + f0fe66b commit 4c1550b

File tree

5 files changed

+789
-78
lines changed

5 files changed

+789
-78
lines changed

cloudbaseinit/metadata/services/nocloudservice.py

Lines changed: 266 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1212
# License for the specific language governing permissions and limitations
1313
# under the License.
14+
15+
import copy
1416
import netaddr
1517

1618
from oslo_log import log as oslo_logging
@@ -235,6 +237,10 @@ def parse(self, network_config):
235237
networks = []
236238
services = []
237239

240+
network_config = network_config.get('network') \
241+
if network_config else {}
242+
network_config = network_config.get('config') \
243+
if network_config else None
238244
if not network_config:
239245
LOG.warning("Network configuration is empty")
240246
return
@@ -272,6 +278,265 @@ def parse(self, network_config):
272278
)
273279

274280

281+
class NoCloudNetworkConfigV2Parser(object):
282+
DEFAULT_GATEWAY_CIDR_IPV4 = u"0.0.0.0/0"
283+
DEFAULT_GATEWAY_CIDR_IPV6 = u"::/0"
284+
285+
NETWORK_LINK_TYPE_ETHERNET = 'ethernet'
286+
NETWORK_LINK_TYPE_BOND = 'bond'
287+
NETWORK_LINK_TYPE_VLAN = 'vlan'
288+
NETWORK_LINK_TYPE_BRIDGE = 'bridge'
289+
290+
SUPPORTED_NETWORK_CONFIG_TYPES = {
291+
NETWORK_LINK_TYPE_ETHERNET: 'ethernets',
292+
NETWORK_LINK_TYPE_BOND: 'bonds',
293+
NETWORK_LINK_TYPE_VLAN: 'vlans',
294+
}
295+
296+
def _parse_mac_address(self, item):
297+
return item.get("match", {}).get("macaddress")
298+
299+
def _parse_addresses(self, item, link_name):
300+
networks = []
301+
services = []
302+
303+
routes = []
304+
# handle route config in deprecated gateway4/gateway6
305+
gateway4 = item.get("gateway4")
306+
gateway6 = item.get("gateway6")
307+
default_route = None
308+
if gateway6 and netaddr.valid_ipv6(gateway6):
309+
default_route = network_model.Route(
310+
network_cidr=self.DEFAULT_GATEWAY_CIDR_IPV6,
311+
gateway=gateway6)
312+
elif gateway4 and netaddr.valid_ipv4(gateway4):
313+
default_route = network_model.Route(
314+
network_cidr=self.DEFAULT_GATEWAY_CIDR_IPV4,
315+
gateway=gateway4)
316+
if default_route:
317+
routes.append(default_route)
318+
319+
# netplan format config
320+
routes_config = item.get("routes", {})
321+
for route_config in routes_config:
322+
network_cidr = route_config.get("to")
323+
gateway = route_config.get("via")
324+
if network_cidr.lower() == "default":
325+
if netaddr.valid_ipv6(gateway):
326+
network_cidr = self.DEFAULT_GATEWAY_CIDR_IPV6
327+
else:
328+
network_cidr = self.DEFAULT_GATEWAY_CIDR_IPV4
329+
route = network_model.Route(
330+
network_cidr=network_cidr,
331+
gateway=gateway)
332+
routes.append(route)
333+
334+
nameservers = item.get("nameservers")
335+
nameserver_addresses = nameservers.get("addresses", []) \
336+
if nameservers else []
337+
searches = nameservers.get("search", [])
338+
service = network_model.NameServerService(
339+
addresses=nameserver_addresses,
340+
search=','.join(searches) if searches else None,
341+
)
342+
services.append(service)
343+
344+
addresses = item.get("addresses", [])
345+
for addr in addresses:
346+
network = network_model.Network(
347+
link=link_name,
348+
address_cidr=addr,
349+
dns_nameservers=nameserver_addresses,
350+
routes=routes
351+
)
352+
networks.append(network)
353+
354+
return networks, services
355+
356+
def _parse_ethernet_config_item(self, item):
357+
if not item.get('name'):
358+
LOG.warning("Ethernet does not have a name.")
359+
return
360+
361+
name = item.get('name')
362+
eth_name = item.get("set-name", name)
363+
link = network_model.Link(
364+
id=name,
365+
name=eth_name,
366+
type=network_model.LINK_TYPE_PHYSICAL,
367+
enabled=True,
368+
mac_address=self._parse_mac_address(item),
369+
mtu=item.get('mtu'),
370+
bond=None,
371+
vlan_link=None,
372+
vlan_id=None
373+
)
374+
375+
networks, services = self._parse_addresses(item, link.name)
376+
return network_model.NetworkDetailsV2(
377+
links=[link],
378+
networks=networks,
379+
services=services,
380+
)
381+
382+
def _parse_bond_config_item(self, item):
383+
if not item.get('name'):
384+
LOG.warning("Bond does not have a name.")
385+
return
386+
387+
bond_params = item.get('parameters')
388+
if not bond_params:
389+
LOG.warning("Bond does not have parameters")
390+
return
391+
392+
bond_mode = bond_params.get('mode')
393+
if bond_mode not in network_model.AVAILABLE_BOND_TYPES:
394+
raise exception.CloudbaseInitException(
395+
"Unsupported bond mode: %s" % bond_mode)
396+
397+
bond_lacp_rate = None
398+
if bond_mode == network_model.BOND_TYPE_8023AD:
399+
bond_lacp_rate = bond_params.get('lacp-rate')
400+
if (bond_lacp_rate and bond_lacp_rate not in
401+
network_model.AVAILABLE_BOND_LACP_RATES):
402+
raise exception.CloudbaseInitException(
403+
"Unsupported bond lacp rate: %s" % bond_lacp_rate)
404+
405+
bond_xmit_hash_policy = bond_params.get('transmit-hash-policy')
406+
if (bond_xmit_hash_policy and bond_xmit_hash_policy not in
407+
network_model.AVAILABLE_BOND_LB_ALGORITHMS):
408+
raise exception.CloudbaseInitException(
409+
"Unsupported bond hash policy: %s" %
410+
bond_xmit_hash_policy)
411+
412+
bond_interfaces = item.get('interfaces')
413+
414+
bond = network_model.Bond(
415+
members=bond_interfaces,
416+
type=bond_mode,
417+
lb_algorithm=bond_xmit_hash_policy,
418+
lacp_rate=bond_lacp_rate,
419+
)
420+
421+
link = network_model.Link(
422+
id=item.get('name'),
423+
name=item.get('name'),
424+
type=network_model.LINK_TYPE_BOND,
425+
enabled=True,
426+
mac_address=self._parse_mac_address(item),
427+
mtu=item.get('mtu'),
428+
bond=bond,
429+
vlan_link=None,
430+
vlan_id=None
431+
)
432+
433+
networks, services = self._parse_addresses(item, link.name)
434+
return network_model.NetworkDetailsV2(
435+
links=[link],
436+
networks=networks,
437+
services=services
438+
)
439+
440+
def _parse_vlan_config_item(self, item):
441+
if not item.get('name'):
442+
LOG.warning("VLAN NIC does not have a name.")
443+
return
444+
445+
link = network_model.Link(
446+
id=item.get('name'),
447+
name=item.get('name'),
448+
type=network_model.LINK_TYPE_VLAN,
449+
enabled=True,
450+
mac_address=self._parse_mac_address(item),
451+
mtu=item.get('mtu'),
452+
bond=None,
453+
vlan_link=item.get('link'),
454+
vlan_id=item.get('id')
455+
)
456+
457+
networks, services = self._parse_addresses(item, link.name)
458+
return network_model.NetworkDetailsV2(
459+
links=[link],
460+
networks=networks,
461+
services=services,
462+
)
463+
464+
def _get_network_config_parser(self, parser_type):
465+
parsers = {
466+
self.NETWORK_LINK_TYPE_ETHERNET: self._parse_ethernet_config_item,
467+
self.NETWORK_LINK_TYPE_BOND: self._parse_bond_config_item,
468+
self.NETWORK_LINK_TYPE_VLAN: self._parse_vlan_config_item,
469+
}
470+
parser = parsers.get(parser_type)
471+
if not parser:
472+
raise exception.CloudbaseInitException(
473+
"Network config parser '%s' does not exist",
474+
parser_type)
475+
return parser
476+
477+
def parse(self, network_config):
478+
links = []
479+
networks = []
480+
services = []
481+
482+
network_config = network_config.get('network') \
483+
if network_config else {}
484+
if not network_config:
485+
LOG.warning("Network configuration is empty")
486+
return
487+
488+
if not isinstance(network_config, dict):
489+
LOG.warning("Network config '%s' is not a dict.",
490+
network_config)
491+
return
492+
493+
for singular, plural in self.SUPPORTED_NETWORK_CONFIG_TYPES.items():
494+
network_config_items = network_config.get(plural, {})
495+
if not network_config_items:
496+
continue
497+
498+
if not isinstance(network_config_items, dict):
499+
LOG.warning("Network config '%s' is not a dict",
500+
network_config_items)
501+
continue
502+
503+
for name, network_config_item in network_config_items.items():
504+
if not isinstance(network_config_item, dict):
505+
LOG.warning(
506+
"network config item '%s' of type %s is not a dict",
507+
network_config_item, singular)
508+
continue
509+
510+
item = copy.deepcopy(network_config_item)
511+
item['name'] = name
512+
net_details = (
513+
self._get_network_config_parser(singular)
514+
(item))
515+
516+
if net_details:
517+
links += net_details.links
518+
networks += net_details.networks
519+
services += net_details.services
520+
521+
return network_model.NetworkDetailsV2(
522+
links=links,
523+
networks=networks,
524+
services=services
525+
)
526+
527+
528+
class NoCloudNetworkConfigParser(object):
529+
530+
@staticmethod
531+
def parse(network_data):
532+
network_data_version = network_data.get("network", {}).get("version")
533+
if network_data_version == 1:
534+
network_config_parser = NoCloudNetworkConfigV1Parser()
535+
return network_config_parser.parse(network_data)
536+
537+
return NoCloudNetworkConfigV2Parser().parse(network_data)
538+
539+
275540
class NoCloudConfigDriveService(baseconfigdrive.BaseConfigDriveService):
276541

277542
def __init__(self):
@@ -337,11 +602,4 @@ def get_network_details_v2(self):
337602
LOG.exception("V2 network metadata could not be deserialized")
338603
return
339604

340-
network_data_version = network_data.get("version")
341-
if network_data_version != 1:
342-
LOG.error("Network data version '%s' is not supported",
343-
network_data_version)
344-
return
345-
346-
network_config_parser = NoCloudNetworkConfigV1Parser()
347-
return network_config_parser.parse(network_data.get("config"))
605+
return NoCloudNetworkConfigParser.parse(network_data)

0 commit comments

Comments
 (0)