Add gateways everywhere when new node group is added

It is required for environments with multiple node groups.
Before, user should set gateways for all networks via API.
Otherwise, he had got an error.

Now, it is done automatically on node group creation:
1. Set gateways for all networks in new node group.
2. Set gateways for networks in default node group where they were not set.

IP ranges of networks of default node group are cut or deleted if they
intersect with new gateways. IP addresses which intersect with new gateways
are deleted as well. Need to add appropriate message on UI when new node
group is being added.

DocImpact
Closes-Bug: #1517400
Partial-Bug: #1484008

Change-Id: I6580d97b849ccd991bbfb65308aa35f217f30987
This commit is contained in:
Aleksey Kasatkin 2015-12-15 19:28:36 +02:00
parent 62f8c8be2e
commit 469f5629a1
5 changed files with 220 additions and 22 deletions

View File

@ -1438,11 +1438,54 @@ class NetworkManager(object):
return objects.NetworkGroup.create(data)
@classmethod
def create_network_groups(cls, cluster, neutron_segment_type, gid=None):
"""Method for creation of network groups for cluster.
def ensure_gateways_present_in_default_node_group(cls, cluster):
"""Ensure that all networks in default node group have gateways.
It is required for environment with multiple node groups. GWs are added
to all networks that have L3 setup. If some of IP ranges of such
network intersects with new GW, they will be cut.
:param cluster: Cluster instance
:return: None
"""
for network in objects.Cluster.get_default_group(cluster).networks:
if network.meta['notation'] is None or network.meta['use_gateway']:
continue
# add first address of network as a gateway
cidr = IPNetwork(network.cidr)
default_gw_ip = cidr[1]
network.meta['use_gateway'] = True
network.gateway = str(default_gw_ip)
for ip_range_db in network.ip_ranges:
# check that IP ranges do not intersect with GW
# and cut them if they are
if default_gw_ip not in IPRange(ip_range_db.first,
ip_range_db.last):
continue
if ip_range_db.first != ip_range_db.last:
ip_range_db.first = str(cidr[2])
else:
db.delete(ip_range_db)
# delete intersecting IPs
# TODO(akasatkin): need to reexamine deleting of IPs when
# manual setting of IPs will be allowed
db.query(IPAddr).filter(
IPAddr.ip_addr == network.gateway,
IPAddr.network == network.id
).delete()
db().flush()
@classmethod
def create_network_groups(cls, cluster, neutron_segment_type,
node_group_id=None, set_all_gateways=False):
"""Create network groups for node group.
Creates network groups for default node group of cluster if
node_group_id is not supplied. Node group should not contain any
network groups before this.
:param cluster: Cluster instance.
:type cluster: instance
:param neutron_segment_type: segmentation type (only for neutron)
:param node_group_id: ID of node group.
:param set_all_gateways: set gateways for all network groups
:returns: None
"""
networks_metadata = cluster.release.networks_metadata
@ -1456,7 +1499,9 @@ class NetworkManager(object):
if cls.check_network_restrictions(cluster,
net['restrictions']):
continue
cls.create_network_group(cluster, net, gid)
if net['notation'] is not None and set_all_gateways:
net['use_gateway'] = True
cls.create_network_group(cluster, net, node_group_id)
@classmethod
def update_networks(cls, network_configuration):
@ -1924,7 +1969,7 @@ class AllocateVIPs80Mixin(object):
if net_group != consts.NETWORKS.fuelweb_admin and \
net_group not in net_names:
logger.warning(
"Skip VIP '{0}' whitch is mapped to non-existing"
"Skip VIP '{0}' which is mapped to non-existing"
" network '{1}'".format(vip_name, net_group))
continue

View File

@ -37,7 +37,13 @@ class NodeGroup(NailgunObject):
cluster = Cluster.get_by_uid(new_group.cluster_id)
nm = Cluster.get_network_manager(cluster)
nst = cluster.network_config.segmentation_type
nm.create_network_groups(cluster, nst, gid=new_group.id)
# We have two node groups here when user adds the first custom
# node group.
if NodeGroupCollection.get_by_cluster_id(cluster.id).count() == 2:
nm.ensure_gateways_present_in_default_node_group(cluster)
nm.create_network_groups(
cluster, neutron_segment_type=nst, node_group_id=new_group.id,
set_all_gateways=True)
nm.create_admin_network_group(new_group.cluster_id, new_group.id)
except (
errors.OutOfVLANs,

View File

@ -30,7 +30,6 @@ 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
@ -485,12 +484,6 @@ class EnvironmentManager(object):
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'] = \

View File

@ -776,8 +776,12 @@ class TestNetworkManager(BaseNetworkManagerTest):
def test_restricted_networks(self):
rel = self.env.create_release()
enabled_net = {'name': 'always_enabled', 'restrictions': ['false']}
disabled_net = {'name': 'always_disabled', 'restrictions': ['true']}
enabled_net = {'name': 'always_enabled',
'notation': None,
'restrictions': ['false']}
disabled_net = {'name': 'always_disabled',
'notation': None,
'restrictions': ['true']}
netw_meta = deepcopy(rel.networks_metadata)
netw_meta['neutron']['networks'].extend([enabled_net, disabled_net])
rel.networks_metadata = netw_meta

View File

@ -16,11 +16,13 @@
from mock import patch
import copy
import json
import six
from nailgun import consts
from nailgun.db import db
from nailgun.db.sqlalchemy.models import NetworkGroup
from nailgun.db.sqlalchemy import models
from nailgun import objects
from nailgun.test.base import BaseIntegrationTest
from nailgun.utils import reverse
@ -28,12 +30,17 @@ from nailgun.utils import reverse
class TestNodeGroups(BaseIntegrationTest):
segmentation_type = consts.NEUTRON_SEGMENT_TYPES.gre
def setUp(self):
super(TestNodeGroups, self).setUp()
self.cluster = self.env.create_cluster(
api=False,
net_provider=consts.CLUSTER_NET_PROVIDERS.neutron,
net_segment_type=consts.NEUTRON_SEGMENT_TYPES.gre
self.cluster = self.env.create(
release_kwargs={'version': '1111-8.0'},
cluster_kwargs={
'api': False,
'net_provider': consts.CLUSTER_NET_PROVIDERS.neutron,
'net_segment_type': self.segmentation_type
}
)
def test_nodegroup_creation(self):
@ -102,7 +109,8 @@ class TestNodeGroups(BaseIntegrationTest):
resp = self.env.create_node_group()
response = resp.json_body
nets = db().query(NetworkGroup).filter_by(group_id=response['id'])
nets = db().query(models.NetworkGroup).filter_by(
group_id=response['id'])
self.assertEquals(nets.count(), 5)
@patch('nailgun.task.task.rpc.cast')
@ -120,7 +128,8 @@ class TestNodeGroups(BaseIntegrationTest):
expect_errors=False
)
nets = db().query(NetworkGroup).filter_by(group_id=response['id'])
nets = db().query(models.NetworkGroup).filter_by(
group_id=response['id'])
self.assertEquals(nets.count(), 0)
def test_nodegroup_vlan_segmentation_type(self):
@ -379,3 +388,144 @@ class TestNodeGroups(BaseIntegrationTest):
message = resp.json_body['message']
self.assertEquals(resp.status_code, 400)
self.assertRegexpMatches(message, 'Cannot assign node group')
@patch('nailgun.task.task.rpc.cast')
def test_net_config_is_valid_after_nodegroup_is_created(self, _):
# API validator does not allow to setup networks w/o gateways when
# cluster has multiple node groups. Since now, no additional actions
# are required from user to get valid configuration after new node
# group is created, i.e. this network configuration will pass through
# the API validator.
resp = self.env.create_node_group()
self.assertEquals(resp.status_code, 201)
config = self.env.neutron_networks_get(self.cluster.id).json_body
resp = self.env.neutron_networks_put(self.cluster.id, config)
self.assertEqual(resp.status_code, 200)
def test_all_networks_have_gw_after_nodegroup_is_created(self):
resp = self.env.create_node_group()
self.assertEquals(resp.status_code, 201)
for network in self.cluster.network_groups:
if network.meta['notation'] is not None:
self.assertTrue(network.meta['use_gateway'])
self.assertIsNotNone(network.gateway)
def test_intersecting_ip_deleted_after_nodegroup_is_created(self):
net_roles = copy.copy(
self.env.clusters[0].release.network_roles_metadata)
net_roles.append({
'id': 'stor/vip',
'default_mapping': consts.NETWORKS.storage,
'properties': {
'subnet': True,
'gateway': False,
'vip': [{
'name': 'my-vip',
'node_roles': ['controller'],
}]
}})
self.env.clusters[0].release.network_roles_metadata = net_roles
self.db.flush()
# VIPs are allocated on this call
config = self.env.neutron_networks_get(self.cluster.id).json_body
# Storage network has no GW by default
vip_config = config['vips']['my-vip']['ipaddr']
self.assertEqual(
1,
self.db.query(models.IPAddr).filter_by(ip_addr=vip_config).count()
)
resp = self.env.create_node_group()
self.assertEquals(resp.status_code, 201)
# VIP address was deleted
self.assertEqual(
0,
self.db.query(models.IPAddr).filter_by(ip_addr=vip_config).count()
)
# Storage GW has this address now
resp = self.env.neutron_networks_get(self.cluster.id)
self.assertEquals(resp.status_code, 200)
config = resp.json_body
for net in config['networks']:
if net['name'] == consts.NETWORKS.storage:
self.assertEqual(vip_config, net['gateway'])
break
else:
self.fail('No storage network found')
# VIP is allocated to different address
self.assertNotEqual(vip_config, config['vips']['my-vip']['ipaddr'])
@patch('nailgun.task.task.rpc.cast')
def test_ensure_gateways_present_cuts_ranges(self, _):
# setup particular networks without gateways
networks = {
'public': {'cidr': '199.101.9.0/24',
'ip_ranges': [['199.101.9.1', '199.101.9.1'],
['199.101.9.5', '199.101.9.111']]},
'management': {'cidr': '199.101.1.0/24'},
'storage': {'cidr': '199.101.2.0/24'},
}
config = self.env.neutron_networks_get(self.cluster.id).json_body
for net in config['networks']:
if net['name'] in networks:
for pkey, pval in six.iteritems(networks[net['name']]):
net[pkey] = pval
net['meta']['use_gateway'] = False
config['networking_parameters']['floating_ranges'] = [
['199.101.9.122', '199.101.9.233']]
resp = self.env.neutron_networks_put(self.cluster.id, config)
self.assertEquals(resp.status_code, 200)
ranges_before = {
'public': [['199.101.9.1', '199.101.9.1'],
['199.101.9.5', '199.101.9.111']],
'management': [['199.101.1.1', '199.101.1.254']],
'storage': [['199.101.2.1', '199.101.2.254']],
}
config = self.env.neutron_networks_get(self.cluster.id).json_body
for net in config['networks']:
if net['name'] in networks:
self.assertEqual(ranges_before[net['name']], net['ip_ranges'])
objects.Cluster.get_network_manager(self.cluster).\
ensure_gateways_present_in_default_node_group(self.cluster)
ranges_after = {
'public': [['199.101.9.5', '199.101.9.111']],
'management': [['199.101.1.2', '199.101.1.254']],
'storage': [['199.101.2.2', '199.101.2.254']],
}
config = self.env.neutron_networks_get(self.cluster.id).json_body
for net in config['networks']:
if net['name'] in networks:
self.assertEqual(ranges_after[net['name']], net['ip_ranges'])
def test_ensure_gateways_present_is_executed_once(self):
with patch.object(
objects.Cluster.get_network_manager(self.cluster),
'ensure_gateways_present_in_default_node_group') as \
ensure_mock:
for n in range(2):
resp = self.env.create_node_group(name='group{0}'.format(n))
self.assertEquals(resp.status_code, 201)
# one call is made when first custom node group is being added
# no more calls after that
self.assertEqual(
ensure_mock.call_count, 1,
'Method was called {0} time(s) unexpectedly, '
'current node group: {1}'.format(ensure_mock.call_count,
resp.json_body['name']))
class TestNodeGroupsVlan(TestNodeGroups):
segmentation_type = consts.NEUTRON_SEGMENT_TYPES.vlan
class TestNodeGroupsTun(TestNodeGroups):
segmentation_type = consts.NEUTRON_SEGMENT_TYPES.tun