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:
parent
62f8c8be2e
commit
469f5629a1
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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'] = \
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue