Support get_network_details_v2 in network plugin

The original network processing is retained for metadata services
that don't implement get_network_details_v2.

Partially-Implements: blueprint json-network-config
Change-Id: Ie2cee13211bc52f69104875803dbc283b3d8e38d
Co-Authored-By: Adrian Vladu <avladu@cloudbasesolutions.com>
This commit is contained in:
Alessandro Pilotti 2018-08-26 01:18:33 +03:00
parent 6e079d6fa4
commit 6782252536
2 changed files with 339 additions and 28 deletions

View File

@ -12,9 +12,9 @@
# License for the specific language governing permissions and limitations
# under the License.
import re
import netaddr
from oslo_log import log as oslo_logging
from cloudbaseinit import exception
@ -37,6 +37,8 @@ NET_REQUIRE = {
"dnsnameservers": False
}
BOND_FORMAT_STR = "bond_%s"
def _name2idx(name):
"""Get the position of a network interface by its name."""
@ -120,13 +122,8 @@ def _preprocess_nics(network_details, network_adapters):
class NetworkConfigPlugin(plugin_base.BasePlugin):
def execute(self, service, shared_data):
def _process_network_details(self, network_details):
osutils = osutils_factory.get_os_utils()
network_details = service.get_network_details()
if not network_details:
return plugin_base.PLUGIN_EXECUTION_DONE, False
# Check and save NICs by MAC.
network_adapters = osutils.get_network_adapters()
network_details = _preprocess_nics(network_details,
@ -148,6 +145,7 @@ class NetworkConfigPlugin(plugin_base.BasePlugin):
name = osutils.get_network_adapter_name_by_mac_address(mac)
LOG.info("Configuring network adapter: %s", name)
reboot = osutils.set_static_network_config(
name,
nic.address,
@ -173,3 +171,141 @@ class NetworkConfigPlugin(plugin_base.BasePlugin):
LOG.error("No adapters were configured")
return plugin_base.PLUGIN_EXECUTION_DONE, reboot_required
@staticmethod
def _process_link_common(osutils, link):
LOG.debug(
"Enable network adapter \"%(name)s\": %(enabled)s",
{"name": link.name, "enabled": link.enabled})
osutils.enable_network_adapter(link.name, link.enabled)
if link.mtu:
LOG.debug(
"Setting MTU on network adapter \"%(name)s\": %(mtu)s",
{"name": link.name, "mtu": link.mtu})
osutils.set_network_adapter_mtu(link.name, link.mtu)
@staticmethod
def _process_physical_links(osutils, network_details):
physical_links = [
link for link in network_details.links if
link.type == network_model.LINK_TYPE_PHYSICAL]
for link in physical_links:
adapter_name = osutils.get_network_adapter_name_by_mac_address(
link.mac_address)
if adapter_name != link.name:
LOG.info(
"Renaming network adapter \"%(old_name)s\" to "
"\"%(new_name)s\"",
{"old_name": adapter_name, "new_name": link.name})
osutils.rename_network_adapter(adapter_name, link.name)
NetworkConfigPlugin._process_link_common(osutils, link)
@staticmethod
def _process_bond_links(osutils, network_details):
bond_links = [
link for link in network_details.links if
link.type == network_model.LINK_TYPE_BOND]
for link in bond_links:
bond_name = BOND_FORMAT_STR % link.id
primary_nic_vlan_id = None
LOG.info("Creating network team: %s", bond_name)
osutils.create_network_team(
bond_name, link.bond.type, link.bond.lb_algorithm,
link.bond.members, link.mac_address, link.name,
primary_nic_vlan_id, link.bond.lacp_rate)
NetworkConfigPlugin._process_link_common(osutils, link)
@staticmethod
def _process_vlan_links(osutils, network_details):
vlan_links = [
link for link in network_details.links if
link.type == network_model.LINK_TYPE_VLAN]
for link in vlan_links:
bond_name = BOND_FORMAT_STR % link.vlan_link
LOG.info(
"Creating bond network adapter \"%(nic_name)s\" on team "
"\"%(bond_name)s\" with VLAN: %(vlan_id)s",
{"nic_name": link.name, "bond_name": bond_name,
"vlan_id": link.vlan_id})
osutils.add_network_team_nic(bond_name, link.name, link.vlan_id)
NetworkConfigPlugin._process_link_common(osutils, link)
@staticmethod
def _get_default_dns_nameservers(network_details):
ipv4_nameservers = []
ipv6_nameservers = []
for s in network_details.services:
if isinstance(s, network_model.NameServerService):
for nameserver in s.addresses:
if netaddr.valid_ipv6(nameserver):
ipv6_nameservers.append(nameserver)
else:
ipv4_nameservers.append(nameserver)
return (ipv4_nameservers, ipv6_nameservers)
@staticmethod
def _process_networks(osutils, network_details):
reboot_required = False
ipv4_ns, ipv6_ns = NetworkConfigPlugin._get_default_dns_nameservers(
network_details)
for net in network_details.networks:
ip_address, prefix_len = net.address_cidr.split("/")
gateway = None
default_gw_route = [
r for r in net.routes if
netaddr.IPNetwork(r.network_cidr).prefixlen == 0]
if default_gw_route:
gateway = default_gw_route[0].gateway
nameservers = net.dns_nameservers
if not nameservers:
if netaddr.valid_ipv6(ip_address):
nameservers = ipv6_ns
else:
nameservers = ipv4_ns
LOG.info(
"Setting static IP configuration on network adapter "
"\"%(name)s\". IP: %(ip)s, prefix length: %(prefix_len)s, "
"gateway: %(gateway)s, dns: %(dns)s",
{"name": net.link, "ip": ip_address, "prefix_len": prefix_len,
"gateway": gateway, "dns": nameservers})
reboot = osutils.set_static_network_config(
net.link, ip_address, prefix_len, gateway, nameservers)
reboot_required = reboot or reboot_required
return reboot_required
@staticmethod
def _process_network_details_v2(network_details):
osutils = osutils_factory.get_os_utils()
NetworkConfigPlugin._process_physical_links(
osutils, network_details)
NetworkConfigPlugin._process_bond_links(osutils, network_details)
NetworkConfigPlugin._process_vlan_links(osutils, network_details)
reboot_required = NetworkConfigPlugin._process_networks(
osutils, network_details)
return plugin_base.PLUGIN_EXECUTION_DONE, reboot_required
def execute(self, service, shared_data):
network_details = service.get_network_details_v2()
if network_details:
return self._process_network_details_v2(network_details)
network_details = service.get_network_details()
if network_details:
return self._process_network_details(network_details)
return plugin_base.PLUGIN_EXECUTION_DONE, False

View File

@ -31,20 +31,21 @@ from cloudbaseinit.tests import testutils
class TestNetworkConfigPlugin(unittest.TestCase):
def setUp(self):
self._setUp()
self._setup_network_details_v1()
@mock.patch("cloudbaseinit.osutils.factory.get_os_utils")
def _test_execute(self, mock_get_os_utils,
network_adapters=None,
network_details=None,
invalid_details=False,
missed_adapters=[],
extra_network_details=[]):
def _test_execute_network_details_v1(self, mock_get_os_utils,
network_adapters=None,
network_details=None,
invalid_details=False,
missed_adapters=[],
extra_network_details=[]):
# Prepare mock environment.
mock_service = mock.MagicMock()
mock_shared_data = mock.Mock()
mock_osutils = mock.MagicMock()
mock_service.get_network_details.return_value = network_details
mock_service.get_network_details_v2.return_value = None
mock_get_os_utils.return_value = mock_osutils
mock_osutils.get_network_adapters.return_value = network_adapters
mock_osutils.set_static_network_config.return_value = True
@ -102,7 +103,8 @@ class TestNetworkConfigPlugin(unittest.TestCase):
reboot = len(missed_adapters) != self._count
self.assertEqual((plugin_base.PLUGIN_EXECUTION_DONE, reboot), ret)
def _setUp(self, same_names=True, wrong_names=False, no_macs=False):
def _setup_network_details_v1(self, same_names=True, wrong_names=False,
no_macs=False):
# Generate fake pairs of NetworkDetails objects and
# local ethernet network adapters.
iface_name = "Ethernet" if wrong_names else "eth"
@ -179,42 +181,43 @@ class TestNetworkConfigPlugin(unittest.TestCase):
# Get the network config plugin.
self._network_plugin = networkconfig.NetworkConfigPlugin()
# Execution wrapper.
self._partial_test_execute = functools.partial(
self._test_execute,
self._partial_test_execute_network_details_v1 = functools.partial(
self._test_execute_network_details_v1,
network_adapters=self._network_adapters,
network_details=self._network_details
)
def test_execute_no_network_details(self):
self._network_details[:] = []
self._partial_test_execute()
self._partial_test_execute_network_details_v1()
def test_execute_no_network_adapters(self):
self._network_adapters[:] = []
self._partial_test_execute()
self._partial_test_execute_network_details_v1()
def test_execute_invalid_network_details(self):
self._network_details.append([None] * 6)
self._partial_test_execute(invalid_details=True)
self._partial_test_execute_network_details_v1(invalid_details=True)
def test_execute_invalid_network_details_name(self):
self._setUp(wrong_names=True, no_macs=True)
self._partial_test_execute(invalid_details=True)
self._setup_network_details_v1(wrong_names=True, no_macs=True)
self._partial_test_execute_network_details_v1(invalid_details=True)
def test_execute_single(self):
for _ in range(self._count - 1):
self._network_adapters.pop()
self._network_details.pop()
self._partial_test_execute()
self._partial_test_execute_network_details_v1()
def test_execute_multiple(self):
self._partial_test_execute()
self._partial_test_execute_network_details_v1()
def test_execute_missing_one(self):
self.assertGreater(self._count, 1)
self._network_details.pop(0)
adapter = self._network_adapters[0]
self._partial_test_execute(missed_adapters=[adapter])
self._partial_test_execute_network_details_v1(
missed_adapters=[adapter])
def test_execute_missing_all(self):
nic = self._network_details[0]
@ -231,7 +234,8 @@ class TestNetworkConfigPlugin(unittest.TestCase):
nic.dnsnameservers
)
self._network_details[:] = [nic]
self._partial_test_execute(missed_adapters=self._network_adapters)
self._partial_test_execute_network_details_v1(
missed_adapters=self._network_adapters)
def _test_execute_missing_smth(self, name=False, mac=False,
address=False, address6=False,
@ -262,7 +266,7 @@ class TestNetworkConfigPlugin(unittest.TestCase):
# Or other vital details.
missed_adapters = [self._network_adapters[ind]]
extra_network_details = []
self._partial_test_execute(
self._partial_test_execute_network_details_v1(
missed_adapters=missed_adapters,
extra_network_details=extra_network_details
)
@ -271,7 +275,7 @@ class TestNetworkConfigPlugin(unittest.TestCase):
self._test_execute_missing_smth(mac=True)
def test_execute_missing_mac2(self):
self._setUp(same_names=False)
self._setup_network_details_v1(same_names=False)
self._test_execute_missing_smth(mac=True)
def test_execute_missing_name_mac(self):
@ -295,3 +299,174 @@ class TestNetworkConfigPlugin(unittest.TestCase):
def test_execute_missing_gateway(self):
self._test_execute_missing_smth(gateway=True)
def _get_network_details_v2(self):
links = []
link1 = network_model.Link(
id=mock.sentinel.link_id1,
name=mock.sentinel.link_name1,
type=network_model.LINK_TYPE_PHYSICAL,
enabled=mock.sentinel.link_enabled1,
mac_address=mock.sentinel.link_mac1,
mtu=mock.sentinel.link_mtu1,
bond=None,
vlan_link=None,
vlan_id=None)
links.append(link1)
bond1 = network_model.Bond(
members=[mock.sentinel.link_id1],
type=mock.sentinel.bond_type1,
lb_algorithm=mock.sentinel.bond_lb_algo1,
lacp_rate=mock.sentinel.lacp_rate1)
bond_link1 = network_model.Link(
id=mock.sentinel.bond_link_id1,
name=mock.sentinel.bond_link_name1,
type=network_model.LINK_TYPE_BOND,
enabled=mock.sentinel.bond_link_enabled1,
mac_address=mock.sentinel.bond_link_mac1,
mtu=mock.sentinel.bond_link_mtu1,
bond=bond1,
vlan_link=None,
vlan_id=None)
links.append(bond_link1)
vlan_link1 = network_model.Link(
id=mock.sentinel.vlan_link_id1,
name=mock.sentinel.vlan_link_name1,
type=network_model.LINK_TYPE_VLAN,
enabled=mock.sentinel.vlan_link_enabled1,
mac_address=mock.sentinel.vlan_link_mac1,
mtu=mock.sentinel.vlan_link_mtu1,
bond=None,
vlan_link=mock.sentinel.bond_link_id1,
vlan_id=mock.sentinel.vlan_id1)
links.append(vlan_link1)
networks = []
route1 = network_model.Route(
network_cidr=mock.sentinel.network_cidr1,
gateway=mock.sentinel.gateway1)
route2 = network_model.Route(
network_cidr=mock.sentinel.network_cidr2,
gateway=mock.sentinel.gateway2)
network1 = network_model.Network(
link=mock.sentinel.link_id1,
address_cidr=mock.sentinel.address_cidr1,
dns_nameservers=mock.sentinel.network_dns_list1,
routes=[route1, route2])
networks.append(network1)
services = []
service1 = network_model.NameServerService(
addresses=[mock.sentinel.dns1, mock.sentinel.dns3],
search=mock.sentinel.dns_search1)
services.append(service1)
return network_model.NetworkDetailsV2(
links=links, networks=networks, services=services)
@mock.patch("cloudbaseinit.osutils.factory.get_os_utils")
def _test_execute_network_details_v2(self, mock_get_os_utils,
empty_network_dns_list=False,
both_ipv4_dns_list=False,
both_ipv6_dns_list=False):
mock.sentinel.link_mac1 = u"00:00:00:00:00:01"
mock.sentinel.network_cidr1 = u"0.0.0.0/0"
mock.sentinel.gateway1 = u"10.0.0.254"
mock.sentinel.network_cidr2 = u"172.16.0.0/16"
mock.sentinel.gateway2 = u"172.16.1.1"
mock.sentinel.address_cidr1 = u"10.0.0.1/24"
mock.sentinel.dns1 = "10.0.0.1"
mock.sentinel.dns2 = "10.0.0.2"
mock.sentinel.network_dns_list1 = []
if empty_network_dns_list:
mock.sentinel.dns3 = "10.0.0.3"
expected_dns_list = [mock.sentinel.dns1, mock.sentinel.dns3]
elif both_ipv4_dns_list:
mock.sentinel.dns3 = "2001:db8::3"
expected_dns_list = [mock.sentinel.dns1]
elif both_ipv6_dns_list:
mock.sentinel.address_cidr1 = u"2001:db8::3/24"
mock.sentinel.dns3 = "2001:db8::4"
expected_dns_list = [mock.sentinel.dns3]
else:
mock.sentinel.network_dns_list1 = [
mock.sentinel.dns1, mock.sentinel.dns2]
expected_dns_list = mock.sentinel.network_dns_list1
service = mock.Mock()
network_details = self._get_network_details_v2()
service.get_network_details_v2.return_value = network_details
mock_os_utils = mock.Mock()
mock_get_os_utils.return_value = mock_os_utils
m = mock_os_utils.get_network_adapter_name_by_mac_address
m.return_value = mock.sentinel.adapter_old_name1
plugin = networkconfig.NetworkConfigPlugin()
plugin.execute(service, {})
service.get_network_details_v2.assert_called_once_with()
service.get_network_details.assert_not_called()
m.assert_called_once_with(mock.sentinel.link_mac1)
mock_os_utils.rename_network_adapter.assert_called_once_with(
mock.sentinel.adapter_old_name1, mock.sentinel.link_name1)
bond_name = (networkconfig.BOND_FORMAT_STR %
mock.sentinel.bond_link_id1)
mock_os_utils.create_network_team.assert_called_once_with(
bond_name, mock.sentinel.bond_type1,
mock.sentinel.bond_lb_algo1,
[mock.sentinel.link_id1],
mock.sentinel.bond_link_mac1,
mock.sentinel.bond_link_name1,
None,
mock.sentinel.lacp_rate1)
mock_os_utils.add_network_team_nic.assert_called_once_with(
bond_name,
mock.sentinel.vlan_link_name1,
mock.sentinel.vlan_id1)
mock_os_utils.set_network_adapter_mtu.assert_has_calls(
[mock.call(mock.sentinel.link_name1, mock.sentinel.link_mtu1),
mock.call(
mock.sentinel.bond_link_name1, mock.sentinel.bond_link_mtu1),
mock.call(
mock.sentinel.vlan_link_name1, mock.sentinel.vlan_link_mtu1)],
any_order=False)
mock_os_utils.enable_network_adapter.assert_has_calls(
[mock.call(mock.sentinel.link_name1, mock.sentinel.link_enabled1),
mock.call(
mock.sentinel.bond_link_name1,
mock.sentinel.bond_link_enabled1),
mock.call(
mock.sentinel.vlan_link_name1,
mock.sentinel.vlan_link_enabled1)],
any_order=False)
ip_address, prefix_len = mock.sentinel.address_cidr1.split("/")
mock_os_utils.set_static_network_config.assert_called_once_with(
mock.sentinel.link_id1, ip_address, prefix_len,
mock.sentinel.gateway1, expected_dns_list)
def test_execute_network_details_v2(self):
self._test_execute_network_details_v2()
def test_execute_network_details_v2_empty_network_dns_list(self):
self._test_execute_network_details_v2(empty_network_dns_list=True)
def test_execute_network_details_v2_ipv4_dns_list(self):
self._test_execute_network_details_v2(both_ipv4_dns_list=True)
def test_execute_network_details_v2_ipv6_dns_list(self):
self._test_execute_network_details_v2(both_ipv6_dns_list=True)