Add host list api

Add host list api

Change-Id: I0511f3d0608043a709249052aa29a76ba9ded166
Partially-Implements: blueprint show-container-engine-info
This commit is contained in:
ShunliZhou 2017-07-18 15:39:30 +08:00
parent 5a49fefb15
commit 1b5bb285e5
11 changed files with 315 additions and 5 deletions

45
api-ref/source/hosts.inc Normal file
View File

@ -0,0 +1,45 @@
.. -*- rst -*-
==================
Manage zun host
==================
List all compute hosts
========================================
.. rest_method:: GET /v1/hosts
Enables administrative users to list all Zun container hosts.
Response Codes
--------------
.. rest_status_code:: success status.yaml
- 200
.. rest_status_code:: error status.yaml
- 400
- 401
- 403
Response Parameters
-------------------
.. rest_parameters:: parameters.yaml
- X-Openstack-Request-Id: request_id
- hosts: host_list
- uuid: uuid
- hostname: hostname
- mem_total: mem_total
- cpus: cpus
- os: os
- labels: labels
Response Example
----------------
.. literalinclude:: samples/host-get-all-resp.json
:language: javascript

View File

@ -10,3 +10,4 @@
.. include:: containers.inc
.. include:: images.inc
.. include:: services.inc
.. include:: hosts.inc

View File

@ -0,0 +1,23 @@
{
"hosts": [
{
"hostname": "testhost",
"uuid": "d0405f06-101e-4340-8735-d1bf9fa8b8ad",
"links": [{
"href": "http://192.168.2.200:9517/v1/hosts/d0405f06-101e-4340-8735-d1bf9fa8b8ad",
"rel": "self"
},
{"href": "http://192.168.2.200:9517/hosts/d0405f06-101e-4340-8735-d1bf9fa8b8ad",
"rel": "bookmark"
}
],
"labels": {
"type": "test"
},
"cpus": 48,
"mem_total": 128446,
"os": "CentOS Linux 7 (Core)"
}
],
"next": null
}

View File

@ -38,5 +38,7 @@
"zun-service:disable": "rule:admin_api",
"zun-service:enable": "rule:admin_api",
"zun-service:force_down": "rule:admin_api",
"zun-service:get_all": "rule:admin_api"
"zun-service:get_all": "rule:admin_api",
"host:get_all": "rule:admin_api"
}

View File

@ -24,6 +24,7 @@ import pecan
from zun.api.controllers import base as controllers_base
from zun.api.controllers import link
from zun.api.controllers.v1 import containers as container_controller
from zun.api.controllers.v1 import hosts as host_controller
from zun.api.controllers.v1 import images as image_controller
from zun.api.controllers.v1 import zun_services
from zun.api.controllers import versions as ver
@ -63,7 +64,8 @@ class V1(controllers_base.APIBase):
'links',
'services',
'containers',
'images'
'images',
'hosts'
)
@staticmethod
@ -97,6 +99,12 @@ class V1(controllers_base.APIBase):
pecan.request.host_url,
'images', '',
bookmark=True)]
v1.hosts = [link.make_link('self', pecan.request.host_url,
'hosts', ''),
link.make_link('bookmark',
pecan.request.host_url,
'hosts', '',
bookmark=True)]
return v1
@ -106,6 +114,7 @@ class Controller(controllers_base.Controller):
services = zun_services.ZunServiceController()
containers = container_controller.ContainersController()
images = image_controller.ImagesController()
hosts = host_controller.HostController()
@pecan.expose('json')
def get(self):

View File

@ -0,0 +1,85 @@
# 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.
from oslo_log import log as logging
import pecan
from zun.api.controllers import base
from zun.api.controllers.v1 import collection
from zun.api.controllers.v1.views import hosts_view as view
from zun.api import utils as api_utils
from zun.common import exception
from zun.common import policy
from zun import objects
LOG = logging.getLogger(__name__)
class HostCollection(collection.Collection):
"""API representation of a collection of hosts."""
fields = {
'hosts',
'next'
}
"""A list containing compute node objects"""
def __init__(self, **kwargs):
super(HostCollection, self).__init__(**kwargs)
self._type = 'hosts'
@staticmethod
def convert_with_links(nodes, limit, url=None,
expand=False, **kwargs):
collection = HostCollection()
collection.hosts = [view.format_host(url, p) for p in nodes]
collection.next = collection.get_next(limit, url=url, **kwargs)
return collection
class HostController(base.Controller):
"""Host info controller"""
@pecan.expose('json')
@base.Controller.api_version("1.3")
@exception.wrap_pecan_controller_exception
def get_all(self, **kwargs):
"""Retrieve a list of hosts"""
context = pecan.request.context
policy.enforce(context, "host:get_all",
action="host:get_all")
return self._get_host_collection(**kwargs)
def _get_host_collection(self, **kwargs):
context = pecan.request.context
limit = api_utils.validate_limit(kwargs.get('limit'))
sort_dir = api_utils.validate_sort_dir(kwargs.get('sort_dir', 'asc'))
sort_key = kwargs.get('sort_key', 'hostname')
expand = kwargs.get('expand')
filters = None
marker_obj = None
resource_url = kwargs.get('resource_url')
marker = kwargs.get('marker')
if marker:
marker_obj = objects.ComputeNode.get_by_uuid(context, marker)
nodes = objects.ComputeNode.list(context,
limit,
marker_obj,
sort_key,
sort_dir,
filters=filters)
return HostCollection.convert_with_links(nodes, limit,
url=resource_url,
expand=expand,
sort_key=sort_key,
sort_dir=sort_dir)

View File

@ -0,0 +1,45 @@
#
# 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 itertools
from zun.api.controllers import link
_basic_keys = (
'uuid',
'hostname',
'mem_total',
'cpus',
'os',
'labels'
)
def format_host(url, host):
def transform(key, value):
if key not in _basic_keys:
return
if key == 'uuid':
yield ('uuid', value)
yield ('links', [link.make_link(
'self', url, 'hosts', value),
link.make_link(
'bookmark', url,
'hosts', value,
bookmark=True)])
else:
yield (key, value)
return dict(itertools.chain.from_iterable(
transform(k, v) for k, v in host.as_dict().items()))

View File

@ -36,10 +36,11 @@ REST_API_VERSION_HISTORY = """REST API Version History:
* 1.1 - Initial version
* 1.2 - Support user specify pre created networks
* 1.3 - Add auto_remove to container
* 1.4 - Support list all container host
"""
BASE_VER = '1.1'
CURRENT_MAX_VER = '1.3'
CURRENT_MAX_VER = '1.4'
class Version(object):

View File

@ -42,3 +42,9 @@ user documentation.
Add 'auto_remove' field for creating a container.
With this field, the container will be automatically removed if it exists.
The new one will be created instead.
1.4
---
Add host list api.
Users can use this api to list all the zun compute hosts.

View File

@ -25,7 +25,7 @@ class TestRootController(api_base.FunctionalTest):
u'default_version':
{u'id': u'v1',
u'links': [{u'href': u'http://localhost/v1/', u'rel': u'self'}],
u'max_version': u'1.3',
u'max_version': u'1.4',
u'min_version': u'1.1',
u'status': u'CURRENT'},
u'description': u'Zun is an OpenStack project which '
@ -33,7 +33,7 @@ class TestRootController(api_base.FunctionalTest):
u'versions': [{u'id': u'v1',
u'links': [{u'href': u'http://localhost/v1/',
u'rel': u'self'}],
u'max_version': u'1.3',
u'max_version': u'1.4',
u'min_version': u'1.1',
u'status': u'CURRENT'}]}
@ -56,6 +56,10 @@ class TestRootController(api_base.FunctionalTest):
u'rel': u'self'},
{u'href': u'http://localhost/containers/',
u'rel': u'bookmark'}],
u'hosts': [{u'href': u'http://localhost/v1/hosts/',
u'rel': u'self'},
{u'href': u'http://localhost/hosts/',
u'rel': u'bookmark'}],
u'images': [{u'href': u'http://localhost/v1/images/',
u'rel': u'self'},
{u'href': u'http://localhost/images/',

View File

@ -0,0 +1,89 @@
# 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 mock
from mock import patch
from oslo_utils import uuidutils
from zun import objects
from zun.objects import numa
from zun.tests.unit.api import base as api_base
from zun.tests.unit.db import utils
class TestHostController(api_base.FunctionalTest):
@patch('zun.objects.ComputeNode.list')
def test_get_all_hosts(self, mock_host_list):
test_host = utils.get_test_compute_node()
numat = numa.NUMATopology._from_dict(test_host['numa_topology'])
test_host['numa_topology'] = numat
hosts = [objects.ComputeNode(self.context, **test_host)]
mock_host_list.return_value = hosts
extra_environ = {'HTTP_ACCEPT': 'application/json'}
headers = {'OpenStack-API-Version': 'container 1.3'}
response = self.app.get('/v1/hosts/', extra_environ=extra_environ,
headers=headers)
mock_host_list.assert_called_once_with(mock.ANY,
1000, None, 'hostname', 'asc',
filters=None)
self.assertEqual(200, response.status_int)
actual_hosts = response.json['hosts']
self.assertEqual(1, len(actual_hosts))
self.assertEqual(test_host['uuid'],
actual_hosts[0].get('uuid'))
@patch('zun.objects.ComputeNode.list')
def test_get_all_hosts_with_pagination_marker(self, mock_host_list):
host_list = []
for id_ in range(4):
test_host = utils.create_test_compute_node(
context=self.context,
uuid=uuidutils.generate_uuid())
numat = numa.NUMATopology._from_dict(test_host['numa_topology'])
test_host['numa_topology'] = numat
host = objects.ComputeNode(self.context, **test_host)
host_list.append(host)
mock_host_list.return_value = host_list[-1:]
extra_environ = {'HTTP_ACCEPT': 'application/json'}
headers = {'OpenStack-API-Version': 'container 1.3'}
response = self.app.get('/v1/hosts/?limit=3&marker=%s'
% host_list[2].uuid,
extra_environ=extra_environ, headers=headers)
self.assertEqual(200, response.status_int)
actual_hosts = response.json['hosts']
self.assertEqual(1, len(actual_hosts))
self.assertEqual(host_list[-1].uuid,
actual_hosts[0].get('uuid'))
class TestHostEnforcement(api_base.FunctionalTest):
def _common_policy_check(self, rule, func, *arg, **kwarg):
self.policy.set_rules({rule: 'project_id:non_fake'})
response = func(*arg, **kwarg)
self.assertEqual(403, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(
"Policy doesn't allow %s to be performed." % rule,
response.json['errors'][0]['detail'])
def test_policy_disallow_get_all(self):
extra_environ = {'HTTP_ACCEPT': 'application/json'}
headers = {'OpenStack-API-Version': 'container 1.3'}
self._common_policy_check(
'host:get_all', self.get_json, '/hosts/',
expect_errors=True, extra_environ=extra_environ, headers=headers)