Nova API gateway needs to finish two tasks for security group functionality. One is to create security group in bottom pod if it does not exist. Only when the user boot a server can we know which pod to create the security group. The other one is to handle default security group. We decide to replace the default "remote group" rules with several "remote ip prefix" rules. Each rule points to a CIDR of one of the project's subnets. Since the collection of subnets may change, we need to update bottom security group rules. Currently these two tasks both run in synchronous way. For better response time the second task can be implemented in asynchronous way later. Neutron plugin part for security group functionality will be covered in next patch. Change-Id: I1822ff16e3dfc2a89c34f7833adcc1ad1d952462
866 lines
35 KiB
Python
866 lines
35 KiB
Python
# Copyright 2015 Huawei Technologies Co., Ltd.
|
|
# All Rights Reserved
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import copy
|
|
import datetime
|
|
import mock
|
|
from mock import patch
|
|
import pecan
|
|
import unittest
|
|
|
|
import neutronclient.common.exceptions as q_exceptions
|
|
from oslo_utils import uuidutils
|
|
|
|
from tricircle.common import context
|
|
from tricircle.common import lock_handle
|
|
from tricircle.db import api
|
|
from tricircle.db import core
|
|
from tricircle.db import models
|
|
from tricircle.nova_apigw.controllers import server
|
|
|
|
|
|
TOP_NETS = []
|
|
TOP_SUBNETS = []
|
|
TOP_PORTS = []
|
|
TOP_SGS = []
|
|
BOTTOM1_NETS = []
|
|
BOTTOM1_SUBNETS = []
|
|
BOTTOM1_PORTS = []
|
|
BOTTOM1_SGS = []
|
|
BOTTOM2_NETS = []
|
|
BOTTOM2_SUBNETS = []
|
|
BOTTOM2_PORTS = []
|
|
BOTTOM2_SGS = []
|
|
|
|
BOTTOM_NETS = BOTTOM1_NETS
|
|
BOTTOM_SUBNETS = BOTTOM1_SUBNETS
|
|
BOTTOM_PORTS = BOTTOM1_PORTS
|
|
BOTTOM_SGS = BOTTOM1_SGS
|
|
|
|
RES_LIST = [TOP_NETS, TOP_SUBNETS, TOP_PORTS, TOP_SGS,
|
|
BOTTOM1_NETS, BOTTOM1_SUBNETS, BOTTOM1_PORTS, BOTTOM1_SGS,
|
|
BOTTOM2_NETS, BOTTOM2_SUBNETS, BOTTOM2_PORTS, BOTTOM2_SGS]
|
|
|
|
|
|
class FakeException(Exception):
|
|
pass
|
|
|
|
|
|
class FakeServerController(server.ServerController):
|
|
def __init__(self, project_id):
|
|
self.clients = {'t_region': FakeClient('t_region')}
|
|
self.project_id = project_id
|
|
|
|
def _get_client(self, pod_name=None):
|
|
if not pod_name:
|
|
return self.clients['t_region']
|
|
else:
|
|
if pod_name not in self.clients:
|
|
self.clients[pod_name] = FakeClient(pod_name)
|
|
return self.clients[pod_name]
|
|
|
|
|
|
class FakeClient(object):
|
|
|
|
_res_map = {'top': {'network': TOP_NETS,
|
|
'subnet': TOP_SUBNETS,
|
|
'port': TOP_PORTS,
|
|
'security_group': TOP_SGS},
|
|
'bottom': {'network': BOTTOM_NETS,
|
|
'subnet': BOTTOM_SUBNETS,
|
|
'port': BOTTOM_PORTS,
|
|
'security_group': BOTTOM_SGS},
|
|
'bottom2': {'network': BOTTOM2_NETS,
|
|
'subnet': BOTTOM2_SUBNETS,
|
|
'port': BOTTOM2_PORTS,
|
|
'security_group': BOTTOM2_SGS}}
|
|
|
|
def __init__(self, pod_name):
|
|
self.pod_name = pod_name
|
|
self.ip_suffix_gen = self._get_ip_suffix()
|
|
|
|
def _get_res_list(self, _type):
|
|
if self.pod_name == 'b_region_2':
|
|
pod = 'bottom2'
|
|
elif self.pod_name == 't_region':
|
|
pod = 'top'
|
|
else:
|
|
pod = 'bottom'
|
|
return self._res_map[pod][_type]
|
|
|
|
def _check_port_ip_conflict(self, subnet_id, ip):
|
|
port_list = self._get_res_list('port')
|
|
for port in port_list:
|
|
if 'fixed_ips' in port:
|
|
if port['fixed_ips'][0]['ip_address'] == ip and (
|
|
port['fixed_ips'][0]['subnet_id'] == subnet_id
|
|
):
|
|
raise FakeException()
|
|
|
|
def create_resources(self, _type, ctx, body):
|
|
if 'id' not in body[_type]:
|
|
body[_type]['id'] = uuidutils.generate_uuid()
|
|
if _type == 'port' and 'fixed_ips' in body[_type]:
|
|
ip_dict = body[_type]['fixed_ips'][0]
|
|
self._check_port_ip_conflict(ip_dict['subnet_id'],
|
|
ip_dict['ip_address'])
|
|
if _type == 'security_group':
|
|
body[_type]['security_group_rules'] = [
|
|
{'remote_group_id': None,
|
|
'direction': 'egress',
|
|
'remote_ip_prefix': None,
|
|
'protocol': None,
|
|
'port_range_max': None,
|
|
'port_range_min': None,
|
|
'ethertype': 'IPv4',
|
|
'id': uuidutils.generate_uuid()},
|
|
{'remote_group_id': None,
|
|
'direction': 'egress',
|
|
'remote_ip_prefix': None,
|
|
'protocol': None,
|
|
'port_range_max': None,
|
|
'port_range_min': None,
|
|
'ethertype': 'IPv6',
|
|
'id': uuidutils.generate_uuid()},
|
|
]
|
|
res_list = self._get_res_list(_type)
|
|
res = dict(body[_type])
|
|
res_list.append(res)
|
|
return res
|
|
|
|
def list_resources(self, _type, ctx, filters):
|
|
res_list = self._get_res_list(_type)
|
|
ret_list = []
|
|
for res in res_list:
|
|
match = True
|
|
for filter in filters:
|
|
if filter['key'] not in res:
|
|
match = False
|
|
break
|
|
if res[filter['key']] != filter['value']:
|
|
match = False
|
|
break
|
|
if match:
|
|
ret_list.append(res)
|
|
return ret_list
|
|
|
|
@staticmethod
|
|
def _get_ip_suffix():
|
|
# three elements should be enough
|
|
suffix_list = ['3', '4', '5']
|
|
index = 0
|
|
while True:
|
|
yield suffix_list[index]
|
|
index += 1
|
|
index %= 3
|
|
|
|
def create_ports(self, ctx, body):
|
|
if 'fixed_ips' in body['port']:
|
|
return self.create_resources('port', ctx, body)
|
|
net_id = body['port']['network_id']
|
|
subnets = self._get_res_list('subnet')
|
|
fixed_ip_list = []
|
|
for subnet in subnets:
|
|
if subnet['network_id'] == net_id:
|
|
cidr = subnet['cidr']
|
|
ip_prefix = cidr[:cidr.rindex('.') + 1]
|
|
mac_prefix = 'fa:16:3e:96:41:0'
|
|
if 'device_owner' in body['port']:
|
|
ip = ip_prefix + '2'
|
|
body['port']['mac_address'] = mac_prefix + '2'
|
|
else:
|
|
suffix = self.ip_suffix_gen.next()
|
|
ip = ip_prefix + suffix
|
|
body['port']['mac_address'] = mac_prefix + suffix
|
|
fixed_ip_list.append({'ip_address': ip,
|
|
'subnet_id': subnet['id']})
|
|
body['port']['fixed_ips'] = fixed_ip_list
|
|
return self.create_resources('port', ctx, body)
|
|
|
|
def list_ports(self, ctx, filters):
|
|
return self.list_resources('port', ctx, filters)
|
|
|
|
def list_security_groups(self, ctx, filters):
|
|
return self.list_resources('security_group', ctx, filters)
|
|
|
|
def delete_ports(self, ctx, port_id):
|
|
port_list = self._get_res_list('port')
|
|
for i, port in enumerate(port_list):
|
|
if port['id'] == port_id:
|
|
break
|
|
port_list.pop(i)
|
|
|
|
def get_networks(self, ctx, network_id):
|
|
return self.list_resources(
|
|
'network', ctx,
|
|
[{'key': 'id', 'comparator': 'eq', 'value': network_id}])[0]
|
|
|
|
def list_subnets(self, ctx, filters):
|
|
return self.list_resources('subnet', ctx, filters)
|
|
|
|
def get_subnets(self, ctx, subnet_id):
|
|
return self.list_resources(
|
|
'subnet', ctx,
|
|
[{'key': 'id', 'comparator': 'eq', 'value': subnet_id}])[0]
|
|
|
|
def create_servers(self, ctx, body):
|
|
# do nothing here since it will be mocked
|
|
pass
|
|
|
|
def get_security_groups(self, ctx, sg_id):
|
|
sg = self.list_resources(
|
|
'security_group', ctx,
|
|
[{'key': 'id', 'comparator': 'eq', 'value': sg_id}])[0]
|
|
# need to do a deep copy because we will traverse the security group's
|
|
# 'security_group_rules' field and make change to the group
|
|
ret_sg = copy.deepcopy(sg)
|
|
return ret_sg
|
|
|
|
def create_security_group_rules(self, ctx, body):
|
|
for _rule in body['security_group_rules']:
|
|
sg_id = _rule['security_group_id']
|
|
sg = self.list_resources(
|
|
'security_group', ctx,
|
|
[{'key': 'id', 'comparator': 'eq', 'value': sg_id}])[0]
|
|
new_rule = copy.copy(_rule)
|
|
match_found = False
|
|
for rule in sg['security_group_rules']:
|
|
old_rule = copy.copy(rule)
|
|
if new_rule == old_rule:
|
|
match_found = True
|
|
break
|
|
if match_found:
|
|
raise q_exceptions.Conflict()
|
|
sg['security_group_rules'].append(new_rule)
|
|
|
|
def delete_security_group_rules(self, ctx, rule_id):
|
|
res_list = self._get_res_list('security_group')
|
|
for sg in res_list:
|
|
for rule in sg['security_group_rules']:
|
|
if rule['id'] == rule_id:
|
|
sg['security_group_rules'].remove(rule)
|
|
return
|
|
|
|
|
|
class ServerTest(unittest.TestCase):
|
|
def setUp(self):
|
|
core.initialize()
|
|
core.ModelBase.metadata.create_all(core.get_engine())
|
|
self.context = context.Context()
|
|
self.project_id = 'test_project'
|
|
self.controller = FakeServerController(self.project_id)
|
|
|
|
def _prepare_pod(self, bottom_pod_num=1):
|
|
t_pod = {'pod_id': 't_pod_uuid', 'pod_name': 't_region',
|
|
'az_name': ''}
|
|
api.create_pod(self.context, t_pod)
|
|
if bottom_pod_num == 1:
|
|
b_pod = {'pod_id': 'b_pod_uuid', 'pod_name': 'b_region',
|
|
'az_name': 'b_az'}
|
|
api.create_pod(self.context, b_pod)
|
|
return t_pod, b_pod
|
|
b_pods = []
|
|
for i in xrange(1, bottom_pod_num + 1):
|
|
b_pod = {'pod_id': 'b_pod_%d_uuid' % i,
|
|
'pod_name': 'b_region_%d' % i,
|
|
'az_name': 'b_az_%d' % i}
|
|
api.create_pod(self.context, b_pod)
|
|
b_pods.append(b_pod)
|
|
return t_pod, b_pods
|
|
|
|
def test_get_or_create_route(self):
|
|
t_pod, b_pod = self._prepare_pod()
|
|
route, is_own = self.controller._get_or_create_route(
|
|
self.context, b_pod, 'test_top_id', 'port')
|
|
self.assertTrue(is_own)
|
|
self.assertEqual('test_top_id', route['top_id'])
|
|
self.assertIsNone(route['bottom_id'])
|
|
self.assertEqual('port', route['resource_type'])
|
|
self.assertEqual(self.project_id, route['project_id'])
|
|
|
|
def test_get_or_create_route_conflict(self):
|
|
t_pod, b_pod = self._prepare_pod()
|
|
self.controller._get_or_create_route(
|
|
self.context, b_pod, 'test_top_id', 'port')
|
|
route, status = self.controller._get_or_create_route(
|
|
self.context, b_pod, 'test_top_id', 'port')
|
|
self.assertEqual(lock_handle.NONE_DONE, status)
|
|
self.assertIsNone(route)
|
|
|
|
def test_get_or_create_route_conflict_expire(self):
|
|
t_pod, b_pod = self._prepare_pod()
|
|
route, is_own = self.controller._get_or_create_route(
|
|
self.context, b_pod, 'test_top_id', 'port')
|
|
# manually set update time to expire the routing entry
|
|
with self.context.session.begin():
|
|
update_time = route['created_at'] - datetime.timedelta(0, 60)
|
|
core.update_resource(self.context, models.ResourceRouting,
|
|
route['id'], {'updated_at': update_time})
|
|
new_route, is_own = self.controller._get_or_create_route(
|
|
self.context, b_pod, 'test_top_id', 'port')
|
|
self.assertTrue(is_own)
|
|
self.assertEqual('test_top_id', new_route['top_id'])
|
|
self.assertIsNone(new_route['bottom_id'])
|
|
self.assertEqual('port', new_route['resource_type'])
|
|
self.assertEqual(self.project_id, new_route['project_id'])
|
|
|
|
def test_get_or_create_route_conflict_expire_has_bottom_res(self):
|
|
t_pod, b_pod = self._prepare_pod()
|
|
route, is_own = self.controller._get_or_create_route(
|
|
self.context, b_pod, 'test_top_id', 'port')
|
|
# manually set update time to expire the routing entry
|
|
with self.context.session.begin():
|
|
update_time = route['created_at'] - datetime.timedelta(0, 60)
|
|
core.update_resource(self.context, models.ResourceRouting,
|
|
route['id'], {'updated_at': update_time})
|
|
# insert a fake bottom port
|
|
BOTTOM_PORTS.append({'id': 'test_bottom_id', 'name': 'test_top_id'})
|
|
new_route, status = self.controller._get_or_create_route(
|
|
self.context, b_pod, 'test_top_id', 'port')
|
|
self.assertEqual(lock_handle.RES_DONE, status)
|
|
self.assertEqual('test_top_id', new_route['top_id'])
|
|
self.assertEqual('test_bottom_id', new_route['bottom_id'])
|
|
self.assertEqual('port', new_route['resource_type'])
|
|
self.assertEqual(self.project_id, new_route['project_id'])
|
|
|
|
def test_prepare_neutron_element(self):
|
|
t_pod, b_pod = self._prepare_pod()
|
|
port = {'id': 'top_port_id'}
|
|
body = {'port': {'name': 'top_port_id'}}
|
|
_, bottom_port_id = self.controller._prepare_neutron_element(
|
|
self.context, b_pod, port, 'port', body)
|
|
mappings = api.get_bottom_mappings_by_top_id(self.context,
|
|
'top_port_id', 'port')
|
|
self.assertEqual(bottom_port_id, mappings[0][1])
|
|
|
|
@patch.object(FakeClient, 'create_resources')
|
|
def test_prepare_neutron_element_create_res_exception(self, mock_method):
|
|
mock_method.side_effect = FakeException()
|
|
t_pod, b_pod = self._prepare_pod()
|
|
port = {'id': 'top_port_id'}
|
|
body = {'port': {'name': 'top_port_id'}}
|
|
self.assertRaises(FakeException,
|
|
self.controller._prepare_neutron_element,
|
|
self.context, b_pod, port, 'port', body)
|
|
mappings = api.get_bottom_mappings_by_top_id(self.context,
|
|
'top_port_id', 'port')
|
|
self.assertEqual(0, len(mappings))
|
|
|
|
def _check_routes(self):
|
|
for res in (TOP_NETS, TOP_SUBNETS, BOTTOM_NETS, BOTTOM_SUBNETS):
|
|
self.assertEqual(1, len(res))
|
|
self.assertEqual(2, len(TOP_PORTS))
|
|
self.assertEqual(2, len(BOTTOM_PORTS))
|
|
|
|
with self.context.session.begin():
|
|
routes = core.query_resource(self.context,
|
|
models.ResourceRouting, [], [])
|
|
self.assertEqual(4, len(routes))
|
|
actual = [[], [], [], []]
|
|
for region in ('t_region', 'b_region'):
|
|
actual[0].append(self.controller._get_client(
|
|
region).list_resources('network', self.context, [])[0]['id'])
|
|
actual[1].append(self.controller._get_client(
|
|
region).list_resources('subnet', self.context, [])[0]['id'])
|
|
t_ports = self.controller._get_client(
|
|
region).list_resources('port', self.context, [])
|
|
if 'device_id' in t_ports[0]:
|
|
actual[2].append(t_ports[0]['id'])
|
|
actual[3].append(t_ports[1]['id'])
|
|
else:
|
|
actual[2].append(t_ports[1]['id'])
|
|
actual[3].append(t_ports[0]['id'])
|
|
expect = [[route['top_id'], route['bottom_id']] for route in routes]
|
|
self.assertItemsEqual(expect, actual)
|
|
|
|
def test_handle_network(self):
|
|
t_pod, b_pod = self._prepare_pod()
|
|
net = {'id': 'top_net_id'}
|
|
subnet = {'id': 'top_subnet_id',
|
|
'network_id': 'top_net_id',
|
|
'ip_version': 4,
|
|
'cidr': '10.0.0.0/24',
|
|
'gateway_ip': '10.0.0.1',
|
|
'allocation_pools': {'start': '10.0.0.2',
|
|
'end': '10.0.0.254'},
|
|
'enable_dhcp': True}
|
|
TOP_NETS.append(net)
|
|
TOP_SUBNETS.append(subnet)
|
|
self.controller._handle_network(self.context, b_pod, net, [subnet])
|
|
self._check_routes()
|
|
|
|
def test_handle_port(self):
|
|
t_pod, b_pod = self._prepare_pod()
|
|
net = {'id': 'top_net_id'}
|
|
subnet = {'id': 'top_subnet_id',
|
|
'network_id': 'top_net_id',
|
|
'ip_version': 4,
|
|
'cidr': '10.0.0.0/24',
|
|
'gateway_ip': '10.0.0.1',
|
|
'allocation_pools': {'start': '10.0.0.2',
|
|
'end': '10.0.0.254'},
|
|
'enable_dhcp': True}
|
|
port = {
|
|
'id': 'top_port_id',
|
|
'network_id': 'top_net_id',
|
|
'mac_address': 'fa:16:3e:96:41:03',
|
|
'fixed_ips': [{'subnet_id': 'top_subnet_id',
|
|
'ip_address': '10.0.0.3'}]
|
|
}
|
|
TOP_NETS.append(net)
|
|
TOP_SUBNETS.append(subnet)
|
|
TOP_PORTS.append(port)
|
|
self.controller._handle_port(self.context, b_pod, port)
|
|
self._check_routes()
|
|
|
|
def _test_handle_network_dhcp_port(self, dhcp_ip):
|
|
t_pod, b_pod = self._prepare_pod()
|
|
|
|
top_net_id = 'top_net_id'
|
|
bottom_net_id = 'bottom_net_id'
|
|
top_subnet_id = 'top_subnet_id'
|
|
bottom_subnet_id = 'bottom_subnet_id'
|
|
t_net = {'id': top_net_id}
|
|
b_net = {'id': bottom_net_id}
|
|
t_subnet = {'id': top_subnet_id,
|
|
'network_id': top_net_id,
|
|
'ip_version': 4,
|
|
'cidr': '10.0.0.0/24',
|
|
'gateway_ip': '10.0.0.1',
|
|
'allocation_pools': {'start': '10.0.0.2',
|
|
'end': '10.0.0.254'},
|
|
'enable_dhcp': True}
|
|
b_subnet = {'id': bottom_subnet_id,
|
|
'network_id': bottom_net_id,
|
|
'ip_version': 4,
|
|
'cidr': '10.0.0.0/24',
|
|
'gateway_ip': '10.0.0.1',
|
|
'allocation_pools': {'start': '10.0.0.2',
|
|
'end': '10.0.0.254'},
|
|
'enable_dhcp': True}
|
|
b_dhcp_port = {'id': 'bottom_dhcp_port_id',
|
|
'network_id': bottom_net_id,
|
|
'fixed_ips': [
|
|
{'subnet_id': bottom_subnet_id,
|
|
'ip_address': dhcp_ip}
|
|
],
|
|
'mac_address': 'fa:16:3e:96:41:0a',
|
|
'binding:profile': {},
|
|
'device_id': 'reserved_dhcp_port',
|
|
'device_owner': 'network:dhcp'}
|
|
TOP_NETS.append(t_net)
|
|
TOP_SUBNETS.append(t_subnet)
|
|
BOTTOM_NETS.append(b_net)
|
|
BOTTOM_SUBNETS.append(b_subnet)
|
|
BOTTOM_PORTS.append(b_dhcp_port)
|
|
with self.context.session.begin():
|
|
core.create_resource(
|
|
self.context, models.ResourceRouting,
|
|
{'top_id': top_net_id, 'bottom_id': bottom_net_id,
|
|
'pod_id': b_pod['pod_id'], 'project_id': self.project_id,
|
|
'resource_type': 'network'})
|
|
core.create_resource(
|
|
self.context, models.ResourceRouting,
|
|
{'top_id': top_subnet_id, 'bottom_id': bottom_subnet_id,
|
|
'pod_id': b_pod['pod_id'], 'project_id': self.project_id,
|
|
'resource_type': 'subnet'})
|
|
self.controller._handle_network(self.context,
|
|
b_pod, t_net, [t_subnet])
|
|
self._check_routes()
|
|
|
|
def test_handle_network_dhcp_port_same_ip(self):
|
|
self._test_handle_network_dhcp_port('10.0.0.2')
|
|
|
|
def test_handle_network_dhcp_port_exist_diff_ip(self):
|
|
self._test_handle_network_dhcp_port('10.0.0.4')
|
|
|
|
@patch.object(pecan, 'abort')
|
|
@patch.object(FakeClient, 'create_servers')
|
|
@patch.object(context, 'extract_context_from_environ')
|
|
def test_post_with_network_az(self, mock_ctx, mock_create, mock_abort):
|
|
t_pod, b_pod = self._prepare_pod()
|
|
top_net_id = 'top_net_id'
|
|
top_subnet_id = 'top_subnet_id'
|
|
top_sg_id = 'top_sg_id'
|
|
t_net = {'id': top_net_id}
|
|
t_subnet = {'id': top_subnet_id,
|
|
'network_id': top_net_id,
|
|
'ip_version': 4,
|
|
'cidr': '10.0.0.0/24',
|
|
'gateway_ip': '10.0.0.1',
|
|
'allocation_pools': {'start': '10.0.0.2',
|
|
'end': '10.0.0.254'},
|
|
'enable_dhcp': True}
|
|
t_sg = {'id': top_sg_id, 'name': 'default', 'description': '',
|
|
'tenant_id': self.project_id,
|
|
'security_group_rules': [
|
|
{'remote_group_id': top_sg_id,
|
|
'direction': 'ingress',
|
|
'remote_ip_prefix': None,
|
|
'protocol': None,
|
|
'port_range_max': None,
|
|
'port_range_min': None,
|
|
'ethertype': 'IPv4'},
|
|
{'remote_group_id': None,
|
|
'direction': 'egress',
|
|
'remote_ip_prefix': None,
|
|
'protocol': None,
|
|
'port_range_max': None,
|
|
'port_range_min': None,
|
|
'ethertype': 'IPv4'},
|
|
]}
|
|
TOP_NETS.append(t_net)
|
|
TOP_SUBNETS.append(t_subnet)
|
|
TOP_SGS.append(t_sg)
|
|
|
|
server_name = 'test_server'
|
|
image_id = 'image_id'
|
|
flavor_id = 1
|
|
body = {
|
|
'server': {
|
|
'name': server_name,
|
|
'imageRef': image_id,
|
|
'flavorRef': flavor_id,
|
|
'availability_zone': b_pod['az_name'],
|
|
'networks': [{'uuid': top_net_id}]
|
|
}
|
|
}
|
|
mock_create.return_value = {'id': 'bottom_server_id'}
|
|
mock_ctx.return_value = self.context
|
|
|
|
# update top net for test purpose, correct az
|
|
TOP_NETS[0]['availability_zone_hints'] = ['b_az']
|
|
self.controller.post(**body)
|
|
|
|
# update top net for test purpose, wrong az
|
|
TOP_NETS[0]['availability_zone_hints'] = ['fake_az']
|
|
self.controller.post(**body)
|
|
|
|
# update top net for test purpose, correct az and wrong az
|
|
TOP_NETS[0]['availability_zone_hints'] = ['b_az', 'fake_az']
|
|
self.controller.post(**body)
|
|
|
|
msg = 'Network and server not in the same availability zone'
|
|
# abort two times
|
|
calls = [mock.call(400, msg), mock.call(400, msg)]
|
|
mock_abort.assert_has_calls(calls)
|
|
|
|
@patch.object(FakeClient, 'create_servers')
|
|
@patch.object(context, 'extract_context_from_environ')
|
|
def test_post(self, mock_ctx, mock_create):
|
|
t_pod, b_pod = self._prepare_pod()
|
|
top_net_id = 'top_net_id'
|
|
top_subnet_id = 'top_subnet_id'
|
|
top_sg_id = 'top_sg_id'
|
|
|
|
t_net = {'id': top_net_id}
|
|
t_subnet = {'id': top_subnet_id,
|
|
'network_id': top_net_id,
|
|
'ip_version': 4,
|
|
'cidr': '10.0.0.0/24',
|
|
'gateway_ip': '10.0.0.1',
|
|
'allocation_pools': {'start': '10.0.0.2',
|
|
'end': '10.0.0.254'},
|
|
'enable_dhcp': True}
|
|
t_sg = {'id': top_sg_id, 'name': 'default', 'description': '',
|
|
'tenant_id': self.project_id,
|
|
'security_group_rules': [
|
|
{'remote_group_id': top_sg_id,
|
|
'direction': 'ingress',
|
|
'remote_ip_prefix': None,
|
|
'protocol': None,
|
|
'port_range_max': None,
|
|
'port_range_min': None,
|
|
'ethertype': 'IPv4'},
|
|
{'remote_group_id': None,
|
|
'direction': 'egress',
|
|
'remote_ip_prefix': None,
|
|
'protocol': None,
|
|
'port_range_max': None,
|
|
'port_range_min': None,
|
|
'ethertype': 'IPv4'},
|
|
]}
|
|
TOP_NETS.append(t_net)
|
|
TOP_SUBNETS.append(t_subnet)
|
|
TOP_SGS.append(t_sg)
|
|
|
|
server_name = 'test_server'
|
|
image_id = 'image_id'
|
|
flavor_id = 1
|
|
body = {
|
|
'server': {
|
|
'name': server_name,
|
|
'imageRef': image_id,
|
|
'flavorRef': flavor_id,
|
|
'availability_zone': b_pod['az_name'],
|
|
'networks': [{'uuid': top_net_id}]
|
|
}
|
|
}
|
|
mock_create.return_value = {'id': 'bottom_server_id'}
|
|
mock_ctx.return_value = self.context
|
|
|
|
server_dict = self.controller.post(**body)['server']
|
|
|
|
for port in BOTTOM_PORTS:
|
|
if 'device_id' not in port:
|
|
bottom_port_id = port['id']
|
|
for sg in BOTTOM_SGS:
|
|
if sg['name'] == top_sg_id:
|
|
bottom_sg = sg
|
|
|
|
mock_create.assert_called_with(self.context, name=server_name,
|
|
image=image_id, flavor=flavor_id,
|
|
nics=[{'port-id': bottom_port_id}],
|
|
security_groups=[bottom_sg['id']])
|
|
# make sure remote group is extended to ip addresses
|
|
for rule in bottom_sg['security_group_rules']:
|
|
if rule['ethertype'] == 'IPv4' and rule['direction'] == 'ingress':
|
|
self.assertIsNone(rule['remote_group_id'])
|
|
self.assertEqual('10.0.0.0/24', rule['remote_ip_prefix'])
|
|
with self.context.session.begin():
|
|
routes = core.query_resource(self.context, models.ResourceRouting,
|
|
[{'key': 'resource_type',
|
|
'comparator': 'eq',
|
|
'value': 'server'}], [])
|
|
self.assertEqual(1, len(routes))
|
|
self.assertEqual(server_dict['id'], routes[0]['top_id'])
|
|
self.assertEqual(server_dict['id'], routes[0]['bottom_id'])
|
|
self.assertEqual(b_pod['pod_id'], routes[0]['pod_id'])
|
|
self.assertEqual(self.project_id, routes[0]['project_id'])
|
|
|
|
# make sure security group mapping is built
|
|
routes = core.query_resource(self.context, models.ResourceRouting,
|
|
[{'key': 'resource_type',
|
|
'comparator': 'eq',
|
|
'value': 'security_group'}], [])
|
|
self.assertEqual(1, len(routes))
|
|
self.assertEqual(top_sg_id, routes[0]['top_id'])
|
|
self.assertEqual(bottom_sg['id'], routes[0]['bottom_id'])
|
|
self.assertEqual(b_pod['pod_id'], routes[0]['pod_id'])
|
|
self.assertEqual(self.project_id, routes[0]['project_id'])
|
|
|
|
@patch.object(FakeClient, 'create_servers')
|
|
@patch.object(context, 'extract_context_from_environ')
|
|
def test_post_exception_retry(self, mock_ctx, mock_server):
|
|
t_pod, b_pod = self._prepare_pod()
|
|
top_net_id = 'top_net_id'
|
|
top_subnet_id = 'top_subnet_id'
|
|
top_sg_id = 'top_sg_id'
|
|
|
|
t_net = {'id': top_net_id}
|
|
t_subnet = {'id': top_subnet_id,
|
|
'network_id': top_net_id,
|
|
'ip_version': 4,
|
|
'cidr': '10.0.0.0/24',
|
|
'gateway_ip': '10.0.0.1',
|
|
'allocation_pools': {'start': '10.0.0.2',
|
|
'end': '10.0.0.254'},
|
|
'enable_dhcp': True}
|
|
t_sg = {'id': top_sg_id, 'name': 'test_sg', 'description': '',
|
|
'tenant_id': self.project_id,
|
|
'security_group_rules': [
|
|
{'remote_group_id': None,
|
|
'direction': 'ingress',
|
|
'remote_ip_prefix': '10.0.1.0/24',
|
|
'protocol': None,
|
|
'port_range_max': None,
|
|
'port_range_min': None,
|
|
'ethertype': 'IPv4'},
|
|
{'remote_group_id': None,
|
|
'direction': 'egress',
|
|
'remote_ip_prefix': None,
|
|
'protocol': None,
|
|
'port_range_max': None,
|
|
'port_range_min': None,
|
|
'ethertype': 'IPv4'},
|
|
]}
|
|
TOP_NETS.append(t_net)
|
|
TOP_SUBNETS.append(t_subnet)
|
|
TOP_SGS.append(t_sg)
|
|
|
|
server_name = 'test_server'
|
|
image_id = 'image_id'
|
|
flavor_id = 1
|
|
body = {
|
|
'server': {
|
|
'name': server_name,
|
|
'imageRef': image_id,
|
|
'flavorRef': flavor_id,
|
|
'availability_zone': b_pod['az_name'],
|
|
'networks': [{'uuid': top_net_id}],
|
|
'security_groups': [{'name': 'test_sg'}]
|
|
}
|
|
}
|
|
mock_server.return_value = {'id': 'bottom_server_id'}
|
|
mock_ctx.return_value = self.context
|
|
|
|
create_security_group_rules = FakeClient.create_security_group_rules
|
|
FakeClient.create_security_group_rules = mock.Mock()
|
|
FakeClient.create_security_group_rules.side_effect = \
|
|
q_exceptions.ConnectionFailed
|
|
|
|
self.assertRaises(q_exceptions.ConnectionFailed, self.controller.post,
|
|
**body)
|
|
with self.context.session.begin():
|
|
routes = core.query_resource(
|
|
self.context, models.ResourceRouting,
|
|
[{'key': 'top_sg_id', 'comparator': 'eq',
|
|
'value': t_sg['id']},
|
|
{'key': 'pod_id', 'comparator': 'eq',
|
|
'value': 'b_pod_uuid'}], [])
|
|
self.assertIsNone(routes[0]['bottom_id'])
|
|
|
|
# test we can redo after exception
|
|
FakeClient.create_security_group_rules = create_security_group_rules
|
|
self.controller.post(**body)
|
|
|
|
for port in BOTTOM_PORTS:
|
|
if 'device_id' not in port:
|
|
bottom_port_id = port['id']
|
|
for sg in BOTTOM_SGS:
|
|
if sg['name'] == top_sg_id:
|
|
bottom_sg = sg
|
|
|
|
mock_server.assert_called_with(self.context, name=server_name,
|
|
image=image_id, flavor=flavor_id,
|
|
nics=[{'port-id': bottom_port_id}],
|
|
security_groups=[bottom_sg['id']])
|
|
|
|
@patch.object(FakeClient, 'create_servers')
|
|
@patch.object(context, 'extract_context_from_environ')
|
|
def test_post_across_pods(self, mock_ctx, mock_create):
|
|
t_pod, b_pods = self._prepare_pod(2)
|
|
b_pod1, b_pod2 = b_pods
|
|
top_net1_id = 'top_net1_id'
|
|
top_subnet1_id = 'top_subnet1_id'
|
|
top_net2_id = 'top_net2_id'
|
|
top_subnet2_id = 'top_subnet2_id'
|
|
top_sg_id = 'top_sg_id'
|
|
|
|
t_net1 = {'id': top_net1_id}
|
|
t_subnet1 = {'id': top_subnet1_id,
|
|
'tenant_id': self.project_id,
|
|
'network_id': top_net1_id,
|
|
'ip_version': 4,
|
|
'cidr': '10.0.1.0/24',
|
|
'gateway_ip': '10.0.1.1',
|
|
'allocation_pools': {'start': '10.0.1.2',
|
|
'end': '10.0.1.254'},
|
|
'enable_dhcp': True}
|
|
t_net2 = {'id': top_net2_id}
|
|
t_subnet2 = {'id': top_subnet2_id,
|
|
'tenant_id': self.project_id,
|
|
'network_id': top_net2_id,
|
|
'ip_version': 4,
|
|
'cidr': '10.0.2.0/24',
|
|
'gateway_ip': '10.0.2.1',
|
|
'allocation_pools': {'start': '10.0.2.2',
|
|
'end': '10.0.2.254'},
|
|
'enable_dhcp': True}
|
|
t_sg = {'id': top_sg_id, 'name': 'default', 'description': '',
|
|
'tenant_id': self.project_id,
|
|
'security_group_rules': [
|
|
{'remote_group_id': top_sg_id,
|
|
'direction': 'ingress',
|
|
'remote_ip_prefix': None,
|
|
'protocol': None,
|
|
'port_range_max': None,
|
|
'port_range_min': None,
|
|
'ethertype': 'IPv4'},
|
|
{'remote_group_id': None,
|
|
'direction': 'egress',
|
|
'remote_ip_prefix': None,
|
|
'protocol': None,
|
|
'port_range_max': None,
|
|
'port_range_min': None,
|
|
'ethertype': 'IPv4'},
|
|
]}
|
|
TOP_NETS.append(t_net1)
|
|
TOP_SUBNETS.append(t_subnet1)
|
|
TOP_NETS.append(t_net2)
|
|
TOP_SUBNETS.append(t_subnet2)
|
|
TOP_SGS.append(t_sg)
|
|
|
|
image_id = 'image_id'
|
|
flavor_id = 1
|
|
mock_ctx.return_value = self.context
|
|
|
|
body = {
|
|
'server': {
|
|
'name': 'test_server1',
|
|
'imageRef': image_id,
|
|
'flavorRef': flavor_id,
|
|
'availability_zone': b_pod1['az_name'],
|
|
'networks': [{'uuid': top_net1_id}]
|
|
}
|
|
}
|
|
mock_create.return_value = {'id': 'bottom_server1_id'}
|
|
self.controller.post(**body)['server']
|
|
|
|
body = {
|
|
'server': {
|
|
'name': 'test_server2',
|
|
'imageRef': image_id,
|
|
'flavorRef': flavor_id,
|
|
'availability_zone': b_pod2['az_name'],
|
|
'networks': [{'uuid': top_net2_id}]
|
|
}
|
|
}
|
|
mock_create.return_value = {'id': 'bottom_server2_id'}
|
|
self.controller.post(**body)['server']
|
|
|
|
for port in BOTTOM1_PORTS:
|
|
if 'device_id' not in port:
|
|
bottom_port1_id = port['id']
|
|
for port in BOTTOM2_PORTS:
|
|
if 'device_id' not in port:
|
|
bottom_port2_id = port['id']
|
|
for sg in BOTTOM1_SGS:
|
|
if sg['name'] == top_sg_id:
|
|
bottom_sg1 = sg
|
|
for sg in BOTTOM2_SGS:
|
|
if sg['name'] == top_sg_id:
|
|
bottom_sg2 = sg
|
|
|
|
calls = [mock.call(self.context, name='test_server1', image=image_id,
|
|
flavor=flavor_id,
|
|
nics=[{'port-id': bottom_port1_id}],
|
|
security_groups=[bottom_sg1['id']]),
|
|
mock.call(self.context, name='test_server2', image=image_id,
|
|
flavor=flavor_id,
|
|
nics=[{'port-id': bottom_port2_id}],
|
|
security_groups=[bottom_sg2['id']])]
|
|
mock_create.assert_has_calls(calls)
|
|
|
|
# make sure remote group is extended to ip addresses
|
|
expected_ips = ['10.0.1.0/24', '10.0.2.0/24']
|
|
ips = []
|
|
for rule in bottom_sg1['security_group_rules']:
|
|
if rule['ethertype'] == 'IPv4' and rule['direction'] == 'ingress':
|
|
self.assertIsNone(rule['remote_group_id'])
|
|
ips.append(rule['remote_ip_prefix'])
|
|
self.assertEqual(expected_ips, ips)
|
|
ips = []
|
|
for rule in bottom_sg2['security_group_rules']:
|
|
if rule['ethertype'] == 'IPv4' and rule['direction'] == 'ingress':
|
|
self.assertIsNone(rule['remote_group_id'])
|
|
ips.append(rule['remote_ip_prefix'])
|
|
self.assertEqual(expected_ips, ips)
|
|
|
|
def tearDown(self):
|
|
core.ModelBase.metadata.drop_all(core.get_engine())
|
|
for res in RES_LIST:
|
|
del res[:]
|