Skip to content

Commit

Permalink
Merge "Add network config support to VMware guest info service"
Browse files Browse the repository at this point in the history
  • Loading branch information
Zuul authored and openstack-gerrit committed May 23, 2024
2 parents 1adf31b + f0fe66b commit 4c1550b
Show file tree
Hide file tree
Showing 5 changed files with 789 additions and 78 deletions.
274 changes: 266 additions & 8 deletions cloudbaseinit/metadata/services/nocloudservice.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
# 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 copy
import netaddr

from oslo_log import log as oslo_logging
Expand Down Expand Up @@ -235,6 +237,10 @@ def parse(self, network_config):
networks = []
services = []

network_config = network_config.get('network') \
if network_config else {}
network_config = network_config.get('config') \
if network_config else None
if not network_config:
LOG.warning("Network configuration is empty")
return
Expand Down Expand Up @@ -272,6 +278,265 @@ def parse(self, network_config):
)


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'
NETWORK_LINK_TYPE_BRIDGE = 'bridge'

SUPPORTED_NETWORK_CONFIG_TYPES = {
NETWORK_LINK_TYPE_ETHERNET: 'ethernets',
NETWORK_LINK_TYPE_BOND: 'bonds',
NETWORK_LINK_TYPE_VLAN: 'vlans',
}

def _parse_mac_address(self, item):
return item.get("match", {}).get("macaddress")

def _parse_addresses(self, item, link_name):
networks = []
services = []

routes = []
# handle route config in deprecated gateway4/gateway6
gateway4 = item.get("gateway4")
gateway6 = item.get("gateway6")
default_route = None
if gateway6 and netaddr.valid_ipv6(gateway6):
default_route = network_model.Route(
network_cidr=self.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,
gateway=gateway4)
if default_route:
routes.append(default_route)

# netplan format config
routes_config = item.get("routes", {})
for route_config in routes_config:
network_cidr = route_config.get("to")
gateway = route_config.get("via")
if network_cidr.lower() == "default":
if netaddr.valid_ipv6(gateway):
network_cidr = self.DEFAULT_GATEWAY_CIDR_IPV6
else:
network_cidr = self.DEFAULT_GATEWAY_CIDR_IPV4
route = network_model.Route(
network_cidr=network_cidr,
gateway=gateway)
routes.append(route)

nameservers = item.get("nameservers")
nameserver_addresses = nameservers.get("addresses", []) \
if nameservers else []
searches = nameservers.get("search", [])
service = network_model.NameServerService(
addresses=nameserver_addresses,
search=','.join(searches) if searches else None,
)
services.append(service)

addresses = item.get("addresses", [])
for addr in addresses:
network = network_model.Network(
link=link_name,
address_cidr=addr,
dns_nameservers=nameserver_addresses,
routes=routes
)
networks.append(network)

return networks, services

def _parse_ethernet_config_item(self, item):
if not item.get('name'):
LOG.warning("Ethernet does not have a name.")
return

name = item.get('name')
eth_name = item.get("set-name", name)
link = network_model.Link(
id=name,
name=eth_name,
type=network_model.LINK_TYPE_PHYSICAL,
enabled=True,
mac_address=self._parse_mac_address(item),
mtu=item.get('mtu'),
bond=None,
vlan_link=None,
vlan_id=None
)

networks, services = self._parse_addresses(item, link.name)
return network_model.NetworkDetailsV2(
links=[link],
networks=networks,
services=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('parameters')
if not bond_params:
LOG.warning("Bond does not have parameters")
return

bond_mode = bond_params.get('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('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('transmit-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('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=self._parse_mac_address(item),
mtu=item.get('mtu'),
bond=bond,
vlan_link=None,
vlan_id=None
)

networks, services = self._parse_addresses(item, link.name)
return network_model.NetworkDetailsV2(
links=[link],
networks=networks,
services=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=self._parse_mac_address(item),
mtu=item.get('mtu'),
bond=None,
vlan_link=item.get('link'),
vlan_id=item.get('id')
)

networks, services = self._parse_addresses(item, link.name)
return network_model.NetworkDetailsV2(
links=[link],
networks=networks,
services=services,
)

def _get_network_config_parser(self, parser_type):
parsers = {
self.NETWORK_LINK_TYPE_ETHERNET: self._parse_ethernet_config_item,
self.NETWORK_LINK_TYPE_BOND: self._parse_bond_config_item,
self.NETWORK_LINK_TYPE_VLAN: self._parse_vlan_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 = []

network_config = network_config.get('network') \
if network_config else {}
if not network_config:
LOG.warning("Network configuration is empty")
return

if not isinstance(network_config, dict):
LOG.warning("Network config '%s' is not a dict.",
network_config)
return

for singular, plural in self.SUPPORTED_NETWORK_CONFIG_TYPES.items():
network_config_items = network_config.get(plural, {})
if not network_config_items:
continue

if not isinstance(network_config_items, dict):
LOG.warning("Network config '%s' is not a dict",
network_config_items)
continue

for name, network_config_item in network_config_items.items():
if not isinstance(network_config_item, dict):
LOG.warning(
"network config item '%s' of type %s is not a dict",
network_config_item, singular)
continue

item = copy.deepcopy(network_config_item)
item['name'] = name
net_details = (
self._get_network_config_parser(singular)
(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 NoCloudNetworkConfigParser(object):

@staticmethod
def parse(network_data):
network_data_version = network_data.get("network", {}).get("version")
if network_data_version == 1:
network_config_parser = NoCloudNetworkConfigV1Parser()
return network_config_parser.parse(network_data)

return NoCloudNetworkConfigV2Parser().parse(network_data)


class NoCloudConfigDriveService(baseconfigdrive.BaseConfigDriveService):

def __init__(self):
Expand Down Expand Up @@ -337,11 +602,4 @@ def get_network_details_v2(self):
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"))
return NoCloudNetworkConfigParser.parse(network_data)
Loading

0 comments on commit 4c1550b

Please sign in to comment.