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:: containers.inc
.. include:: images.inc .. include:: images.inc
.. include:: services.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:disable": "rule:admin_api",
"zun-service:enable": "rule:admin_api", "zun-service:enable": "rule:admin_api",
"zun-service:force_down": "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 base as controllers_base
from zun.api.controllers import link from zun.api.controllers import link
from zun.api.controllers.v1 import containers as container_controller 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 images as image_controller
from zun.api.controllers.v1 import zun_services from zun.api.controllers.v1 import zun_services
from zun.api.controllers import versions as ver from zun.api.controllers import versions as ver
@ -63,7 +64,8 @@ class V1(controllers_base.APIBase):
'links', 'links',
'services', 'services',
'containers', 'containers',
'images' 'images',
'hosts'
) )
@staticmethod @staticmethod
@ -97,6 +99,12 @@ class V1(controllers_base.APIBase):
pecan.request.host_url, pecan.request.host_url,
'images', '', 'images', '',
bookmark=True)] 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 return v1
@ -106,6 +114,7 @@ class Controller(controllers_base.Controller):
services = zun_services.ZunServiceController() services = zun_services.ZunServiceController()
containers = container_controller.ContainersController() containers = container_controller.ContainersController()
images = image_controller.ImagesController() images = image_controller.ImagesController()
hosts = host_controller.HostController()
@pecan.expose('json') @pecan.expose('json')
def get(self): 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.1 - Initial version
* 1.2 - Support user specify pre created networks * 1.2 - Support user specify pre created networks
* 1.3 - Add auto_remove to container * 1.3 - Add auto_remove to container
* 1.4 - Support list all container host
""" """
BASE_VER = '1.1' BASE_VER = '1.1'
CURRENT_MAX_VER = '1.3' CURRENT_MAX_VER = '1.4'
class Version(object): class Version(object):

View File

@ -42,3 +42,9 @@ user documentation.
Add 'auto_remove' field for creating a container. Add 'auto_remove' field for creating a container.
With this field, the container will be automatically removed if it exists. With this field, the container will be automatically removed if it exists.
The new one will be created instead. 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'default_version':
{u'id': u'v1', {u'id': u'v1',
u'links': [{u'href': u'http://localhost/v1/', u'rel': u'self'}], 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'min_version': u'1.1',
u'status': u'CURRENT'}, u'status': u'CURRENT'},
u'description': u'Zun is an OpenStack project which ' 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'versions': [{u'id': u'v1',
u'links': [{u'href': u'http://localhost/v1/', u'links': [{u'href': u'http://localhost/v1/',
u'rel': u'self'}], u'rel': u'self'}],
u'max_version': u'1.3', u'max_version': u'1.4',
u'min_version': u'1.1', u'min_version': u'1.1',
u'status': u'CURRENT'}]} u'status': u'CURRENT'}]}
@ -56,6 +56,10 @@ class TestRootController(api_base.FunctionalTest):
u'rel': u'self'}, u'rel': u'self'},
{u'href': u'http://localhost/containers/', {u'href': u'http://localhost/containers/',
u'rel': u'bookmark'}], 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'images': [{u'href': u'http://localhost/v1/images/',
u'rel': u'self'}, u'rel': u'self'},
{u'href': u'http://localhost/images/', {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)