Add filters to server list API
APIImpact DocImpact Closes-Bug: #1697390 Change-Id: I1fd41d0e1897d0187e49cb54c7d4539ce64a70ff
This commit is contained in:
parent
eca9e61187
commit
0563c48554
@ -73,6 +73,42 @@ fields:
|
|||||||
in: query
|
in: query
|
||||||
required: false
|
required: false
|
||||||
type: array
|
type: array
|
||||||
|
fixed_ip_query:
|
||||||
|
description: |
|
||||||
|
Filters the server list result by fixed ip. Users can filter by prefix of ip address.
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
flavor_name_query:
|
||||||
|
description: |
|
||||||
|
Filters the server list by flavor's name.
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
flavor_query:
|
||||||
|
description: |
|
||||||
|
Filters the server list by flavor's UUID.
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
image_query:
|
||||||
|
description: |
|
||||||
|
Filters the server list by image's UUID.
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
server_name_query:
|
||||||
|
description: |
|
||||||
|
Filters the server list by name. Users can filter by prefix of server's name.
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
status_query:
|
||||||
|
description: |
|
||||||
|
Filters the server list by the server's status.
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
user_id:
|
user_id:
|
||||||
description: |
|
description: |
|
||||||
Filters the response by a user, by ID.
|
Filters the response by a user, by ID.
|
||||||
|
@ -143,6 +143,12 @@ Request
|
|||||||
|
|
||||||
.. rest_parameters:: parameters.yaml
|
.. rest_parameters:: parameters.yaml
|
||||||
|
|
||||||
|
- name: server_name_query
|
||||||
|
- status: status_query
|
||||||
|
- flavor_uuid: flavor_query
|
||||||
|
- flavor_name: flavor_name_query
|
||||||
|
- image_uuid: image_query
|
||||||
|
- ip: fixed_ip_query
|
||||||
- all_tenants: all_tenants
|
- all_tenants: all_tenants
|
||||||
- fields: fields
|
- fields: fields
|
||||||
|
|
||||||
@ -169,7 +175,8 @@ List Servers Detailed
|
|||||||
|
|
||||||
.. rest_method:: GET /servers/detail
|
.. rest_method:: GET /servers/detail
|
||||||
|
|
||||||
Return a list of bare metal Servers with complete details.
|
Return a list of bare metal Servers with complete details. We can also apply
|
||||||
|
filters to show more precisely the servers.
|
||||||
|
|
||||||
Normal response codes: 200
|
Normal response codes: 200
|
||||||
|
|
||||||
@ -181,8 +188,15 @@ Request
|
|||||||
|
|
||||||
.. rest_parameters:: parameters.yaml
|
.. rest_parameters:: parameters.yaml
|
||||||
|
|
||||||
|
- name: server_name_query
|
||||||
|
- status: status_query
|
||||||
|
- flavor_uuid: flavor_query
|
||||||
|
- flavor_name: flavor_name_query
|
||||||
|
- image_uuid: image_query
|
||||||
|
- ip: fixed_ip_query
|
||||||
- all_tenants: all_tenants
|
- all_tenants: all_tenants
|
||||||
|
|
||||||
|
|
||||||
Response
|
Response
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
@ -41,6 +41,8 @@ from mogan.common import states
|
|||||||
from mogan import network
|
from mogan import network
|
||||||
from mogan import objects
|
from mogan import objects
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
_DEFAULT_SERVER_RETURN_FIELDS = ('uuid', 'name', 'description',
|
_DEFAULT_SERVER_RETURN_FIELDS = ('uuid', 'name', 'description',
|
||||||
'status', 'power_state')
|
'status', 'power_state')
|
||||||
|
|
||||||
@ -591,21 +593,62 @@ class ServerController(ServerControllerBase):
|
|||||||
'detail': ['GET']
|
'detail': ['GET']
|
||||||
}
|
}
|
||||||
|
|
||||||
def _get_server_collection(self, fields=None, all_tenants=False):
|
def _get_server_collection(self, name=None, status=None,
|
||||||
|
flavor_uuid=None, flavor_name=None,
|
||||||
|
image_uuid=None, ip=None,
|
||||||
|
all_tenants=None, fields=None):
|
||||||
context = pecan.request.context
|
context = pecan.request.context
|
||||||
project_only = True
|
project_only = True
|
||||||
if context.is_admin and all_tenants:
|
if context.is_admin and all_tenants:
|
||||||
project_only = False
|
project_only = False
|
||||||
|
|
||||||
|
filters = {}
|
||||||
|
if name:
|
||||||
|
filters['name'] = name
|
||||||
|
if status:
|
||||||
|
filters['status'] = status
|
||||||
|
if flavor_uuid:
|
||||||
|
filters['flavor_uuid'] = flavor_uuid
|
||||||
|
if flavor_name:
|
||||||
|
filters['flavor_name'] = flavor_name
|
||||||
|
if image_uuid:
|
||||||
|
filters['image_uuid'] = image_uuid
|
||||||
|
|
||||||
servers = objects.Server.list(pecan.request.context,
|
servers = objects.Server.list(pecan.request.context,
|
||||||
project_only=project_only)
|
project_only=project_only,
|
||||||
|
filters=filters)
|
||||||
|
if ip:
|
||||||
|
servers = self._ip_filter(servers, ip)
|
||||||
|
|
||||||
servers_data = [server.as_dict() for server in servers]
|
servers_data = [server.as_dict() for server in servers]
|
||||||
|
|
||||||
return ServerCollection.convert_with_links(servers_data,
|
return ServerCollection.convert_with_links(servers_data,
|
||||||
fields=fields)
|
fields=fields)
|
||||||
|
|
||||||
@expose.expose(ServerCollection, types.listtype, types.boolean)
|
@staticmethod
|
||||||
def get_all(self, fields=None, all_tenants=None):
|
def _ip_filter(servers, ip):
|
||||||
|
ip = ip.replace('.', '\.')
|
||||||
|
ip_obj = re.compile(ip)
|
||||||
|
|
||||||
|
def _match_server(server):
|
||||||
|
nw_info = server.nics
|
||||||
|
for vif in nw_info:
|
||||||
|
for fixed_ip in vif.fixed_ips:
|
||||||
|
address = fixed_ip.get('ip_address')
|
||||||
|
if not address:
|
||||||
|
continue
|
||||||
|
if ip_obj.match(address):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
return filter(_match_server, servers)
|
||||||
|
|
||||||
|
@expose.expose(ServerCollection, wtypes.text, wtypes.text,
|
||||||
|
types.uuid, wtypes.text, types.uuid, wtypes.text,
|
||||||
|
types.listtype, types.boolean)
|
||||||
|
def get_all(self, name=None, status=None,
|
||||||
|
flavor_uuid=None, flavor_name=None, image_uuid=None, ip=None,
|
||||||
|
fields=None, all_tenants=None):
|
||||||
"""Retrieve a list of server.
|
"""Retrieve a list of server.
|
||||||
|
|
||||||
:param fields: Optional, a list with a specified set of fields
|
:param fields: Optional, a list with a specified set of fields
|
||||||
@ -617,8 +660,11 @@ class ServerController(ServerControllerBase):
|
|||||||
"""
|
"""
|
||||||
if fields is None:
|
if fields is None:
|
||||||
fields = _DEFAULT_SERVER_RETURN_FIELDS
|
fields = _DEFAULT_SERVER_RETURN_FIELDS
|
||||||
return self._get_server_collection(fields=fields,
|
return self._get_server_collection(name, status,
|
||||||
all_tenants=all_tenants)
|
flavor_uuid, flavor_name,
|
||||||
|
image_uuid, ip,
|
||||||
|
all_tenants=all_tenants,
|
||||||
|
fields=fields)
|
||||||
|
|
||||||
@policy.authorize_wsgi("mogan:server", "get")
|
@policy.authorize_wsgi("mogan:server", "get")
|
||||||
@expose.expose(Server, types.uuid, types.listtype)
|
@expose.expose(Server, types.uuid, types.listtype)
|
||||||
@ -634,14 +680,23 @@ class ServerController(ServerControllerBase):
|
|||||||
|
|
||||||
return Server.convert_with_links(server_data, fields=fields)
|
return Server.convert_with_links(server_data, fields=fields)
|
||||||
|
|
||||||
@expose.expose(ServerCollection, types.boolean)
|
@expose.expose(ServerCollection, wtypes.text, wtypes.text,
|
||||||
def detail(self, all_tenants=None):
|
types.uuid, wtypes.text, types.uuid, wtypes.text,
|
||||||
|
types.boolean)
|
||||||
|
def detail(self, name=None, status=None,
|
||||||
|
flavor_uuid=None, flavor_name=None, image_uuid=None, ip=None,
|
||||||
|
all_tenants=None):
|
||||||
"""Retrieve detail of a list of servers."""
|
"""Retrieve detail of a list of servers."""
|
||||||
# /detail should only work against collections
|
# /detail should only work against collections
|
||||||
|
cdict = pecan.request.context.to_policy_values()
|
||||||
|
policy.authorize('mogan:server:get', cdict, cdict)
|
||||||
parent = pecan.request.path.split('/')[:-1][-1]
|
parent = pecan.request.path.split('/')[:-1][-1]
|
||||||
if parent != "servers":
|
if parent != "servers":
|
||||||
raise exception.NotFound()
|
raise exception.NotFound()
|
||||||
return self._get_server_collection(all_tenants=all_tenants)
|
return self._get_server_collection(name, status,
|
||||||
|
flavor_uuid, flavor_name,
|
||||||
|
image_uuid, ip,
|
||||||
|
all_tenants=all_tenants)
|
||||||
|
|
||||||
@policy.authorize_wsgi("mogan:server", "create", False)
|
@policy.authorize_wsgi("mogan:server", "create", False)
|
||||||
@expose.expose(Server, body=types.jsontype,
|
@expose.expose(Server, body=types.jsontype,
|
||||||
|
@ -68,7 +68,6 @@ def model_query(context, model, *args, **kwargs):
|
|||||||
project_id.
|
project_id.
|
||||||
:type project_only: bool
|
:type project_only: bool
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if kwargs.pop("project_only", False):
|
if kwargs.pop("project_only", False):
|
||||||
kwargs["project_id"] = context.tenant
|
kwargs["project_id"] = context.tenant
|
||||||
|
|
||||||
@ -103,6 +102,25 @@ class Connection(api.Connection):
|
|||||||
self.QUOTA_SYNC_FUNCTIONS = {'_sync_servers': self._sync_servers}
|
self.QUOTA_SYNC_FUNCTIONS = {'_sync_servers': self._sync_servers}
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def _add_servers_filters(self, context, query, filters):
|
||||||
|
if filters is None:
|
||||||
|
filters = []
|
||||||
|
if 'name' in filters:
|
||||||
|
name_start_with = filters['name']
|
||||||
|
query = query.filter(models.Server.name.like(
|
||||||
|
"%" + name_start_with + "%"))
|
||||||
|
if 'status' in filters:
|
||||||
|
query = query.filter_by(status=filters['status'])
|
||||||
|
if 'flavor_uuid' in filters or 'flavor_name' in filters:
|
||||||
|
if 'flavor_name' in filters:
|
||||||
|
flavor_query = model_query(context, models.Flavors).filter_by(
|
||||||
|
name=filters['flavor_name'])
|
||||||
|
filters['flavor_uuid'] = flavor_query.first().uuid
|
||||||
|
query = query.filter_by(flavor_uuid=filters['flavor_uuid'])
|
||||||
|
if 'image_uuid' in filters:
|
||||||
|
query = query.filter_by(image_uuid=filters['image_uuid'])
|
||||||
|
return query
|
||||||
|
|
||||||
def flavor_create(self, context, values):
|
def flavor_create(self, context, values):
|
||||||
if not values.get('uuid'):
|
if not values.get('uuid'):
|
||||||
values['uuid'] = uuidutils.generate_uuid()
|
values['uuid'] = uuidutils.generate_uuid()
|
||||||
@ -213,9 +231,11 @@ class Connection(api.Connection):
|
|||||||
except NoResultFound:
|
except NoResultFound:
|
||||||
raise exception.ServerNotFound(server=server_id)
|
raise exception.ServerNotFound(server=server_id)
|
||||||
|
|
||||||
def server_get_all(self, context, project_only):
|
def server_get_all(self, context, project_only, filters=None):
|
||||||
return model_query(context, models.Server,
|
query = model_query(context, models.Server,
|
||||||
project_only=project_only)
|
project_only=project_only)
|
||||||
|
query = self._add_servers_filters(context, query, filters)
|
||||||
|
return query.all()
|
||||||
|
|
||||||
def server_destroy(self, context, server_id):
|
def server_destroy(self, context, server_id):
|
||||||
with _session_for_write():
|
with _session_for_write():
|
||||||
|
@ -124,10 +124,11 @@ class Server(base.MoganObject, object_base.VersionedObjectDictCompat):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def list(cls, context, project_only=False):
|
def list(cls, context, project_only=False, filters=None):
|
||||||
"""Return a list of Server objects."""
|
"""Return a list of Server objects."""
|
||||||
db_servers = cls.dbapi.server_get_all(context,
|
db_servers = cls.dbapi.server_get_all(context,
|
||||||
project_only=project_only)
|
project_only=project_only,
|
||||||
|
filters=filters)
|
||||||
return Server._from_db_object_list(db_servers, cls, context)
|
return Server._from_db_object_list(db_servers, cls, context)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -208,6 +208,32 @@ class TestServers(v1_test.APITestV1):
|
|||||||
self.assertEqual('b8f82429-3a13-4ffe-9398-4d1abdc256a8',
|
self.assertEqual('b8f82429-3a13-4ffe-9398-4d1abdc256a8',
|
||||||
resps[0]['image_uuid'])
|
resps[0]['image_uuid'])
|
||||||
|
|
||||||
|
# Test with filters
|
||||||
|
# Filter with server name
|
||||||
|
resps = self.get_json('/servers/detail?name=test_server_0',
|
||||||
|
headers=headers)['servers']
|
||||||
|
self.assertEqual(1, len(resps))
|
||||||
|
resps = self.get_json('/servers/detail?name=test',
|
||||||
|
headers=headers)['servers']
|
||||||
|
self.assertEqual(4, len(resps))
|
||||||
|
# Filter with server status
|
||||||
|
resps = self.get_json('/servers/detail?status=building',
|
||||||
|
headers=headers)['servers']
|
||||||
|
self.assertEqual(4, len(resps))
|
||||||
|
resps = self.get_json('/servers/detail?status=active',
|
||||||
|
headers=headers)['servers']
|
||||||
|
self.assertEqual(0, len(resps))
|
||||||
|
# Filter with flavor uuid
|
||||||
|
search_opt = "flavor_uuid=ff28b5a2-73e5-431c-b4b7-1b96b74bca7b"
|
||||||
|
resps = self.get_json('/servers/detail?' + search_opt,
|
||||||
|
headers=headers)['servers']
|
||||||
|
self.assertEqual(4, len(resps))
|
||||||
|
# Filter with image uuid
|
||||||
|
search_opt = "image_uuid=b8f82429-3a13-4ffe-9398-4d1abdc256a8"
|
||||||
|
resps = self.get_json('/servers/detail?' + search_opt,
|
||||||
|
headers=headers)['servers']
|
||||||
|
self.assertEqual(4, len(resps))
|
||||||
|
|
||||||
def test_server_delete(self):
|
def test_server_delete(self):
|
||||||
self._prepare_server(4)
|
self._prepare_server(4)
|
||||||
headers = self.gen_headers(self.context)
|
headers = self.gen_headers(self.context)
|
||||||
|
@ -47,35 +47,66 @@ class DbServerTestCase(base.DbTestCase):
|
|||||||
'12345678-9999-0000-aaaa-123456789012')
|
'12345678-9999-0000-aaaa-123456789012')
|
||||||
|
|
||||||
def test_server_get_all(self):
|
def test_server_get_all(self):
|
||||||
uuids_project_1 = []
|
servers_project_1 = []
|
||||||
uuids_project_2 = []
|
servers_project_2 = []
|
||||||
uuids_project_all = []
|
servers_project_all = []
|
||||||
for i in range(0, 3):
|
for i in range(0, 3):
|
||||||
server = utils.create_test_server(
|
server = utils.create_test_server(
|
||||||
uuid=uuidutils.generate_uuid(),
|
uuid=uuidutils.generate_uuid(),
|
||||||
project_id='project_1',
|
project_id='project_1',
|
||||||
name=str(i))
|
name=str(i))
|
||||||
uuids_project_1.append(six.text_type(server['uuid']))
|
servers_project_1.append(server)
|
||||||
for i in range(3, 5):
|
for i in range(3, 5):
|
||||||
server = utils.create_test_server(
|
server = utils.create_test_server(
|
||||||
uuid=uuidutils.generate_uuid(),
|
uuid=uuidutils.generate_uuid(),
|
||||||
project_id='project_2',
|
project_id='project_2',
|
||||||
name=str(i))
|
name=str(i))
|
||||||
uuids_project_2.append(six.text_type(server['uuid']))
|
servers_project_2.append(server)
|
||||||
uuids_project_all.extend(uuids_project_1)
|
servers_project_all.extend(servers_project_1)
|
||||||
uuids_project_all.extend(uuids_project_2)
|
servers_project_all.extend(servers_project_2)
|
||||||
|
|
||||||
# Set project_only to False
|
# Set project_only to False
|
||||||
# get all servers from all projects
|
# get all servers from all projects
|
||||||
res = self.dbapi.server_get_all(self.context, project_only=False)
|
res = self.dbapi.server_get_all(self.context, project_only=False)
|
||||||
res_uuids = [r.uuid for r in res]
|
for i, item in enumerate(res):
|
||||||
six.assertCountEqual(self, uuids_project_all, res_uuids)
|
self.assertEqual(servers_project_all[i].uuid, item.uuid)
|
||||||
|
|
||||||
|
# Filter by server name test
|
||||||
|
res = self.dbapi.server_get_all(self.context, project_only=False,
|
||||||
|
filters={"name": "1"})
|
||||||
|
self.assertEqual(len(res), 1)
|
||||||
|
self.assertEqual(res[0].name, "1")
|
||||||
|
|
||||||
|
# Filter by server status test
|
||||||
|
res = self.dbapi.server_get_all(self.context, project_only=False,
|
||||||
|
filters={"status": "active"})
|
||||||
|
for i, item in enumerate(res):
|
||||||
|
self.assertEqual(item.status, "active")
|
||||||
|
|
||||||
|
# Filter by flavor_uuid test
|
||||||
|
res = self.dbapi.server_get_all(self.context, project_only=False,
|
||||||
|
filters={"flavor_uuid":
|
||||||
|
servers_project_all[0].
|
||||||
|
flavor_uuid})
|
||||||
|
for i, item in enumerate(res):
|
||||||
|
self.assertEqual(item.flavor_uuid,
|
||||||
|
servers_project_all[0].flavor_uuid)
|
||||||
|
|
||||||
|
# Filter by image_uuid test
|
||||||
|
res = self.dbapi.server_get_all(self.context, project_only=False,
|
||||||
|
filters={"image_uuid":
|
||||||
|
servers_project_all[0].
|
||||||
|
image_uuid})
|
||||||
|
for i, item in enumerate(res):
|
||||||
|
self.assertEqual(item.image_uuid,
|
||||||
|
servers_project_all[0].image_uuid)
|
||||||
|
|
||||||
# Set project_only to True
|
# Set project_only to True
|
||||||
# get servers from current project (project_1)
|
# get servers from current project (project_1)
|
||||||
self.context.tenant = 'project_1'
|
self.context.tenant = 'project_1'
|
||||||
res = self.dbapi.server_get_all(self.context, project_only=True)
|
res = self.dbapi.server_get_all(self.context, project_only=True)
|
||||||
res_uuids = [r.uuid for r in res]
|
res_uuids = [r.uuid for r in res]
|
||||||
|
uuids_project_1 = [r.uuid for r in servers_project_1]
|
||||||
six.assertCountEqual(self, uuids_project_1, res_uuids)
|
six.assertCountEqual(self, uuids_project_1, res_uuids)
|
||||||
|
|
||||||
# Set project_only to True
|
# Set project_only to True
|
||||||
@ -83,6 +114,7 @@ class DbServerTestCase(base.DbTestCase):
|
|||||||
self.context.tenant = 'project_2'
|
self.context.tenant = 'project_2'
|
||||||
res = self.dbapi.server_get_all(self.context, project_only=True)
|
res = self.dbapi.server_get_all(self.context, project_only=True)
|
||||||
res_uuids = [r.uuid for r in res]
|
res_uuids = [r.uuid for r in res]
|
||||||
|
uuids_project_2 = [r.uuid for r in servers_project_2]
|
||||||
six.assertCountEqual(self, uuids_project_2, res_uuids)
|
six.assertCountEqual(self, uuids_project_2, res_uuids)
|
||||||
|
|
||||||
def test_server_destroy(self):
|
def test_server_destroy(self):
|
||||||
|
@ -45,12 +45,11 @@ class TestServerObject(base.DbTestCase):
|
|||||||
with mock.patch.object(self.dbapi, 'server_get_all',
|
with mock.patch.object(self.dbapi, 'server_get_all',
|
||||||
autospec=True) as mock_server_get_all:
|
autospec=True) as mock_server_get_all:
|
||||||
mock_server_get_all.return_value = [self.fake_server]
|
mock_server_get_all.return_value = [self.fake_server]
|
||||||
|
|
||||||
project_only = False
|
project_only = False
|
||||||
servers = objects.Server.list(self.context, project_only)
|
filters = None
|
||||||
|
servers = objects.Server.list(self.context, project_only, filters)
|
||||||
mock_server_get_all.assert_called_once_with(
|
mock_server_get_all.assert_called_once_with(
|
||||||
self.context, project_only)
|
self.context, project_only, filters)
|
||||||
self.assertIsInstance(servers[0], objects.Server)
|
self.assertIsInstance(servers[0], objects.Server)
|
||||||
self.assertEqual(self.context, servers[0]._context)
|
self.assertEqual(self.context, servers[0]._context)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user