Implement server delete API
Security group rule are kept after server is deleted and is deleted when subnet is deleted. Change-Id: I4a7ef7f6df0402bd841d36d1d3dd38118f46fe3a
This commit is contained in:
parent
fbaaf20324
commit
9e95bfd9bc
@ -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):
|
||||
|
@ -70,3 +70,4 @@ TOP = 'top'
|
||||
|
||||
# job type
|
||||
JT_ROUTER = 'router'
|
||||
JT_PORT_DELETE = 'port_delete'
|
||||
|
@ -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}
|
||||
|
||||
|
@ -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})
|
||||
|
@ -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'
|
||||
|
@ -381,7 +381,12 @@ class FakeQuery(object):
|
||||
keys = []
|
||||
values = []
|
||||
for e in criteria:
|
||||
if not isinstance(e.right, elements.Null):
|
||||
if not hasattr(e, 'right') and isinstance(e, elements.False_):
|
||||
# filter is a single False value, set key to a 'INVALID_FIELD'
|
||||
# then no records will be returned
|
||||
keys.append('INVALID_FIELD')
|
||||
values.append(False)
|
||||
elif not isinstance(e.right, elements.Null):
|
||||
_filter.append(e)
|
||||
else:
|
||||
if e.left.name == 'network_id' and (
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user