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:
Aleksey Kasatkin 2015-11-19 20:33:55 +02:00
parent 0f4dfa4c5d
commit 482743e3e7
7 changed files with 275 additions and 17 deletions

View File

@ -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()

View File

@ -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))

View File

@ -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
}

View File

@ -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,

View File

@ -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)

View File

@ -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

View File

@ -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, {})