Allow sharing of networks between nodegroups
Networks are considered as shared if they are in different nodegroups, have the same Name, CIDR and Gateway values. No routes are geneated between shared segments of networks. Partial-Bug: #1484008 Partial-Bug: #1473047 Change-Id: Ifeaffd76d52a46d95918527c62c302ad356d2fe5 Blueprint: nodegroups-share-network-parameters
This commit is contained in:
parent
0f4dfa4c5d
commit
482743e3e7
|
@ -357,21 +357,31 @@ class NetworkCheck(object):
|
|||
|
||||
(Neutron)
|
||||
"""
|
||||
# check intersection of address ranges
|
||||
# between all networks
|
||||
for ngs in combinations(self.networks, 2):
|
||||
if ngs[0].get('cidr') and ngs[1].get('cidr'):
|
||||
cidr1 = netaddr.IPNetwork(ngs[0]['cidr'])
|
||||
cidr2 = netaddr.IPNetwork(ngs[1]['cidr'])
|
||||
# check intersection of address ranges between all networks
|
||||
for ng1, ng2 in combinations(self.networks, 2):
|
||||
if ng1.get('cidr') and ng2.get('cidr'):
|
||||
# networks with the same name in different node groups maybe
|
||||
# considered as one shared network if they have equal CIDRs
|
||||
# and gateways
|
||||
if (ng1['group_id'] != ng2['group_id'] and
|
||||
ng1['group_id'] is not None and
|
||||
ng2['group_id'] is not None and
|
||||
ng1['name'] == ng2['name'] and
|
||||
ng1['gateway'] is not None and
|
||||
ng1['gateway'] == ng2['gateway'] and
|
||||
ng1['cidr'] == ng2['cidr']):
|
||||
continue
|
||||
cidr1 = netaddr.IPNetwork(ng1['cidr'])
|
||||
cidr2 = netaddr.IPNetwork(ng2['cidr'])
|
||||
if self.net_man.is_cidr_intersection(cidr1, cidr2):
|
||||
self.err_msgs.append(
|
||||
u"Address space intersection "
|
||||
u"between networks:\n{0}".format(
|
||||
", ".join([ngs[0]['name'], ngs[1]['name']])
|
||||
", ".join([ng1['name'], ng2['name']])
|
||||
)
|
||||
)
|
||||
self.result.append({
|
||||
"ids": [int(ngs[0]["id"]), int(ngs[1]["id"])],
|
||||
"ids": [int(ng1["id"]), int(ng2["id"])],
|
||||
"errors": ["cidr"]
|
||||
})
|
||||
self.expose_error_messages()
|
||||
|
|
|
@ -1469,9 +1469,9 @@ class NetworkManager(object):
|
|||
def get_networks_not_on_node(cls, node, networks=None):
|
||||
networks = networks or cls.get_node_networks(node)
|
||||
node_net = [(n['name'], n['cidr'])
|
||||
for n in networks if n.get('cidr')]
|
||||
for n in networks if n.get('cidr')]
|
||||
all_nets = [(n.name, n.cidr)
|
||||
for n in node.cluster.network_groups if n.cidr]
|
||||
for n in node.cluster.network_groups if n.cidr]
|
||||
|
||||
admin_net = cls.get_admin_network_group()
|
||||
all_nets.append((admin_net.name, admin_net.cidr))
|
||||
|
|
|
@ -132,9 +132,7 @@ class NeutronManager70(AllocateVIPs70Mixin, NeutronManager):
|
|||
|
||||
@classmethod
|
||||
def get_node_networks_with_ips(cls, node):
|
||||
"""Returns IP and network data (meta, gateway) for each node network.
|
||||
|
||||
"""
|
||||
"""Returns IP, CIDR, meta, gateway for each network on given node."""
|
||||
if not node.group_id:
|
||||
return {}
|
||||
|
||||
|
@ -149,6 +147,7 @@ class NeutronManager70(AllocateVIPs70Mixin, NeutronManager):
|
|||
for ng, ip in ngs:
|
||||
networks[ng.name] = {
|
||||
'ip': cls.get_ip_w_cidr_prefix_len(ip, ng),
|
||||
'cidr': ng.cidr,
|
||||
'meta': ng.meta,
|
||||
'gateway': ng.gateway
|
||||
}
|
||||
|
@ -157,6 +156,7 @@ class NeutronManager70(AllocateVIPs70Mixin, NeutronManager):
|
|||
networks[admin_ng.name] = {
|
||||
'ip': cls.get_ip_w_cidr_prefix_len(
|
||||
cls.get_admin_ip_for_node(node.id), admin_ng),
|
||||
'cidr': admin_ng.cidr,
|
||||
'meta': admin_ng.meta,
|
||||
'gateway': admin_ng.gateway
|
||||
}
|
||||
|
|
|
@ -604,6 +604,18 @@ class NeutronNetworkDeploymentSerializer61(
|
|||
@classmethod
|
||||
def generate_routes(cls, node, attrs, nm, netgroup_mapping, netgroups,
|
||||
networks):
|
||||
"""Generate static routes for environment with multiple node groups.
|
||||
|
||||
Generate static routes for all networks in all node groups where
|
||||
gateway is set.
|
||||
:param node: Node instance
|
||||
:param attrs: deployment attributes hash (is modified in method)
|
||||
:param nm: Network Manager for current environment
|
||||
:param netgroup_mapping: endpoint to network name mapping
|
||||
:param netgroups: hash of network parameters hashes for node
|
||||
:param networks: sequence of network parameters hashes
|
||||
:return: None (attrs is modified)
|
||||
"""
|
||||
other_nets = nm.get_networks_not_on_node(node, networks)
|
||||
|
||||
for ngname, brname in netgroup_mapping:
|
||||
|
@ -1424,6 +1436,37 @@ class NeutronNetworkDeploymentSerializer80(
|
|||
provider='ovs'))
|
||||
return transformations
|
||||
|
||||
@classmethod
|
||||
def generate_routes(cls, node, attrs, nm, netgroup_mapping, netgroups,
|
||||
networks):
|
||||
"""Generate static routes for environment with multiple node groups.
|
||||
|
||||
Generate static routes for all networks in all node groups where
|
||||
gateway is set. Routes are not generated between shared L3 segments.
|
||||
:param node: Node instance
|
||||
:param attrs: deployment attributes hash (is modified in method)
|
||||
:param nm: Network Manager for current environment
|
||||
:param netgroup_mapping: endpoint to network name mapping
|
||||
:param netgroups: hash of network parameters hashes for node
|
||||
:param networks: sequence of network parameters hashes
|
||||
:return: None (attrs is modified)
|
||||
"""
|
||||
other_nets = nm.get_networks_not_on_node(node, networks)
|
||||
cidrs_in_use = set(ng['cidr'] for ng in netgroups if 'cidr' in ng)
|
||||
|
||||
for ngname, brname in netgroup_mapping:
|
||||
netgroup = netgroups[ngname]
|
||||
if netgroup.get('gateway') and netgroup.get('cidr'):
|
||||
via = netgroup['gateway']
|
||||
attrs['endpoints'][brname]['routes'] = []
|
||||
for cidr in other_nets.get(ngname, []):
|
||||
if cidr not in cidrs_in_use:
|
||||
attrs['endpoints'][brname]['routes'].append({
|
||||
'net': cidr,
|
||||
'via': via
|
||||
})
|
||||
cidrs_in_use.add(cidr)
|
||||
|
||||
|
||||
class NeutronNetworkTemplateSerializer80(
|
||||
GenerateL23Mixin80,
|
||||
|
|
|
@ -23,12 +23,14 @@ except ImportError:
|
|||
import mock
|
||||
import os
|
||||
import re
|
||||
import six
|
||||
import time
|
||||
import uuid
|
||||
|
||||
from datetime import datetime
|
||||
from functools import partial
|
||||
from itertools import izip
|
||||
from netaddr import IPAddress
|
||||
from netaddr import IPNetwork
|
||||
from random import randint
|
||||
|
||||
|
@ -425,6 +427,54 @@ class EnvironmentManager(object):
|
|||
db().delete(ng)
|
||||
db().flush()
|
||||
|
||||
def setup_networks_for_nodegroup(
|
||||
self, cluster_id, node_group, cidr_start, floating_ranges=None):
|
||||
"""Setup networks of particular node group in multi-node-group mode.
|
||||
|
||||
:param cluster_id: Cluster Id
|
||||
:param node_group: NodeGroup instance
|
||||
:param cidr_start: first two octets of CIDR which are common for all
|
||||
networks (e.g. "192.168")
|
||||
:param floating_ranges: Floating IP ranges
|
||||
:return: response from network setup API request
|
||||
"""
|
||||
ng2_networks = {
|
||||
'fuelweb_admin': {'cidr': '{0}.9.0/24'.format(cidr_start),
|
||||
'ip_ranges': [['{0}.9.2'.format(cidr_start),
|
||||
'{0}.9.254'.format(cidr_start)]],
|
||||
'gateway': '{0}.9.1'.format(cidr_start)},
|
||||
'public': {'cidr': '{0}.0.0/24'.format(cidr_start),
|
||||
'ip_ranges': [['{0}.0.2'.format(cidr_start),
|
||||
'{0}.0.127'.format(cidr_start)]],
|
||||
'gateway': '{0}.0.1'.format(cidr_start)},
|
||||
'management': {'cidr': '{0}.1.0/24'.format(cidr_start),
|
||||
'gateway': '{0}.1.1'.format(cidr_start)},
|
||||
'storage': {'cidr': '{0}.2.0/24'.format(cidr_start),
|
||||
'gateway': '{0}.2.1'.format(cidr_start)},
|
||||
'private': {'cidr': '{0}.3.0/24'.format(cidr_start),
|
||||
'gateway': '{0}.3.1'.format(cidr_start)},
|
||||
}
|
||||
netw_ids = set(net.id for net in node_group.networks)
|
||||
netconfig = self.neutron_networks_get(cluster_id).json_body
|
||||
for net in netconfig['networks']:
|
||||
if not net['meta']['notation']:
|
||||
continue
|
||||
if net['id'] in netw_ids and net['name'] in ng2_networks:
|
||||
for pkey, pval in six.iteritems(ng2_networks[net['name']]):
|
||||
net[pkey] = pval
|
||||
elif not net['gateway']:
|
||||
net['ip_ranges'] = [[
|
||||
str(IPAddress(IPNetwork(net['cidr'])[2])),
|
||||
str(IPAddress(IPNetwork(net['cidr'])[254])),
|
||||
]]
|
||||
net['gateway'] = str(IPNetwork(net['cidr'])[1])
|
||||
net['meta']['use_gateway'] = True
|
||||
if floating_ranges:
|
||||
netconfig['networking_parameters']['floating_ranges'] = \
|
||||
floating_ranges
|
||||
resp = self.neutron_networks_put(cluster_id, netconfig)
|
||||
return resp
|
||||
|
||||
def create_plugin(self, api=False, cluster=None, **kwargs):
|
||||
plugin_data = self.get_default_plugin_metadata(**kwargs)
|
||||
|
||||
|
|
|
@ -15,10 +15,13 @@
|
|||
# under the License.
|
||||
|
||||
from copy import deepcopy
|
||||
import mock
|
||||
import six
|
||||
|
||||
from nailgun import consts
|
||||
from nailgun.db.sqlalchemy import models
|
||||
from nailgun import objects
|
||||
from nailgun import rpc
|
||||
|
||||
from nailgun.orchestrator.deployment_graph import AstuteGraph
|
||||
from nailgun.orchestrator.deployment_serializers import \
|
||||
|
@ -179,6 +182,82 @@ class TestDeploymentAttributesSerialization80(
|
|||
self.assertIn(expected_patch, transformations)
|
||||
|
||||
|
||||
class TestMultiNodeGroupsSerialization80(BaseDeploymentSerializer):
|
||||
env_version = '2015.1.0-8.0'
|
||||
|
||||
def setUp(self):
|
||||
super(TestMultiNodeGroupsSerialization80, self).setUp()
|
||||
cluster = self.env.create(
|
||||
release_kwargs={'version': self.env_version},
|
||||
cluster_kwargs={
|
||||
'net_provider': consts.CLUSTER_NET_PROVIDERS.neutron,
|
||||
'net_segment_type': consts.NEUTRON_SEGMENT_TYPES.vlan}
|
||||
)
|
||||
self.env.create_nodes_w_interfaces_count(
|
||||
nodes_count=3,
|
||||
if_count=2,
|
||||
roles=['controller', 'cinder'],
|
||||
pending_addition=True,
|
||||
cluster_id=cluster['id'])
|
||||
self.cluster_db = self.db.query(models.Cluster).get(cluster['id'])
|
||||
serializer_type = get_serializer_for_cluster(self.cluster_db)
|
||||
self.serializer = serializer_type(AstuteGraph(self.cluster_db))
|
||||
|
||||
def _add_node_group_with_node(self, cidr_start, node_address):
|
||||
node_group = self.env.create_node_group(
|
||||
api=False, cluster_id=self.cluster_db.id,
|
||||
name='ng_' + cidr_start + '_' + str(node_address))
|
||||
|
||||
with mock.patch.object(rpc, 'cast'):
|
||||
resp = self.env.setup_networks_for_nodegroup(
|
||||
cluster_id=self.cluster_db.id, node_group=node_group,
|
||||
cidr_start=cidr_start)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
self.db.query(models.Task).filter_by(
|
||||
name=consts.TASK_NAMES.update_dnsmasq
|
||||
).delete(synchronize_session=False)
|
||||
|
||||
self.env.create_nodes_w_interfaces_count(
|
||||
nodes_count=1,
|
||||
if_count=2,
|
||||
roles=['compute'],
|
||||
pending_addition=True,
|
||||
cluster_id=self.cluster_db.id,
|
||||
group_id=node_group.id,
|
||||
ip='{0}.9.{1}'.format(cidr_start, node_address))
|
||||
|
||||
def _check_routes_count(self, count):
|
||||
self.prepare_for_deployment(self.cluster_db.nodes)
|
||||
facts = self.serializer.serialize(
|
||||
self.cluster_db, self.cluster_db.nodes)
|
||||
|
||||
for node in facts:
|
||||
endpoints = node['network_scheme']['endpoints']
|
||||
for name, descr in six.iteritems(endpoints):
|
||||
if descr['IP'] == 'none':
|
||||
self.assertNotIn('routes', descr)
|
||||
else:
|
||||
self.assertEqual(len(descr['routes']), count)
|
||||
|
||||
def test_routes_with_no_shared_networks_2_nodegroups(self):
|
||||
self._add_node_group_with_node('199.99', 3)
|
||||
# all networks have different CIDRs
|
||||
self._check_routes_count(1)
|
||||
|
||||
def test_routes_with_no_shared_networks_3_nodegroups(self):
|
||||
self._add_node_group_with_node('199.99', 3)
|
||||
self._add_node_group_with_node('199.77', 3)
|
||||
# all networks have different CIDRs
|
||||
self._check_routes_count(2)
|
||||
|
||||
def test_routes_with_shared_networks_3_nodegroups(self):
|
||||
self._add_node_group_with_node('199.99', 3)
|
||||
self._add_node_group_with_node('199.99', 4)
|
||||
# networks in two racks have equal CIDRs
|
||||
self._check_routes_count(1)
|
||||
|
||||
|
||||
class TestSerializeInterfaceDriversData80(
|
||||
TestSerializer80Mixin,
|
||||
TestSerializeInterfaceDriversData
|
||||
|
|
|
@ -109,7 +109,7 @@ class TestNetworkCheck(BaseIntegrationTest):
|
|||
checker.check_untagged_intersection)
|
||||
|
||||
@patch.object(helpers, 'db')
|
||||
def test_check_network_address_spaces_intersection(self, mocked_db):
|
||||
def test_check_network_address_spaces_intersection(self, _):
|
||||
cluster = self.env.create(
|
||||
cluster_kwargs={
|
||||
'net_provider': consts.CLUSTER_NET_PROVIDERS.nova_network},
|
||||
|
@ -162,7 +162,7 @@ class TestNetworkCheck(BaseIntegrationTest):
|
|||
checker.check_network_address_spaces_intersection)
|
||||
|
||||
@patch.object(helpers, 'db')
|
||||
def test_check_public_floating_ranges_intersection(self, mocked_db):
|
||||
def test_check_public_floating_ranges_intersection(self, _):
|
||||
checker = NetworkCheck(self.task, {})
|
||||
checker.networks = [{'id': 1,
|
||||
'cidr': '192.168.0.0/24',
|
||||
|
@ -207,14 +207,16 @@ class TestNetworkCheck(BaseIntegrationTest):
|
|||
'name': 'public',
|
||||
'gateway': '192.168.0.1',
|
||||
'ip_ranges': [['192.168.0.10', '192.168.0.40']],
|
||||
'meta': {'notation': 'cidr'}
|
||||
'meta': {'notation': 'cidr'},
|
||||
'group_id': 1,
|
||||
},
|
||||
{'id': 2,
|
||||
'cidr': '192.168.0.128/25',
|
||||
'name': 'public',
|
||||
'gateway': '192.168.0.129',
|
||||
'ip_ranges': [['192.168.0.130', '192.168.0.150']],
|
||||
'meta': {'notation': 'cidr'}
|
||||
'meta': {'notation': 'cidr'},
|
||||
'group_id': 2,
|
||||
}
|
||||
]
|
||||
|
||||
|
@ -229,6 +231,80 @@ class TestNetworkCheck(BaseIntegrationTest):
|
|||
"192.168.0.160-192.168.0.200 are not in the same public CIDR.",
|
||||
checker.neutron_check_network_address_spaces_intersection)
|
||||
|
||||
@patch.object(helpers, 'db')
|
||||
def test_check_non_shared_networks_intersection(self, _):
|
||||
checker = NetworkCheck(self.task, {})
|
||||
networks = [
|
||||
{'id': 1,
|
||||
'cidr': '192.168.0.0/25',
|
||||
'name': consts.NETWORKS.public,
|
||||
'gateway': '192.168.0.1',
|
||||
'ip_ranges': [['192.168.0.10', '192.168.0.40']],
|
||||
'meta': {'notation': consts.NETWORK_NOTATION.ip_ranges},
|
||||
'vlan_start': 111,
|
||||
'group_id': 1,
|
||||
},
|
||||
{'id': 2,
|
||||
'cidr': '192.168.0.0/25',
|
||||
'name': consts.NETWORKS.management,
|
||||
'gateway': '192.168.0.2',
|
||||
'ip_ranges': [['192.168.0.50', '192.168.0.110']],
|
||||
'meta': {'notation': consts.NETWORK_NOTATION.ip_ranges},
|
||||
'vlan_start': 222,
|
||||
'group_id': 1,
|
||||
}
|
||||
]
|
||||
# different networks from one node group, different gateways
|
||||
checker.networks = networks
|
||||
self.assertRaisesWithMessage(
|
||||
errors.NetworkCheckError,
|
||||
"Address space intersection between networks:\n"
|
||||
"public, management",
|
||||
checker.neutron_check_network_address_spaces_intersection)
|
||||
|
||||
checker = NetworkCheck(self.task, {})
|
||||
# different networks from different node groups, different gateways
|
||||
networks[1]['group_id'] = 2
|
||||
checker.networks = networks
|
||||
self.assertRaisesWithMessage(
|
||||
errors.NetworkCheckError,
|
||||
"Address space intersection between networks:\n"
|
||||
"public, management",
|
||||
checker.neutron_check_network_address_spaces_intersection)
|
||||
|
||||
checker = NetworkCheck(self.task, {})
|
||||
# same networks from different node groups, different gateways
|
||||
networks[1]['name'] = consts.NETWORKS.public
|
||||
checker.networks = networks
|
||||
self.assertRaisesWithMessage(
|
||||
errors.NetworkCheckError,
|
||||
"Address space intersection between networks:\n"
|
||||
"public, public",
|
||||
checker.neutron_check_network_address_spaces_intersection)
|
||||
|
||||
checker = NetworkCheck(self.task, {})
|
||||
# same networks from different node groups, same gateway
|
||||
networks[1]['gateway'] = '192.168.0.1'
|
||||
checker.networks = networks
|
||||
checker.network_config['floating_ranges'] = [
|
||||
['192.168.0.111', '192.168.0.126']
|
||||
]
|
||||
self.assertNotRaises(
|
||||
errors.NetworkCheckError,
|
||||
checker.check_configuration)
|
||||
|
||||
checker = NetworkCheck(self.task, {})
|
||||
# same networks from different node groups, same gateway,
|
||||
# intersection in ip_ranges
|
||||
networks[1]['ip_ranges'] = [['192.168.0.30', '192.168.0.60']]
|
||||
checker.networks = networks
|
||||
checker.network_config['floating_ranges'] = [
|
||||
['192.168.0.111', '192.168.0.126']
|
||||
]
|
||||
self.assertNotRaises(
|
||||
errors.NetworkCheckError,
|
||||
checker.check_configuration)
|
||||
|
||||
@patch.object(helpers, 'db')
|
||||
def test_check_vlan_ids_range_and_intersection_failed(self, mocked_db):
|
||||
checker = NetworkCheck(self.task, {})
|
||||
|
|
Loading…
Reference in New Issue