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
|
||||
required: false
|
||||
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:
|
||||
description: |
|
||||
Filters the response by a user, by ID.
|
||||
|
@ -143,6 +143,12 @@ Request
|
||||
|
||||
.. 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
|
||||
- fields: fields
|
||||
|
||||
@ -169,7 +175,8 @@ List Servers Detailed
|
||||
|
||||
.. 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
|
||||
|
||||
@ -181,8 +188,15 @@ Request
|
||||
|
||||
.. 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
|
||||
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
|
@ -41,6 +41,8 @@ from mogan.common import states
|
||||
from mogan import network
|
||||
from mogan import objects
|
||||
|
||||
import re
|
||||
|
||||
_DEFAULT_SERVER_RETURN_FIELDS = ('uuid', 'name', 'description',
|
||||
'status', 'power_state')
|
||||
|
||||
@ -591,21 +593,62 @@ class ServerController(ServerControllerBase):
|
||||
'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
|
||||
project_only = True
|
||||
if context.is_admin and all_tenants:
|
||||
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,
|
||||
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]
|
||||
|
||||
return ServerCollection.convert_with_links(servers_data,
|
||||
fields=fields)
|
||||
|
||||
@expose.expose(ServerCollection, types.listtype, types.boolean)
|
||||
def get_all(self, fields=None, all_tenants=None):
|
||||
@staticmethod
|
||||
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.
|
||||
|
||||
:param fields: Optional, a list with a specified set of fields
|
||||
@ -617,8 +660,11 @@ class ServerController(ServerControllerBase):
|
||||
"""
|
||||
if fields is None:
|
||||
fields = _DEFAULT_SERVER_RETURN_FIELDS
|
||||
return self._get_server_collection(fields=fields,
|
||||
all_tenants=all_tenants)
|
||||
return self._get_server_collection(name, status,
|
||||
flavor_uuid, flavor_name,
|
||||
image_uuid, ip,
|
||||
all_tenants=all_tenants,
|
||||
fields=fields)
|
||||
|
||||
@policy.authorize_wsgi("mogan:server", "get")
|
||||
@expose.expose(Server, types.uuid, types.listtype)
|
||||
@ -634,14 +680,23 @@ class ServerController(ServerControllerBase):
|
||||
|
||||
return Server.convert_with_links(server_data, fields=fields)
|
||||
|
||||
@expose.expose(ServerCollection, types.boolean)
|
||||
def detail(self, all_tenants=None):
|
||||
@expose.expose(ServerCollection, wtypes.text, wtypes.text,
|
||||
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."""
|
||||
# /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]
|
||||
if parent != "servers":
|
||||
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)
|
||||
@expose.expose(Server, body=types.jsontype,
|
||||
|
@ -68,7 +68,6 @@ def model_query(context, model, *args, **kwargs):
|
||||
project_id.
|
||||
:type project_only: bool
|
||||
"""
|
||||
|
||||
if kwargs.pop("project_only", False):
|
||||
kwargs["project_id"] = context.tenant
|
||||
|
||||
@ -103,6 +102,25 @@ class Connection(api.Connection):
|
||||
self.QUOTA_SYNC_FUNCTIONS = {'_sync_servers': self._sync_servers}
|
||||
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):
|
||||
if not values.get('uuid'):
|
||||
values['uuid'] = uuidutils.generate_uuid()
|
||||
@ -213,9 +231,11 @@ class Connection(api.Connection):
|
||||
except NoResultFound:
|
||||
raise exception.ServerNotFound(server=server_id)
|
||||
|
||||
def server_get_all(self, context, project_only):
|
||||
return model_query(context, models.Server,
|
||||
project_only=project_only)
|
||||
def server_get_all(self, context, project_only, filters=None):
|
||||
query = model_query(context, models.Server,
|
||||
project_only=project_only)
|
||||
query = self._add_servers_filters(context, query, filters)
|
||||
return query.all()
|
||||
|
||||
def server_destroy(self, context, server_id):
|
||||
with _session_for_write():
|
||||
|
@ -124,10 +124,11 @@ class Server(base.MoganObject, object_base.VersionedObjectDictCompat):
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def list(cls, context, project_only=False):
|
||||
def list(cls, context, project_only=False, filters=None):
|
||||
"""Return a list of Server objects."""
|
||||
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)
|
||||
|
||||
@classmethod
|
||||
|
@ -208,6 +208,32 @@ class TestServers(v1_test.APITestV1):
|
||||
self.assertEqual('b8f82429-3a13-4ffe-9398-4d1abdc256a8',
|
||||
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):
|
||||
self._prepare_server(4)
|
||||
headers = self.gen_headers(self.context)
|
||||
|
@ -47,35 +47,66 @@ class DbServerTestCase(base.DbTestCase):
|
||||
'12345678-9999-0000-aaaa-123456789012')
|
||||
|
||||
def test_server_get_all(self):
|
||||
uuids_project_1 = []
|
||||
uuids_project_2 = []
|
||||
uuids_project_all = []
|
||||
servers_project_1 = []
|
||||
servers_project_2 = []
|
||||
servers_project_all = []
|
||||
for i in range(0, 3):
|
||||
server = utils.create_test_server(
|
||||
uuid=uuidutils.generate_uuid(),
|
||||
project_id='project_1',
|
||||
name=str(i))
|
||||
uuids_project_1.append(six.text_type(server['uuid']))
|
||||
servers_project_1.append(server)
|
||||
for i in range(3, 5):
|
||||
server = utils.create_test_server(
|
||||
uuid=uuidutils.generate_uuid(),
|
||||
project_id='project_2',
|
||||
name=str(i))
|
||||
uuids_project_2.append(six.text_type(server['uuid']))
|
||||
uuids_project_all.extend(uuids_project_1)
|
||||
uuids_project_all.extend(uuids_project_2)
|
||||
servers_project_2.append(server)
|
||||
servers_project_all.extend(servers_project_1)
|
||||
servers_project_all.extend(servers_project_2)
|
||||
|
||||
# Set project_only to False
|
||||
# get all servers from all projects
|
||||
res = self.dbapi.server_get_all(self.context, project_only=False)
|
||||
res_uuids = [r.uuid for r in res]
|
||||
six.assertCountEqual(self, uuids_project_all, res_uuids)
|
||||
for i, item in enumerate(res):
|
||||
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
|
||||
# get servers from current project (project_1)
|
||||
self.context.tenant = 'project_1'
|
||||
res = self.dbapi.server_get_all(self.context, project_only=True)
|
||||
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)
|
||||
|
||||
# Set project_only to True
|
||||
@ -83,6 +114,7 @@ class DbServerTestCase(base.DbTestCase):
|
||||
self.context.tenant = 'project_2'
|
||||
res = self.dbapi.server_get_all(self.context, project_only=True)
|
||||
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)
|
||||
|
||||
def test_server_destroy(self):
|
||||
|
@ -45,12 +45,11 @@ class TestServerObject(base.DbTestCase):
|
||||
with mock.patch.object(self.dbapi, 'server_get_all',
|
||||
autospec=True) as mock_server_get_all:
|
||||
mock_server_get_all.return_value = [self.fake_server]
|
||||
|
||||
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(
|
||||
self.context, project_only)
|
||||
self.context, project_only, filters)
|
||||
self.assertIsInstance(servers[0], objects.Server)
|
||||
self.assertEqual(self.context, servers[0]._context)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user