Merge "Implement server delete API"

This commit is contained in:
Jenkins 2016-04-27 11:53:46 +00:00 committed by Gerrit Code Review
commit 353ebcddb1
7 changed files with 161 additions and 6 deletions

View File

@ -470,7 +470,7 @@ class Client(object):
service = self.resource_service_map[resource]
handle = self.service_handle_map[service]
handle.handle_delete(cxt, resource, resource_id)
return handle.handle_delete(cxt, resource, resource_id)
@_safe_operation('get')
def get_resources(self, resource, cxt, resource_id):

View File

@ -70,3 +70,4 @@ TOP = 'top'
# job type
JT_ROUTER = 'router'
JT_PORT_DELETE = 'port_delete'

View File

@ -209,7 +209,7 @@ def _convert_into_with_meta(item, resp):
class NovaResourceHandle(ResourceHandle):
service_type = cons.ST_NOVA
support_resource = {'flavor': LIST,
'server': LIST | CREATE | GET | ACTION,
'server': LIST | CREATE | DELETE | GET | ACTION,
'aggregate': LIST | CREATE | DELETE | ACTION,
'server_volume': ACTION}

View File

@ -83,3 +83,8 @@ class XJobAPI(object):
self.client.prepare(exchange='openstack').cast(
ctxt, 'configure_extra_routes',
payload={constants.JT_ROUTER: router_id})
def delete_server_port(self, ctxt, port_id):
self.client.prepare(exchange='openstack').cast(
ctxt, 'delete_server_port',
payload={constants.JT_PORT_DELETE: port_id})

View File

@ -33,6 +33,7 @@ from tricircle.common.i18n import _LE
import tricircle.common.lock_handle as t_lock
from tricircle.common.quota import QUOTAS
from tricircle.common import utils
from tricircle.common import xrpcapi
import tricircle.db.api as db_api
from tricircle.db import core
from tricircle.db import models
@ -47,9 +48,10 @@ class ServerController(rest.RestController):
def __init__(self, project_id):
self.project_id = project_id
self.clients = {'top': t_client.Client()}
self.clients = {constants.TOP: t_client.Client()}
self.xjob_handler = xrpcapi.XJobAPI()
def _get_client(self, pod_name='top'):
def _get_client(self, pod_name=constants.TOP):
if pod_name not in self.clients:
self.clients[pod_name] = t_client.Client(pod_name)
return self.clients[pod_name]
@ -82,6 +84,7 @@ class ServerController(rest.RestController):
client = self._get_client(pod['pod_name'])
server = client.get_servers(context, bottom_id)
if not server:
self._remove_stale_mapping(context, _id)
pecan.abort(404, 'Server not found')
return
else:
@ -215,6 +218,52 @@ class ServerController(rest.RestController):
'resource_type': constants.RT_SERVER})
return {'server': server}
@expose(generic=True, template='json')
def delete(self, _id):
context = t_context.extract_context_from_environ()
mappings = db_api.get_bottom_mappings_by_top_id(context, _id,
constants.RT_SERVER)
if not mappings:
pecan.response.status = 404
return {'Error': {'message': _('Server not found'), 'code': 404}}
pod, bottom_id = mappings[0]
client = self._get_client(pod['pod_name'])
top_client = self._get_client()
try:
server_ports = top_client.list_ports(
context, filters=[{'key': 'device_id', 'comparator': 'eq',
'value': _id}])
ret = client.delete_servers(context, bottom_id)
# none return value indicates server not found
if ret is None:
self._remove_stale_mapping(context, _id)
pecan.response.status = 404
return {'Error': {'message': _('Server not found'),
'code': 404}}
for server_port in server_ports:
self.xjob_handler.delete_server_port(context,
server_port['id'])
except Exception as e:
code = 500
message = _('Delete server %(server_id)s fails') % {
'server_id': _id}
if hasattr(e, 'code'):
code = e.code
ex_message = str(e)
if ex_message:
message = ex_message
LOG.error(message)
pecan.response.status = code
return {'Error': {'message': message, 'code': code}}
# NOTE(zhiyuan) Security group rules for default security group are
# also kept until subnet is deleted.
pecan.response.status = 204
return pecan.response
def _get_or_create_route(self, context, pod, _id, _type):
def list_resources(t_ctx, q_ctx, pod_, _id_, _type_):
client = self._get_client(pod_['pod_name'])
@ -635,6 +684,17 @@ class ServerController(rest.RestController):
if remove_index >= 0:
del addresses[remove_index]
@staticmethod
def _remove_stale_mapping(context, server_id):
filters = [{'key': 'top_id', 'comparator': 'eq', 'value': server_id},
{'key': 'resource_type',
'comparator': 'eq',
'value': constants.RT_SERVER}]
with context.session.begin():
core.delete_resources(context,
models.ResourceRouting,
filters)
@staticmethod
def _check_network_server_the_same_az(network, server_az):
az_hints = 'availability_zone_hints'

View File

@ -23,10 +23,12 @@ import unittest
import neutronclient.common.exceptions as q_exceptions
from oslo_utils import uuidutils
from tricircle.common import constants
from tricircle.common import context
import tricircle.common.exceptions as t_exceptions
from tricircle.common.i18n import _
from tricircle.common import lock_handle
from tricircle.common import xrpcapi
from tricircle.db import api
from tricircle.db import core
from tricircle.db import models
@ -60,10 +62,15 @@ class FakeException(Exception):
pass
class FakeResponse(object):
pass
class FakeServerController(server.ServerController):
def __init__(self, project_id):
self.clients = {'t_region': FakeClient('t_region')}
self.project_id = project_id
self.xjob_handler = xrpcapi.XJobAPI()
def _get_client(self, pod_name=None):
if not pod_name:
@ -255,6 +262,9 @@ class FakeClient(object):
sg['security_group_rules'].remove(rule)
return
def delete_servers(self, ctx, _id):
pass
class ServerTest(unittest.TestCase):
def setUp(self):
@ -861,6 +871,82 @@ class ServerTest(unittest.TestCase):
ips.append(rule['remote_ip_prefix'])
self.assertEqual(expected_ips, ips)
@patch.object(xrpcapi.XJobAPI, 'delete_server_port')
@patch.object(FakeClient, 'delete_servers')
@patch.object(pecan, 'response', new=FakeResponse)
@patch.object(context, 'extract_context_from_environ')
def test_delete(self, mock_ctx, mock_delete, mock_delete_port):
t_pod, b_pod = self._prepare_pod()
mock_ctx.return_value = self.context
t_server_id = 't_server_id'
b_server_id = 'b_server_id'
with self.context.session.begin():
core.create_resource(
self.context, models.ResourceRouting,
{'top_id': t_server_id, 'bottom_id': b_server_id,
'pod_id': b_pod['pod_id'], 'project_id': self.project_id,
'resource_type': constants.RT_SERVER})
port_id = uuidutils.generate_uuid()
server_port = {
'id': port_id,
'device_id': t_server_id
}
TOP_PORTS.append(server_port)
mock_delete.return_value = ()
res = self.controller.delete(t_server_id)
mock_delete_port.assert_called_once_with(self.context, port_id)
mock_delete.assert_called_once_with(self.context, b_server_id)
self.assertEqual(204, res.status)
@patch.object(FakeClient, 'delete_servers')
@patch.object(pecan, 'response', new=FakeResponse)
@patch.object(context, 'extract_context_from_environ')
def test_delete_error(self, mock_ctx, mock_delete):
t_pod, b_pod = self._prepare_pod()
mock_ctx.return_value = self.context
# pass invalid id
res = self.controller.delete('fake_id')
self.assertEqual('Server not found', res['Error']['message'])
self.assertEqual(404, res['Error']['code'])
t_server_id = 't_server_id'
b_server_id = 'b_server_id'
with self.context.session.begin():
core.create_resource(
self.context, models.ResourceRouting,
{'top_id': t_server_id, 'bottom_id': b_server_id,
'pod_id': b_pod['pod_id'], 'project_id': self.project_id,
'resource_type': constants.RT_SERVER})
mock_delete.return_value = None
# pass stale server id
res = self.controller.delete(t_server_id)
self.assertEqual('Server not found', res['Error']['message'])
self.assertEqual(404, res['Error']['code'])
routes = core.query_resource(
self.context, models.ResourceRouting,
[{'key': 'top_id', 'comparator': 'eq', 'value': t_server_id}], [])
# check the stale mapping is deleted
self.assertEqual(0, len(routes))
with self.context.session.begin():
core.create_resource(
self.context, models.ResourceRouting,
{'top_id': t_server_id, 'bottom_id': b_server_id,
'pod_id': b_pod['pod_id'], 'project_id': self.project_id,
'resource_type': constants.RT_SERVER})
# exception occurs when deleting server
mock_delete.side_effect = t_exceptions.PodNotFound('pod2')
res = self.controller.delete(t_server_id)
self.assertEqual('Pod pod2 could not be found.',
res['Error']['message'])
self.assertEqual(404, res['Error']['code'])
@patch.object(pecan, 'abort')
def test_process_injected_file_quota(self, mock_abort):
ctx = self.context.elevated()

View File

@ -227,8 +227,6 @@ class XManager(PeriodicTasks):
@_job_handle(constants.JT_ROUTER)
def configure_extra_routes(self, ctx, payload):
# TODO(zhiyuan) performance and reliability issue
# better have a job tracking mechanism
t_router_id = payload[constants.JT_ROUTER]
b_pods, b_router_ids = zip(*db_api.get_bottom_mappings_by_top_id(
@ -274,3 +272,8 @@ class XManager(PeriodicTasks):
'destination': cidr})
bottom_client.update_routers(ctx, b_router_id,
{'router': {'routes': extra_routes}})
@_job_handle(constants.JT_PORT_DELETE)
def delete_server_port(self, ctx, payload):
t_port_id = payload[constants.JT_PORT_DELETE]
self._get_client().delete_ports(ctx, t_port_id)