Adds missing server tags APIs to servers client.

Currently, there is no server tags API testing implemented in
Tempest [1]. This API was introduced to Nova in 2.26 [2].

This patch adds the server tags APIs to the servers client,
as well as corresponding unit test and schema changes.

Mocking was added for the unit tests, because these tests
explicitly require that the COMPUTE_MICROVERSION header
in the base_compute_client be '2.26', or else the schema
that is returned by get_schema in base_compute_client
will return the v2.1 schema, resulting in validation errors.

[1] https://github.com/openstack/tempest/tree/master/tempest/lib/services/compute
[2] https://developer.openstack.org/api-ref/compute/

Change-Id: I06b9c6f42e066863310b4fe7ee7030c1dfb89467
Implements: blueprint missing-server-tags-api-test
This commit is contained in:
Felipe Monteiro 2017-02-17 18:49:02 -05:00
parent 0b5c2c91de
commit 7c95befefb
5 changed files with 363 additions and 0 deletions

View File

@ -0,0 +1,8 @@
---
features:
- |
Add server tags APIs to the servers_client library.
This feature enables the possibility of upating, deleting
and checking existence of a tag on a server, as well
as updating and deleting all tags on a server.

View File

@ -0,0 +1,108 @@
# Copyright 2017 AT&T Corp.
# All Rights Reserved.
#
# 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 six
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest.lib import decorators
from tempest import test
class ServerTagsTestJSON(base.BaseV2ComputeTest):
min_microversion = '2.26'
max_microversion = 'latest'
@classmethod
def skip_checks(cls):
super(ServerTagsTestJSON, cls).skip_checks()
if not test.is_extension_enabled('os-server-tags', 'compute'):
msg = "os-server-tags extension is not enabled."
raise cls.skipException(msg)
@classmethod
def setup_clients(cls):
super(ServerTagsTestJSON, cls).setup_clients()
cls.client = cls.servers_client
@classmethod
def resource_setup(cls):
super(ServerTagsTestJSON, cls).resource_setup()
cls.server = cls.create_test_server(wait_until='ACTIVE')
def _update_server_tags(self, server_id, tags):
if not isinstance(tags, (list, tuple)):
tags = [tags]
for tag in tags:
self.client.update_tag(server_id, tag)
self.addCleanup(self.client.delete_all_tags, server_id)
@decorators.idempotent_id('8d95abe2-c658-4c42-9a44-c0258500306b')
def test_create_delete_tag(self):
# Check that no tags exist.
fetched_tags = self.client.list_tags(self.server['id'])['tags']
self.assertEmpty(fetched_tags)
# Add server tag to the server.
assigned_tag = data_utils.rand_name('tag')
self._update_server_tags(self.server['id'], assigned_tag)
# Check that added tag exists.
fetched_tags = self.client.list_tags(self.server['id'])['tags']
self.assertEqual([assigned_tag], fetched_tags)
# Remove assigned tag from server and check that it was removed.
self.client.delete_tag(self.server['id'], assigned_tag)
fetched_tags = self.client.list_tags(self.server['id'])['tags']
self.assertEmpty(fetched_tags)
@decorators.idempotent_id('a2c1af8c-127d-417d-974b-8115f7e3d831')
def test_update_all_tags(self):
# Add server tags to the server.
tags = [data_utils.rand_name('tag'), data_utils.rand_name('tag')]
self._update_server_tags(self.server['id'], tags)
# Replace tags with new tags and check that they are present.
new_tags = [data_utils.rand_name('tag'), data_utils.rand_name('tag')]
replaced_tags = self.client.update_all_tags(
self.server['id'], new_tags)['tags']
six.assertCountEqual(self, new_tags, replaced_tags)
# List the tags and check that the tags were replaced.
fetched_tags = self.client.list_tags(self.server['id'])['tags']
six.assertCountEqual(self, new_tags, fetched_tags)
@decorators.idempotent_id('a63b2a74-e918-4b7c-bcab-10c855f3a57e')
def test_delete_all_tags(self):
# Add server tags to the server.
assigned_tags = [data_utils.rand_name('tag'),
data_utils.rand_name('tag')]
self._update_server_tags(self.server['id'], assigned_tags)
# Delete tags from the server and check that they were deleted.
self.client.delete_all_tags(self.server['id'])
fetched_tags = self.client.list_tags(self.server['id'])['tags']
self.assertEmpty(fetched_tags)
@decorators.idempotent_id('81279a66-61c3-4759-b830-a2dbe64cbe08')
def test_check_tag_existence(self):
# Add server tag to the server.
assigned_tag = data_utils.rand_name('tag')
self._update_server_tags(self.server['id'], assigned_tag)
# Check that added tag exists. Throws a 404 if not found, else a 204,
# which was already checked by the schema validation.
self.client.check_tag_existence(self.server['id'], assigned_tag)

View File

@ -1,4 +1,5 @@
# Copyright 2016 IBM Corp. # Copyright 2016 IBM Corp.
# Copyright 2017 AT&T Corp.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 # not use this file except in compliance with the License. You may obtain
@ -45,3 +46,41 @@ list_servers_detail['response_body']['properties']['servers']['items'][
# list response schema wasn't changed for v2.26 so use v2.1 # list response schema wasn't changed for v2.26 so use v2.1
list_servers = copy.deepcopy(servers21.list_servers) list_servers = copy.deepcopy(servers21.list_servers)
list_tags = {
'status_code': [200],
'response_body': {
'type': 'object',
'properties': {
'tags': {
'type': 'array',
'items': {
'type': 'string'
}
}
},
'additionalProperties': False,
'required': ['tags']
}
}
update_all_tags = copy.deepcopy(list_tags)
delete_all_tags = {'status_code': [204]}
check_tag_existence = {'status_code': [204]}
update_tag = {
'status_code': [201, 204],
'response_header': {
'type': 'object',
'properties': {
'location': {
'type': 'string'
}
},
'required': ['location']
}
}
delete_tag = {'status_code': [204]}

View File

@ -1,5 +1,6 @@
# Copyright 2012 OpenStack Foundation # Copyright 2012 OpenStack Foundation
# Copyright 2013 Hewlett-Packard Development Company, L.P. # Copyright 2013 Hewlett-Packard Development Company, L.P.
# Copyright 2017 AT&T Corp.
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -732,3 +733,92 @@ class ServersClient(base_compute_client.BaseComputeClient):
self.validate_response(security_groups_schema.list_security_groups, self.validate_response(security_groups_schema.list_security_groups,
resp, body) resp, body)
return rest_client.ResponseBody(resp, body) return rest_client.ResponseBody(resp, body)
def list_tags(self, server_id):
"""Lists all tags for a server.
For a full list of available parameters, please refer to the official
API reference:
https://developer.openstack.org/api-ref/compute/#list-tags
"""
url = 'servers/%s/tags' % server_id
resp, body = self.get(url)
body = json.loads(body)
schema = self.get_schema(self.schema_versions_info)
self.validate_response(schema.list_tags, resp, body)
return rest_client.ResponseBody(resp, body)
def update_all_tags(self, server_id, tags):
"""Replaces all tags on specified server with the new set of tags.
For a full list of available parameters, please refer to the official
API reference:
https://developer.openstack.org/api-ref/compute/#replace-tags
:param tags: List of tags to replace current server tags with.
"""
url = 'servers/%s/tags' % server_id
put_body = {'tags': tags}
resp, body = self.put(url, json.dumps(put_body))
body = json.loads(body)
schema = self.get_schema(self.schema_versions_info)
self.validate_response(schema.update_all_tags, resp, body)
return rest_client.ResponseBody(resp, body)
def delete_all_tags(self, server_id):
"""Deletes all tags from the specified server.
For a full list of available parameters, please refer to the official
API reference:
https://developer.openstack.org/api-ref/compute/#delete-all-tags
"""
url = 'servers/%s/tags' % server_id
resp, body = self.delete(url)
schema = self.get_schema(self.schema_versions_info)
self.validate_response(schema.delete_all_tags, resp, body)
return rest_client.ResponseBody(resp, body)
def check_tag_existence(self, server_id, tag):
"""Checks tag existence on the server.
For a full list of available parameters, please refer to the official
API reference:
https://developer.openstack.org/api-ref/compute/#check-tag-existence
:param tag: Check for existence of tag on specified server.
"""
url = 'servers/%s/tags/%s' % (server_id, tag)
resp, body = self.get(url)
schema = self.get_schema(self.schema_versions_info)
self.validate_response(schema.check_tag_existence, resp, body)
return rest_client.ResponseBody(resp, body)
def update_tag(self, server_id, tag):
"""Adds a single tag to the server if server has no specified tag.
For a full list of available parameters, please refer to the official
API reference:
https://developer.openstack.org/api-ref/compute/#add-a-single-tag
:param tag: Tag to be added to the specified server.
"""
url = 'servers/%s/tags/%s' % (server_id, tag)
resp, body = self.put(url, None)
schema = self.get_schema(self.schema_versions_info)
self.validate_response(schema.update_tag, resp, body)
return rest_client.ResponseBody(resp, body)
def delete_tag(self, server_id, tag):
"""Deletes a single tag from the specified server.
For a full list of available parameters, please refer to the official
API reference:
https://developer.openstack.org/api-ref/compute/#delete-a-single-tag
:param tag: Tag to be removed from the specified server.
"""
url = 'servers/%s/tags/%s' % (server_id, tag)
resp, body = self.delete(url)
schema = self.get_schema(self.schema_versions_info)
self.validate_response(schema.delete_tag, resp, body)
return rest_client.ResponseBody(resp, body)

View File

@ -1,4 +1,5 @@
# Copyright 2015 IBM Corp. # Copyright 2015 IBM Corp.
# Copyright 2017 AT&T Corp.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 # not use this file except in compliance with the License. You may obtain
@ -14,6 +15,9 @@
import copy import copy
import mock
from tempest.lib.services.compute import base_compute_client
from tempest.lib.services.compute import servers_client from tempest.lib.services.compute import servers_client
from tempest.tests.lib import fake_auth_provider from tempest.tests.lib import fake_auth_provider
from tempest.tests.lib.services import base from tempest.tests.lib.services import base
@ -186,6 +190,9 @@ class TestServersClient(base.BaseServiceTest):
FAKE_REBUILD_SERVER = copy.deepcopy(FAKE_SERVER_GET) FAKE_REBUILD_SERVER = copy.deepcopy(FAKE_SERVER_GET)
FAKE_REBUILD_SERVER['server']['adminPass'] = 'fake-admin-pass' FAKE_REBUILD_SERVER['server']['adminPass'] = 'fake-admin-pass'
FAKE_TAGS = ["foo", "bar"]
REPLACE_FAKE_TAGS = ["baz", "qux"]
server_id = FAKE_SERVER_GET['server']['id'] server_id = FAKE_SERVER_GET['server']['id']
network_id = 'a6b0875b-6b5d-4a5a-81eb-0c3aa62e5fdb' network_id = 'a6b0875b-6b5d-4a5a-81eb-0c3aa62e5fdb'
@ -194,6 +201,7 @@ class TestServersClient(base.BaseServiceTest):
fake_auth = fake_auth_provider.FakeAuthProvider() fake_auth = fake_auth_provider.FakeAuthProvider()
self.client = servers_client.ServersClient( self.client = servers_client.ServersClient(
fake_auth, 'compute', 'regionOne') fake_auth, 'compute', 'regionOne')
self.addCleanup(mock.patch.stopall)
def test_list_servers_with_str_body(self): def test_list_servers_with_str_body(self):
self._test_list_servers() self._test_list_servers()
@ -1031,3 +1039,113 @@ class TestServersClient(base.BaseServiceTest):
{'security_groups': self.FAKE_SECURITY_GROUPS}, {'security_groups': self.FAKE_SECURITY_GROUPS},
server_id=self.server_id, server_id=self.server_id,
) )
@mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
new_callable=mock.PropertyMock(return_value='2.26'))
def test_list_tags_str_body(self, _):
self._test_list_tags()
@mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
new_callable=mock.PropertyMock(return_value='2.26'))
def test_list_tags_byte_body(self, _):
self._test_list_tags(bytes_body=True)
def _test_list_tags(self, bytes_body=False):
expected = {"tags": self.FAKE_TAGS}
self.check_service_client_function(
self.client.list_tags,
'tempest.lib.common.rest_client.RestClient.get',
expected,
server_id=self.server_id,
to_utf=bytes_body)
@mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
new_callable=mock.PropertyMock(return_value='2.26'))
def test_update_all_tags_str_body(self, _):
self._test_update_all_tags()
@mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
new_callable=mock.PropertyMock(return_value='2.26'))
def test_update_all_tags_byte_body(self, _):
self._test_update_all_tags(bytes_body=True)
def _test_update_all_tags(self, bytes_body=False):
expected = {"tags": self.REPLACE_FAKE_TAGS}
self.check_service_client_function(
self.client.update_all_tags,
'tempest.lib.common.rest_client.RestClient.put',
expected,
server_id=self.server_id,
tags=self.REPLACE_FAKE_TAGS,
to_utf=bytes_body)
@mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
new_callable=mock.PropertyMock(return_value='2.26'))
def test_delete_all_tags(self, _):
self.check_service_client_function(
self.client.delete_all_tags,
'tempest.lib.common.rest_client.RestClient.delete',
{},
server_id=self.server_id,
status=204)
@mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
new_callable=mock.PropertyMock(return_value='2.26'))
def test_check_tag_existence_str_body(self, _):
self._test_check_tag_existence()
@mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
new_callable=mock.PropertyMock(return_value='2.26'))
def test_check_tag_existence_byte_body(self, _):
self._test_check_tag_existence(bytes_body=True)
def _test_check_tag_existence(self, bytes_body=False):
self.check_service_client_function(
self.client.check_tag_existence,
'tempest.lib.common.rest_client.RestClient.get',
{},
server_id=self.server_id,
tag=self.FAKE_TAGS[0],
status=204,
to_utf=bytes_body)
@mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
new_callable=mock.PropertyMock(return_value='2.26'))
def test_update_tag_str_body(self, _):
self._test_update_tag()
@mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
new_callable=mock.PropertyMock(return_value='2.26'))
def test_update_tag_byte_body(self, _):
self._test_update_tag(bytes_body=True)
def _test_update_tag(self, bytes_body=False):
self.check_service_client_function(
self.client.update_tag,
'tempest.lib.common.rest_client.RestClient.put',
{},
server_id=self.server_id,
tag=self.FAKE_TAGS[0],
status=201,
headers={'location': 'fake_location'},
to_utf=bytes_body)
@mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
new_callable=mock.PropertyMock(return_value='2.26'))
def test_delete_tag_str_body(self, _):
self._test_delete_tag()
@mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
new_callable=mock.PropertyMock(return_value='2.26'))
def test_delete_tag_byte_body(self, _):
self._test_delete_tag(bytes_body=True)
def _test_delete_tag(self, bytes_body=False):
self.check_service_client_function(
self.client.delete_tag,
'tempest.lib.common.rest_client.RestClient.delete',
{},
server_id=self.server_id,
tag=self.FAKE_TAGS[0],
status=204,
to_utf=bytes_body)