From e43c4bc656872a4ea297357ccc9e4f545811859a Mon Sep 17 00:00:00 2001 From: zhiyuan_cai Date: Wed, 20 Apr 2016 16:44:40 +0800 Subject: [PATCH] Pass tempest list_server_filters test Change the following to pass tempest list_server_filters test: (1) Change response body of Nova API gateway image controller (2) Add network controller (3) Add server list filters support (4) Change response body of server list Change-Id: I96013e2a3c871640b530611e36fa0a219c5e0516 --- tricircle/common/lock_handle.py | 11 +- tricircle/common/utils.py | 4 + tricircle/network/plugin.py | 19 ++- tricircle/nova_apigw/controllers/image.py | 115 +++++++++++++++- tricircle/nova_apigw/controllers/network.py | 50 +++++++ tricircle/nova_apigw/controllers/root.py | 2 + tricircle/nova_apigw/controllers/server.py | 59 ++++++--- .../nova_apigw/controllers/test_server.py | 124 ++++++++++++++++-- 8 files changed, 342 insertions(+), 42 deletions(-) create mode 100644 tricircle/nova_apigw/controllers/network.py diff --git a/tricircle/common/lock_handle.py b/tricircle/common/lock_handle.py index 5140aab2..d580b8d2 100644 --- a/tricircle/common/lock_handle.py +++ b/tricircle/common/lock_handle.py @@ -30,10 +30,11 @@ NONE_DONE = 2 # neither router nor bottom resources exists def get_or_create_route(t_ctx, q_ctx, - project_id, pod, _id, _type, list_ele_method): + project_id, pod, ele, _type, list_ele_method): # use configuration option later route_expire_threshold = 30 + _id = ele['id'] with t_ctx.session.begin(): routes = core.query_resource( t_ctx, models.ResourceRouting, @@ -53,7 +54,7 @@ def get_or_create_route(t_ctx, q_ctx, # a race here that other worker is updating this route, we # need to check if the corresponding element has been # created by other worker - eles = list_ele_method(t_ctx, q_ctx, pod, _id, _type) + eles = list_ele_method(t_ctx, q_ctx, pod, ele, _type) if eles: route['bottom_id'] = eles[0]['id'] core.update_resource(t_ctx, @@ -92,7 +93,7 @@ def get_or_create_element(t_ctx, q_ctx, max_tries = 5 for _ in xrange(max_tries): route, status = get_or_create_route( - t_ctx, q_ctx, project_id, pod, ele['id'], _type, list_ele_method) + t_ctx, q_ctx, project_id, pod, ele, _type, list_ele_method) if not route: eventlet.sleep(0) continue @@ -101,7 +102,7 @@ def get_or_create_element(t_ctx, q_ctx, break if status == NONE_DONE: try: - ele = create_ele_method(t_ctx, q_ctx, pod, body, _type) + new_ele = create_ele_method(t_ctx, q_ctx, pod, body, _type) except Exception: with t_ctx.session.begin(): try: @@ -118,7 +119,7 @@ def get_or_create_element(t_ctx, q_ctx, # NOTE(zhiyuan) it's safe to update route, the bottom network # has been successfully created, so other worker will not # delete this route - route['bottom_id'] = ele['id'] + route['bottom_id'] = new_ele['id'] core.update_resource(t_ctx, models.ResourceRouting, route['id'], route) break diff --git a/tricircle/common/utils.py b/tricircle/common/utils.py index 67f3e036..0ed7576c 100644 --- a/tricircle/common/utils.py +++ b/tricircle/common/utils.py @@ -112,3 +112,7 @@ def check_string_length(value, name=None, min_len=0, max_len=None): msg = _("%(name)s has more than %(max_length)s " "characters.") % {'name': name, 'max_length': max_len} raise t_exceptions.InvalidInput(message=msg) + + +def get_bottom_network_name(network): + return '%s#%s' % (network['id'], network['name']) diff --git a/tricircle/network/plugin.py b/tricircle/network/plugin.py index 4d282bc8..c463042f 100644 --- a/tricircle/network/plugin.py +++ b/tricircle/network/plugin.py @@ -49,6 +49,7 @@ import tricircle.common.exceptions as t_exceptions from tricircle.common.i18n import _ from tricircle.common.i18n import _LI import tricircle.common.lock_handle as t_lock +from tricircle.common import utils from tricircle.common import xrpcapi import tricircle.db.api as db_api from tricircle.db import core @@ -629,9 +630,9 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, def _prepare_top_element(self, t_ctx, q_ctx, project_id, pod, ele, _type, body): - def list_resources(t_ctx_, q_ctx_, pod_, _id_, _type_): + def list_resources(t_ctx_, q_ctx_, pod_, ele_, _type_): return getattr(self, 'get_%ss' % _type_)( - q_ctx_, filters={'name': _id_}) + q_ctx_, filters={'name': ele_['id']}) def create_resources(t_ctx_, q_ctx_, pod_, body_, _type_): return getattr(self, 'create_%s' % _type_)(q_ctx_, body_) @@ -643,11 +644,15 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, def _prepare_bottom_element(self, t_ctx, project_id, pod, ele, _type, body): - def list_resources(t_ctx_, q_ctx, pod_, _id_, _type_): + def list_resources(t_ctx_, q_ctx, pod_, ele_, _type_): client = self._get_client(pod_['pod_name']) - return client.list_resources(_type_, t_ctx_, [{'key': 'name', - 'comparator': 'eq', - 'value': _id_}]) + if _type_ == t_constants.RT_NETWORK: + value = utils.get_bottom_network_name(ele_) + else: + value = ele_['id'] + return client.list_resources(_type_, t_ctx_, + [{'key': 'name', 'comparator': 'eq', + 'value': value}]) def create_resources(t_ctx_, q_ctx, pod_, body_, _type_): client = self._get_client(pod_['pod_name']) @@ -753,7 +758,7 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, net_body = { 'network': { 'tenant_id': project_id, - 'name': t_net['id'], + 'name': utils.get_bottom_network_name(t_net), 'admin_state_up': True } } diff --git a/tricircle/nova_apigw/controllers/image.py b/tricircle/nova_apigw/controllers/image.py index 67ca4ef2..9896c3fb 100644 --- a/tricircle/nova_apigw/controllers/image.py +++ b/tricircle/nova_apigw/controllers/image.py @@ -16,9 +16,52 @@ import pecan from pecan import expose from pecan import rest +import re +import urlparse import tricircle.common.client as t_client +from tricircle.common import constants import tricircle.common.context as t_context +import tricircle.db.api as db_api + + +def url_join(*parts): + """Convenience method for joining parts of a URL + + Any leading and trailing '/' characters are removed, and the parts joined + together with '/' as a separator. If last element of 'parts' is an empty + string, the returned URL will have a trailing slash. + """ + parts = parts or [''] + clean_parts = [part.strip('/') for part in parts if part] + if not parts[-1]: + # Empty last element should add a trailing slash + clean_parts.append('') + return '/'.join(clean_parts) + + +def remove_trailing_version_from_href(href): + """Removes the api version from the href. + + Given: 'http://www.nova.com/compute/v1.1' + Returns: 'http://www.nova.com/compute' + + Given: 'http://www.nova.com/v1.1' + Returns: 'http://www.nova.com' + + """ + parsed_url = urlparse.urlsplit(href) + url_parts = parsed_url.path.rsplit('/', 1) + + # NOTE: this should match vX.X or vX + expression = re.compile(r'^v([0-9]+|[0-9]+\.[0-9]+)(/.*|$)') + if not expression.match(url_parts.pop()): + raise ValueError('URL %s does not contain version' % href) + + new_path = url_join(*url_parts) + parsed_url = list(parsed_url) + parsed_url[2] = new_path + return urlparse.urlunsplit(parsed_url) class ImageController(rest.RestController): @@ -27,6 +70,72 @@ class ImageController(rest.RestController): self.project_id = project_id self.client = t_client.Client() + def _get_links(self, context, image): + nova_url = self.client.get_endpoint( + context, db_api.get_top_pod(context)['pod_id'], + constants.ST_NOVA) + nova_url = nova_url.replace('/$(tenant_id)s', '') + self_link = url_join(nova_url, self.project_id, 'images', image['id']) + bookmark_link = url_join( + remove_trailing_version_from_href(nova_url), + self.project_id, 'images', image['id']) + glance_url = self.client.get_endpoint( + context, db_api.get_top_pod(context)['pod_id'], + constants.ST_GLANCE) + alternate_link = '/'.join([glance_url, 'images', image['id']]) + return [{'rel': 'self', 'href': self_link}, + {'rel': 'bookmark', 'href': bookmark_link}, + {'rel': 'alternate', + 'type': 'application/vnd.openstack.image', + 'href': alternate_link}] + + @staticmethod + def _format_date(dt): + """Return standard format for a given datetime string.""" + if dt is not None: + date_string = dt.split('.')[0] + date_string += 'Z' + return date_string + + @staticmethod + def _get_status(image): + """Update the status field to standardize format.""" + return { + 'active': 'ACTIVE', + 'queued': 'SAVING', + 'saving': 'SAVING', + 'deleted': 'DELETED', + 'pending_delete': 'DELETED', + 'killed': 'ERROR', + }.get(image.get('status'), 'UNKNOWN') + + @staticmethod + def _get_progress(image): + return { + 'queued': 25, + 'saving': 50, + 'active': 100, + }.get(image.get('status'), 0) + + def _construct_list_image_entry(self, context, image): + return {'id': image['id'], + 'name': image.get('name'), + 'links': self._get_links(context, image)} + + def _construct_show_image_entry(self, context, image): + return { + 'id': image['id'], + 'name': image.get('name'), + 'minRam': int(image.get('min_ram') or 0), + 'minDisk': int(image.get('min_disk') or 0), + 'metadata': image.get('properties', {}), + 'created': self._format_date(image.get('created_at')), + 'updated': self._format_date(image.get('updated_at')), + 'status': self._get_status(image), + 'progress': self._get_progress(image), + 'links': self._get_links(context, image) + } + @expose(generic=True, template='json') def get_one(self, _id): context = t_context.extract_context_from_environ() @@ -34,10 +143,12 @@ class ImageController(rest.RestController): if not image: pecan.abort(404, 'Image not found') return - return {'image': image} + return {'image': self._construct_show_image_entry(context, image)} @expose(generic=True, template='json') def get_all(self): context = t_context.extract_context_from_environ() images = self.client.list_images(context) - return {'images': images} + ret_images = [self._construct_list_image_entry( + context, image) for image in images] + return {'images': ret_images} diff --git a/tricircle/nova_apigw/controllers/network.py b/tricircle/nova_apigw/controllers/network.py new file mode 100644 index 00000000..abbaa616 --- /dev/null +++ b/tricircle/nova_apigw/controllers/network.py @@ -0,0 +1,50 @@ +# Copyright (c) 2015 Huawei Tech. 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 pecan +from pecan import expose +from pecan import rest + +import tricircle.common.client as t_client +import tricircle.common.context as t_context + + +class NetworkController(rest.RestController): + + def __init__(self, project_id): + self.project_id = project_id + self.client = t_client.Client() + + @staticmethod + def _construct_network_entry(network): + network['uuid'] = network['id'] + network['label'] = network['name'] + return network + + @expose(generic=True, template='json') + def get_one(self, _id): + context = t_context.extract_context_from_environ() + network = self.client.get_networks(context, _id) + if not network: + pecan.abort(404, 'Network not found') + return + return {'network': self._construct_network_entry(network)} + + @expose(generic=True, template='json') + def get_all(self): + context = t_context.extract_context_from_environ() + networks = self.client.list_networks(context) + return {'networks': [self._construct_network_entry( + network) for network in networks]} diff --git a/tricircle/nova_apigw/controllers/root.py b/tricircle/nova_apigw/controllers/root.py index 85611f99..9ab47d39 100755 --- a/tricircle/nova_apigw/controllers/root.py +++ b/tricircle/nova_apigw/controllers/root.py @@ -29,6 +29,7 @@ from tricircle.nova_apigw.controllers import action from tricircle.nova_apigw.controllers import aggregate from tricircle.nova_apigw.controllers import flavor from tricircle.nova_apigw.controllers import image +from tricircle.nova_apigw.controllers import network from tricircle.nova_apigw.controllers import quota_sets from tricircle.nova_apigw.controllers import server from tricircle.nova_apigw.controllers import volume @@ -94,6 +95,7 @@ class V21Controller(object): 'images': image.ImageController, 'os-quota-sets': quota_sets.QuotaSetsController, 'limits': quota_sets.LimitsController, + 'os-networks': network.NetworkController } self.server_sub_controller = { 'os-volume_attachments': volume.VolumeController, diff --git a/tricircle/nova_apigw/controllers/server.py b/tricircle/nova_apigw/controllers/server.py index a572a0c0..9807bc55 100644 --- a/tricircle/nova_apigw/controllers/server.py +++ b/tricircle/nova_apigw/controllers/server.py @@ -56,24 +56,45 @@ class ServerController(rest.RestController): self.clients[pod_name] = t_client.Client(pod_name) return self.clients[pod_name] - def _get_all(self, context): + def _get_all(self, context, params): + filters = [{'key': key, + 'comparator': 'eq', + 'value': value} for key, value in params.iteritems()] ret = [] pods = db_api.list_pods(context) for pod in pods: if not pod['az_name']: continue client = self._get_client(pod['pod_name']) - servers = client.list_servers(context) + servers = client.list_servers(context, filters=filters) self._remove_fip_info(servers) ret.extend(servers) return ret + @staticmethod + def _construct_brief_server_entry(server): + return {'id': server['id'], + 'name': server.get('name'), + 'links': server.get('links')} + + @staticmethod + def _transform_network_name(server): + if 'addresses' not in server: + return + keys = [key for key in server['addresses'].iterkeys()] + for key in keys: + value = server['addresses'].pop(key) + network_name = key.split('#')[1] + server['addresses'][network_name] = value + return server + @expose(generic=True, template='json') - def get_one(self, _id): + def get_one(self, _id, **kwargs): context = t_context.extract_context_from_environ() if _id == 'detail': - return {'servers': self._get_all(context)} + return {'servers': [self._transform_network_name( + server) for server in self._get_all(context, kwargs)]} mappings = db_api.get_bottom_mappings_by_top_id( context, _id, constants.RT_SERVER) @@ -88,12 +109,14 @@ class ServerController(rest.RestController): pecan.abort(404, 'Server not found') return else: + self._transform_network_name(server) return {'server': server} @expose(generic=True, template='json') - def get_all(self): + def get_all(self, **kwargs): context = t_context.extract_context_from_environ() - return {'servers': self._get_all(context)} + return {'servers': [self._construct_brief_server_entry( + server) for server in self._get_all(context, kwargs)]} @expose(generic=True, template='json') def post(self, **kw): @@ -216,6 +239,7 @@ class ServerController(rest.RestController): 'pod_id': pod['pod_id'], 'project_id': self.project_id, 'resource_type': constants.RT_SERVER}) + pecan.response.status = 202 return {'server': server} @expose(generic=True, template='json') @@ -265,21 +289,21 @@ class ServerController(rest.RestController): return pecan.response def _get_or_create_route(self, context, pod, _id, _type): - def list_resources(t_ctx, q_ctx, pod_, _id_, _type_): + def list_resources(t_ctx, q_ctx, pod_, ele, _type_): client = self._get_client(pod_['pod_name']) return client.list_resources(_type_, t_ctx, [{'key': 'name', 'comparator': 'eq', - 'value': _id_}]) + 'value': ele['id']}]) return t_lock.get_or_create_route(context, None, - self.project_id, pod, _id, _type, - list_resources) + self.project_id, pod, {'id': _id}, + _type, list_resources) def _get_create_network_body(self, network): body = { 'network': { 'tenant_id': self.project_id, - 'name': network['id'], + 'name': utils.get_bottom_network_name(network), 'admin_state_up': True } } @@ -342,11 +366,16 @@ class ServerController(rest.RestController): return body def _prepare_neutron_element(self, context, pod, ele, _type, body): - def list_resources(t_ctx, q_ctx, pod_, _id_, _type_): + def list_resources(t_ctx, q_ctx, pod_, ele_, _type_): client = self._get_client(pod_['pod_name']) - return client.list_resources(_type_, t_ctx, [{'key': 'name', - 'comparator': 'eq', - 'value': _id_}]) + if _type_ == constants.RT_NETWORK: + value = utils.get_bottom_network_name(ele_) + else: + value = ele_['id'] + return client.list_resources( + _type_, t_ctx, + [{'key': 'name', 'comparator': 'eq', + 'value': value}]) def create_resources(t_ctx, q_ctx, pod_, body_, _type_): client = self._get_client(pod_['pod_name']) diff --git a/tricircle/tests/unit/nova_apigw/controllers/test_server.py b/tricircle/tests/unit/nova_apigw/controllers/test_server.py index 0ef45967..3f2d7e5d 100644 --- a/tricircle/tests/unit/nova_apigw/controllers/test_server.py +++ b/tricircle/tests/unit/nova_apigw/controllers/test_server.py @@ -52,8 +52,9 @@ BOTTOM_NETS = BOTTOM1_NETS BOTTOM_SUBNETS = BOTTOM1_SUBNETS BOTTOM_PORTS = BOTTOM1_PORTS BOTTOM_SGS = BOTTOM1_SGS +BOTTOM_SERVERS = [] -RES_LIST = [TOP_NETS, TOP_SUBNETS, TOP_PORTS, TOP_SGS, +RES_LIST = [TOP_NETS, TOP_SUBNETS, TOP_PORTS, TOP_SGS, BOTTOM_SERVERS, BOTTOM1_NETS, BOTTOM1_SUBNETS, BOTTOM1_PORTS, BOTTOM1_SGS, BOTTOM2_NETS, BOTTOM2_SUBNETS, BOTTOM2_PORTS, BOTTOM2_SGS] @@ -90,7 +91,8 @@ class FakeClient(object): 'bottom': {'network': BOTTOM_NETS, 'subnet': BOTTOM_SUBNETS, 'port': BOTTOM_PORTS, - 'security_group': BOTTOM_SGS}, + 'security_group': BOTTOM_SGS, + 'server': BOTTOM_SERVERS}, 'bottom2': {'network': BOTTOM2_NETS, 'subnet': BOTTOM2_SUBNETS, 'port': BOTTOM2_PORTS, @@ -224,9 +226,34 @@ class FakeClient(object): '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 create_servers(self, ctx, **body): + body['id'] = uuidutils.generate_uuid() + BOTTOM_SERVERS.append(body) + return body + + def list_servers(self, ctx, filters): + ret_servers = [] + for b_server in self.list_resources('server', ctx, filters): + ret_server = copy.deepcopy(b_server) + for nic in ret_server['nics']: + ports = self.list_ports( + ctx, [{'key': 'id', 'comparator': 'eq', + 'value': nic['port-id']}]) + nets = self.list_resources( + 'network', ctx, [{'key': 'id', 'comparator': 'eq', + 'value': ports[0]['network_id']}]) + ret_server['addresses'] = { + nets[0]['name']: [ + {'OS-EXT-IPS-MAC:mac_addr': ports[0]['mac_address'], + 'version': 4, + 'addr': ports[0]['fixed_ips'][0]['ip_address'], + 'OS-EXT-IPS:type': 'fixed'}]} + ret_servers.append(ret_server) + return ret_servers + + def get_servers(self, ctx, server_id): + return self.list_servers( + ctx, [{'key': 'id', 'comparator': 'eq', 'value': server_id}])[0] def get_security_groups(self, ctx, sg_id): sg = self.list_resources( @@ -399,7 +426,7 @@ class ServerTest(unittest.TestCase): def test_handle_network(self): t_pod, b_pod = self._prepare_pod() - net = {'id': 'top_net_id'} + net = {'id': 'top_net_id', 'name': 'net'} subnet = {'id': 'top_subnet_id', 'network_id': 'top_net_id', 'ip_version': 4, @@ -415,7 +442,7 @@ class ServerTest(unittest.TestCase): def test_handle_port(self): t_pod, b_pod = self._prepare_pod() - net = {'id': 'top_net_id'} + net = {'id': 'top_net_id', 'name': 'net'} subnet = {'id': 'top_subnet_id', 'network_id': 'top_net_id', 'ip_version': 4, @@ -444,7 +471,7 @@ class ServerTest(unittest.TestCase): bottom_net_id = 'bottom_net_id' top_subnet_id = 'top_subnet_id' bottom_subnet_id = 'bottom_subnet_id' - t_net = {'id': top_net_id} + t_net = {'id': top_net_id, 'name': 'net'} b_net = {'id': bottom_net_id} t_subnet = {'id': top_subnet_id, 'network_id': top_net_id, @@ -498,6 +525,7 @@ class ServerTest(unittest.TestCase): def test_handle_network_dhcp_port_exist_diff_ip(self): self._test_handle_network_dhcp_port('10.0.0.4') + @patch.object(pecan, 'response', new=FakeResponse) @patch.object(pecan, 'abort') @patch.object(FakeClient, 'create_servers') @patch.object(context, 'extract_context_from_environ') @@ -506,7 +534,7 @@ class ServerTest(unittest.TestCase): 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_net = {'id': top_net_id, 'name': 'net'} t_subnet = {'id': top_subnet_id, 'network_id': top_net_id, 'ip_version': 4, @@ -569,6 +597,7 @@ class ServerTest(unittest.TestCase): calls = [mock.call(400, msg), mock.call(400, msg)] mock_abort.assert_has_calls(calls) + @patch.object(pecan, 'response', new=FakeResponse) @patch.object(FakeClient, 'create_servers') @patch.object(context, 'extract_context_from_environ') def test_post(self, mock_ctx, mock_create): @@ -577,7 +606,7 @@ class ServerTest(unittest.TestCase): top_subnet_id = 'top_subnet_id' top_sg_id = 'top_sg_id' - t_net = {'id': top_net_id} + t_net = {'id': top_net_id, 'name': 'net'} t_subnet = {'id': top_subnet_id, 'network_id': top_net_id, 'ip_version': 4, @@ -641,6 +670,7 @@ class ServerTest(unittest.TestCase): 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', @@ -663,6 +693,7 @@ class ServerTest(unittest.TestCase): self.assertEqual(b_pod['pod_id'], routes[0]['pod_id']) self.assertEqual(self.project_id, routes[0]['project_id']) + @patch.object(pecan, 'response', new=FakeResponse) @patch.object(FakeClient, 'create_servers') @patch.object(context, 'extract_context_from_environ') def test_post_exception_retry(self, mock_ctx, mock_server): @@ -671,7 +702,7 @@ class ServerTest(unittest.TestCase): top_subnet_id = 'top_subnet_id' top_sg_id = 'top_sg_id' - t_net = {'id': top_net_id} + t_net = {'id': top_net_id, 'name': 'net'} t_subnet = {'id': top_subnet_id, 'network_id': top_net_id, 'ip_version': 4, @@ -750,6 +781,7 @@ class ServerTest(unittest.TestCase): nics=[{'port-id': bottom_port_id}], security_groups=[bottom_sg['id']]) + @patch.object(pecan, 'response', new=FakeResponse) @patch.object(FakeClient, 'create_servers') @patch.object(context, 'extract_context_from_environ') def test_post_across_pods(self, mock_ctx, mock_create): @@ -761,7 +793,7 @@ class ServerTest(unittest.TestCase): top_subnet2_id = 'top_subnet2_id' top_sg_id = 'top_sg_id' - t_net1 = {'id': top_net1_id} + t_net1 = {'id': top_net1_id, 'name': 'net1'} t_subnet1 = {'id': top_subnet1_id, 'tenant_id': self.project_id, 'network_id': top_net1_id, @@ -771,7 +803,7 @@ class ServerTest(unittest.TestCase): 'allocation_pools': {'start': '10.0.1.2', 'end': '10.0.1.254'}, 'enable_dhcp': True} - t_net2 = {'id': top_net2_id} + t_net2 = {'id': top_net2_id, 'name': 'net2'} t_subnet2 = {'id': top_subnet2_id, 'tenant_id': self.project_id, 'network_id': top_net2_id, @@ -1109,6 +1141,72 @@ class ServerTest(unittest.TestCase): calls = [mock.call(400, msg)] mock_abort.assert_has_calls(calls) + @patch.object(pecan, 'response', new=FakeResponse) + @patch.object(context, 'extract_context_from_environ') + def test_get(self, mock_ctx): + 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, 'name': 'net'} + 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_ctx.return_value = self.context + + server_dict = self.controller.post(**body)['server'] + ret_server = self.controller.get_one(server_dict['id'])['server'] + self.assertEqual(server_name, ret_server['name']) + self.assertEqual(image_id, ret_server['image']) + self.assertEqual(flavor_id, ret_server['flavor']) + self.assertEqual(t_net['name'], ret_server['addresses'].keys()[0]) + + ret_server = self.controller.get_one('detail')['servers'][0] + self.assertEqual(server_name, ret_server['name']) + self.assertEqual(image_id, ret_server['image']) + self.assertEqual(flavor_id, ret_server['flavor']) + self.assertEqual(t_net['name'], ret_server['addresses'].keys()[0]) + def tearDown(self): core.ModelBase.metadata.drop_all(core.get_engine()) for res in RES_LIST: