Added server tags support in nova-api
Added new API microversion which allows the following: - add tag to the server - replace set of server tags with new set of tags - get information about server, including list of tags for server - get just list of tags for server - check if tag exists on a server - remove specified tag from server - remove all tags from server - search servers by tags DocImpact APIImpact Implements: blueprint tag-instances Change-Id: I9573aa52aae9f49945d8806ca5e52ada29fb087a
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"tags": ["sometag"]
|
||||||
|
}
|
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"tags": [
|
||||||
|
"sometag"
|
||||||
|
]
|
||||||
|
}
|
@@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"server": {
|
||||||
|
"tags": [
|
||||||
|
"sometag"
|
||||||
|
],
|
||||||
|
"accessIPv4": "1.2.3.4",
|
||||||
|
"accessIPv6": "80fe::",
|
||||||
|
"addresses": {
|
||||||
|
"private": [
|
||||||
|
{
|
||||||
|
"addr": "192.168.0.3",
|
||||||
|
"OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff",
|
||||||
|
"OS-EXT-IPS:type": "fixed",
|
||||||
|
"version": 4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"created": "2012-12-02T02:11:55Z",
|
||||||
|
"flavor": {
|
||||||
|
"id": "1",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/1",
|
||||||
|
"rel": "bookmark"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hostId": "c949ab4256cea23b6089b710aa2df48bf6577ed915278b62e33ad8bb",
|
||||||
|
"id": "5046e2f2-3b33-4041-b3cf-e085f73e78e7",
|
||||||
|
"image": {
|
||||||
|
"id": "70a599e0-31e7-49b7-b260-868f441e862b",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/images/70a599e0-31e7-49b7-b260-868f441e862b",
|
||||||
|
"rel": "bookmark"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/servers/5046e2f2-3b33-4041-b3cf-e085f73e78e7",
|
||||||
|
"rel": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/servers/5046e2f2-3b33-4041-b3cf-e085f73e78e7",
|
||||||
|
"rel": "bookmark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"My Server Name": "Apache1"
|
||||||
|
},
|
||||||
|
"name": "new-server-test",
|
||||||
|
"progress": 0,
|
||||||
|
"status": "ACTIVE",
|
||||||
|
"tenant_id": "6f70656e737461636b20342065766572",
|
||||||
|
"updated": "2012-12-02T02:11:55Z",
|
||||||
|
"key_name": null,
|
||||||
|
"user_id": "fake",
|
||||||
|
"locked": false,
|
||||||
|
"description": null
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"accessIPv4": "1.2.3.4",
|
||||||
|
"accessIPv6": "80fe::",
|
||||||
|
"addresses": {
|
||||||
|
"private": [
|
||||||
|
{
|
||||||
|
"addr": "192.168.0.3",
|
||||||
|
"OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff",
|
||||||
|
"OS-EXT-IPS:type": "fixed",
|
||||||
|
"version": 4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"created": "2013-09-03T04:01:32Z",
|
||||||
|
"flavor": {
|
||||||
|
"id": "1",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/flavors/1",
|
||||||
|
"rel": "bookmark"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hostId": "bcf92836fc9ed4203a75cb0337afc7f917d2be504164b995c2334b25",
|
||||||
|
"id": "f5dc173b-6804-445a-a6d8-c705dad5b5eb",
|
||||||
|
"image": {
|
||||||
|
"id": "70a599e0-31e7-49b7-b260-868f441e862b",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/images/70a599e0-31e7-49b7-b260-868f441e862b",
|
||||||
|
"rel": "bookmark"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"key_name": null,
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/servers/f5dc173b-6804-445a-a6d8-c705dad5b5eb",
|
||||||
|
"rel": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/servers/f5dc173b-6804-445a-a6d8-c705dad5b5eb",
|
||||||
|
"rel": "bookmark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"My Server Name": "Apache1"
|
||||||
|
},
|
||||||
|
"name": "new-server-test",
|
||||||
|
"progress": 0,
|
||||||
|
"status": "ACTIVE",
|
||||||
|
"tenant_id": "6f70656e737461636b20342065766572",
|
||||||
|
"updated": "2013-09-03T04:01:32Z",
|
||||||
|
"user_id": "fake",
|
||||||
|
"locked": false,
|
||||||
|
"tags": ["sometag"],
|
||||||
|
"description": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@@ -19,7 +19,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"status": "CURRENT",
|
"status": "CURRENT",
|
||||||
"version": "2.25",
|
"version": "2.26",
|
||||||
"min_version": "2.1",
|
"min_version": "2.1",
|
||||||
"updated": "2013-07-23T11:33:21Z"
|
"updated": "2013-07-23T11:33:21Z"
|
||||||
}
|
}
|
||||||
|
@@ -22,7 +22,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"status": "CURRENT",
|
"status": "CURRENT",
|
||||||
"version": "2.25",
|
"version": "2.26",
|
||||||
"min_version": "2.1",
|
"min_version": "2.1",
|
||||||
"updated": "2013-07-23T11:33:21Z"
|
"updated": "2013-07-23T11:33:21Z"
|
||||||
}
|
}
|
||||||
|
@@ -71,7 +71,7 @@ REST_API_VERSION_HISTORY = """REST API Version History:
|
|||||||
* 2.24 - Add API to cancel a running live migration
|
* 2.24 - Add API to cancel a running live migration
|
||||||
* 2.25 - Make block_migration support 'auto' and remove
|
* 2.25 - Make block_migration support 'auto' and remove
|
||||||
disk_over_commit for os-migrateLive.
|
disk_over_commit for os-migrateLive.
|
||||||
|
* 2.26 - Adds support of server tags
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# The minimum and maximum versions of the API supported
|
# The minimum and maximum versions of the API supported
|
||||||
@@ -80,7 +80,7 @@ REST_API_VERSION_HISTORY = """REST API Version History:
|
|||||||
# Note(cyeoh): This only applies for the v2.1 API once microversions
|
# Note(cyeoh): This only applies for the v2.1 API once microversions
|
||||||
# support is fully merged. It does not affect the V2 API.
|
# support is fully merged. It does not affect the V2 API.
|
||||||
_MIN_API_VERSION = "2.1"
|
_MIN_API_VERSION = "2.1"
|
||||||
_MAX_API_VERSION = "2.25"
|
_MAX_API_VERSION = "2.26"
|
||||||
DEFAULT_API_VERSION = _MIN_API_VERSION
|
DEFAULT_API_VERSION = _MIN_API_VERSION
|
||||||
|
|
||||||
|
|
||||||
|
@@ -82,7 +82,8 @@ v21_to_v2_extension_list_mapping = {
|
|||||||
v2_extension_suppress_list = ['servers', 'images', 'versions', 'flavors',
|
v2_extension_suppress_list = ['servers', 'images', 'versions', 'flavors',
|
||||||
'os-block-device-mapping-v1', 'os-consoles',
|
'os-block-device-mapping-v1', 'os-consoles',
|
||||||
'extensions', 'image-metadata', 'ips', 'limits',
|
'extensions', 'image-metadata', 'ips', 'limits',
|
||||||
'server-metadata', 'server-migrations'
|
'server-metadata', 'server-migrations',
|
||||||
|
'os-server-tags'
|
||||||
]
|
]
|
||||||
|
|
||||||
# v2.1 plugins which should appear under a different name in v2
|
# v2.1 plugins which should appear under a different name in v2
|
||||||
|
@@ -35,6 +35,7 @@ def _get_tags_names(tags):
|
|||||||
class ServerTagsController(wsgi.Controller):
|
class ServerTagsController(wsgi.Controller):
|
||||||
_view_builder_class = server_tags.ViewBuilder
|
_view_builder_class = server_tags.ViewBuilder
|
||||||
|
|
||||||
|
@wsgi.Controller.api_version("2.26")
|
||||||
@wsgi.response(204)
|
@wsgi.response(204)
|
||||||
@extensions.expected_errors(404)
|
@extensions.expected_errors(404)
|
||||||
def show(self, req, server_id, id):
|
def show(self, req, server_id, id):
|
||||||
@@ -51,6 +52,7 @@ class ServerTagsController(wsgi.Controller):
|
|||||||
% {'server_id': server_id, 'tag': id})
|
% {'server_id': server_id, 'tag': id})
|
||||||
raise exc.HTTPNotFound(explanation=msg)
|
raise exc.HTTPNotFound(explanation=msg)
|
||||||
|
|
||||||
|
@wsgi.Controller.api_version("2.26")
|
||||||
@extensions.expected_errors(404)
|
@extensions.expected_errors(404)
|
||||||
def index(self, req, server_id):
|
def index(self, req, server_id):
|
||||||
context = req.environ["nova.context"]
|
context = req.environ["nova.context"]
|
||||||
@@ -63,6 +65,7 @@ class ServerTagsController(wsgi.Controller):
|
|||||||
|
|
||||||
return {'tags': _get_tags_names(tags)}
|
return {'tags': _get_tags_names(tags)}
|
||||||
|
|
||||||
|
@wsgi.Controller.api_version("2.26")
|
||||||
@extensions.expected_errors((400, 404))
|
@extensions.expected_errors((400, 404))
|
||||||
@validation.schema(schema.update)
|
@validation.schema(schema.update)
|
||||||
def update(self, req, server_id, id, body):
|
def update(self, req, server_id, id, body):
|
||||||
@@ -109,6 +112,7 @@ class ServerTagsController(wsgi.Controller):
|
|||||||
req, server_id, id)
|
req, server_id, id)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
@wsgi.Controller.api_version("2.26")
|
||||||
@extensions.expected_errors((400, 404))
|
@extensions.expected_errors((400, 404))
|
||||||
@validation.schema(schema.update_all)
|
@validation.schema(schema.update_all)
|
||||||
def update_all(self, req, server_id, body):
|
def update_all(self, req, server_id, body):
|
||||||
@@ -149,6 +153,7 @@ class ServerTagsController(wsgi.Controller):
|
|||||||
|
|
||||||
return {'tags': _get_tags_names(tags)}
|
return {'tags': _get_tags_names(tags)}
|
||||||
|
|
||||||
|
@wsgi.Controller.api_version("2.26")
|
||||||
@wsgi.response(204)
|
@wsgi.response(204)
|
||||||
@extensions.expected_errors(404)
|
@extensions.expected_errors(404)
|
||||||
def delete(self, req, server_id, id):
|
def delete(self, req, server_id, id):
|
||||||
@@ -162,6 +167,7 @@ class ServerTagsController(wsgi.Controller):
|
|||||||
except exception.InstanceNotFound as e:
|
except exception.InstanceNotFound as e:
|
||||||
raise exc.HTTPNotFound(explanation=e.format_message())
|
raise exc.HTTPNotFound(explanation=e.format_message())
|
||||||
|
|
||||||
|
@wsgi.Controller.api_version("2.26")
|
||||||
@wsgi.response(204)
|
@wsgi.response(204)
|
||||||
@extensions.expected_errors(404)
|
@extensions.expected_errors(404)
|
||||||
def delete_all(self, req, server_id):
|
def delete_all(self, req, server_id):
|
||||||
|
@@ -45,6 +45,7 @@ from nova import objects
|
|||||||
from nova import utils
|
from nova import utils
|
||||||
|
|
||||||
ALIAS = 'servers'
|
ALIAS = 'servers'
|
||||||
|
TAG_SEARCH_FILTERS = ('tags', 'tags-any', 'not-tags', 'not-tags-any')
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
CONF.import_opt('enable_instance_password',
|
CONF.import_opt('enable_instance_password',
|
||||||
@@ -353,6 +354,12 @@ class ServersController(wsgi.Controller):
|
|||||||
msg = _("Only administrators may list deleted instances")
|
msg = _("Only administrators may list deleted instances")
|
||||||
raise exc.HTTPForbidden(explanation=msg)
|
raise exc.HTTPForbidden(explanation=msg)
|
||||||
|
|
||||||
|
if api_version_request.is_supported(req, min_version='2.26'):
|
||||||
|
for tag_filter in TAG_SEARCH_FILTERS:
|
||||||
|
if tag_filter in search_opts:
|
||||||
|
search_opts[tag_filter] = search_opts[
|
||||||
|
tag_filter].split(',')
|
||||||
|
|
||||||
# If tenant_id is passed as a search parameter this should
|
# If tenant_id is passed as a search parameter this should
|
||||||
# imply that all_tenants is also enabled unless explicitly
|
# imply that all_tenants is also enabled unless explicitly
|
||||||
# disabled. Note that the tenant_id parameter is filtered out
|
# disabled. Note that the tenant_id parameter is filtered out
|
||||||
@@ -397,6 +404,9 @@ class ServersController(wsgi.Controller):
|
|||||||
|
|
||||||
expected_attrs = ['pci_devices']
|
expected_attrs = ['pci_devices']
|
||||||
if is_detail:
|
if is_detail:
|
||||||
|
if api_version_request.is_supported(req, '2.26'):
|
||||||
|
expected_attrs.append("tags")
|
||||||
|
|
||||||
# merge our expected attrs with what the view builder needs for
|
# merge our expected attrs with what the view builder needs for
|
||||||
# showing details
|
# showing details
|
||||||
expected_attrs = self._view_builder.get_show_expected_attrs(
|
expected_attrs = self._view_builder.get_show_expected_attrs(
|
||||||
@@ -1141,6 +1151,8 @@ class ServersController(wsgi.Controller):
|
|||||||
'ip', 'changes-since', 'all_tenants')
|
'ip', 'changes-since', 'all_tenants')
|
||||||
if api_version_request.is_supported(req, min_version='2.5'):
|
if api_version_request.is_supported(req, min_version='2.5'):
|
||||||
opt_list += ('ip6',)
|
opt_list += ('ip6',)
|
||||||
|
if api_version_request.is_supported(req, min_version='2.26'):
|
||||||
|
opt_list += TAG_SEARCH_FILTERS
|
||||||
return opt_list
|
return opt_list
|
||||||
|
|
||||||
def _get_instance(self, context, instance_uuid):
|
def _get_instance(self, context, instance_uuid):
|
||||||
|
@@ -313,4 +313,7 @@ class ViewBuilderV21(ViewBuilder):
|
|||||||
server["server"]["description"] = instance.get(
|
server["server"]["description"] = instance.get(
|
||||||
"display_description")
|
"display_description")
|
||||||
|
|
||||||
|
if api_version_request.is_supported(request, min_version="2.26"):
|
||||||
|
server["server"]["tags"] = [t.tag for t in instance.tags]
|
||||||
|
|
||||||
return server
|
return server
|
||||||
|
@@ -218,3 +218,68 @@ user documentation.
|
|||||||
|
|
||||||
Modify input parameter for ``os-migrateLive``. The block_migration will
|
Modify input parameter for ``os-migrateLive``. The block_migration will
|
||||||
support 'auto' value, and disk_over_commit flag will be removed.
|
support 'auto' value, and disk_over_commit flag will be removed.
|
||||||
|
|
||||||
|
2.26
|
||||||
|
----
|
||||||
|
|
||||||
|
Added support of server tags.
|
||||||
|
|
||||||
|
A user can create, update, delete or check existence of simple string tags
|
||||||
|
for servers by the os-server-tags plugin.
|
||||||
|
|
||||||
|
The resource point for these operations is /servers/<server_id>/tags
|
||||||
|
|
||||||
|
A user can add a single tag to the server by sending PUT request to the
|
||||||
|
/servers/<server_id>/tags/<tag>
|
||||||
|
|
||||||
|
where <tag> is any valid tag name.
|
||||||
|
|
||||||
|
A user can replace **all** current server tags to the new set of tags
|
||||||
|
by sending PUT request to the /servers/<server_id>/tags. New set of tags
|
||||||
|
must be specified in request body. This set must be in list 'tags'.
|
||||||
|
|
||||||
|
A user can remove specified tag from the server by sending DELETE request
|
||||||
|
to the /servers/<server_id>/tags/<tag>
|
||||||
|
|
||||||
|
where <tag> is tag name which user wants to remove.
|
||||||
|
|
||||||
|
A user can remove **all** tags from the server by sending DELETE request
|
||||||
|
to the /servers/<server_id>/tags
|
||||||
|
|
||||||
|
A user can get a set of server tags with information about server by sending
|
||||||
|
GET request to the /servers/<server_id>
|
||||||
|
|
||||||
|
Request returns dictionary with information about specified server, including
|
||||||
|
list 'tags' ::
|
||||||
|
|
||||||
|
{
|
||||||
|
'id': {server_id},
|
||||||
|
...
|
||||||
|
'tags': ['foo', 'bar', 'baz']
|
||||||
|
}
|
||||||
|
|
||||||
|
A user can get **only** a set of server tags by sending GET request to the
|
||||||
|
/servers/<server_id>/tags
|
||||||
|
|
||||||
|
Response ::
|
||||||
|
|
||||||
|
{
|
||||||
|
'tags': ['foo', 'bar', 'baz']
|
||||||
|
}
|
||||||
|
|
||||||
|
A user can check if a tag exists or not on a server by sending
|
||||||
|
GET /servers/{server_id}/tags/{tag}
|
||||||
|
|
||||||
|
Request returns `204 No Content` if tag exist on a server or `404 Not Found`
|
||||||
|
if tag doesn't exist on a server.
|
||||||
|
|
||||||
|
A user can filter servers in GET /servers request by new filters:
|
||||||
|
|
||||||
|
* tags
|
||||||
|
* tags-any
|
||||||
|
* not-tags
|
||||||
|
* not-tags-any
|
||||||
|
|
||||||
|
These filters can be combined. Also user can use more than one string tags
|
||||||
|
for each filter. In this case string tags for each filter must be separated
|
||||||
|
by comma: GET /servers?tags=red&tags-any=green,orange
|
||||||
|
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"tags": ["%(tag)s"]
|
||||||
|
}
|
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"tags": [
|
||||||
|
"%(tag)s"
|
||||||
|
]
|
||||||
|
}
|
@@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"server": {
|
||||||
|
"tags": [
|
||||||
|
"%(tag)s"
|
||||||
|
],
|
||||||
|
"accessIPv4": "%(access_ip_v4)s",
|
||||||
|
"accessIPv6": "%(access_ip_v6)s",
|
||||||
|
"addresses": {
|
||||||
|
"private": [
|
||||||
|
{
|
||||||
|
"addr": "192.168.0.3",
|
||||||
|
"OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff",
|
||||||
|
"OS-EXT-IPS:type": "fixed",
|
||||||
|
"version": 4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"created": "%(isotime)s",
|
||||||
|
"flavor": {
|
||||||
|
"id": "1",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"href": "%(compute_endpoint)s/flavors/1",
|
||||||
|
"rel": "bookmark"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hostId": "%(hostid)s",
|
||||||
|
"id": "%(id)s",
|
||||||
|
"image": {
|
||||||
|
"id": "%(uuid)s",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"href": "%(compute_endpoint)s/images/%(uuid)s",
|
||||||
|
"rel": "bookmark"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"href": "%(versioned_compute_endpoint)s/servers/%(uuid)s",
|
||||||
|
"rel": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "%(compute_endpoint)s/servers/%(uuid)s",
|
||||||
|
"rel": "bookmark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"My Server Name": "Apache1"
|
||||||
|
},
|
||||||
|
"name": "new-server-test",
|
||||||
|
"progress": 0,
|
||||||
|
"status": "ACTIVE",
|
||||||
|
"tenant_id": "6f70656e737461636b20342065766572",
|
||||||
|
"updated": "%(isotime)s",
|
||||||
|
"key_name": null,
|
||||||
|
"user_id": "fake",
|
||||||
|
"locked": false,
|
||||||
|
"description": null
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"accessIPv4": "%(access_ip_v4)s",
|
||||||
|
"accessIPv6": "%(access_ip_v6)s",
|
||||||
|
"addresses": {
|
||||||
|
"private": [
|
||||||
|
{
|
||||||
|
"addr": "%(ip)s",
|
||||||
|
"OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff",
|
||||||
|
"OS-EXT-IPS:type": "fixed",
|
||||||
|
"version": 4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"created": "%(isotime)s",
|
||||||
|
"flavor": {
|
||||||
|
"id": "1",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"href": "%(compute_endpoint)s/flavors/1",
|
||||||
|
"rel": "bookmark"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hostId": "%(hostid)s",
|
||||||
|
"id": "%(id)s",
|
||||||
|
"image": {
|
||||||
|
"id": "%(uuid)s",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"href": "%(compute_endpoint)s/images/%(uuid)s",
|
||||||
|
"rel": "bookmark"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"key_name": null,
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"href": "%(versioned_compute_endpoint)s/servers/%(uuid)s",
|
||||||
|
"rel": "self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"href": "%(compute_endpoint)s/servers/%(id)s",
|
||||||
|
"rel": "bookmark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"My Server Name": "Apache1"
|
||||||
|
},
|
||||||
|
"name": "new-server-test",
|
||||||
|
"progress": 0,
|
||||||
|
"status": "ACTIVE",
|
||||||
|
"tenant_id": "6f70656e737461636b20342065766572",
|
||||||
|
"updated": "%(isotime)s",
|
||||||
|
"user_id": "fake",
|
||||||
|
"locked": false,
|
||||||
|
"tags": ["%(tag)s"],
|
||||||
|
"description": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
96
nova/tests/functional/api_sample_tests/test_server_tags.py
Normal file
96
nova/tests/functional/api_sample_tests/test_server_tags.py
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
# 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 nova.db.sqlalchemy import models
|
||||||
|
from nova.tests.functional.api_sample_tests import test_servers
|
||||||
|
|
||||||
|
TAG = 'sometag'
|
||||||
|
|
||||||
|
|
||||||
|
class ServerTagsJsonTest(test_servers.ServersSampleBase):
|
||||||
|
extension_name = 'os-server-tags'
|
||||||
|
microversion = '2.26'
|
||||||
|
scenarios = [('v2_26', {'api_major_version': 'v2.1'})]
|
||||||
|
|
||||||
|
def _get_create_subs(self):
|
||||||
|
return {'tag': TAG}
|
||||||
|
|
||||||
|
def _put_server_tags(self):
|
||||||
|
"""Verify the response status and returns the UUID of the
|
||||||
|
newly created server with tags.
|
||||||
|
"""
|
||||||
|
uuid = self._post_server()
|
||||||
|
subs = self._get_create_subs()
|
||||||
|
response = self._do_put('servers/%s/tags' % uuid,
|
||||||
|
'server-tags-put-all-req', subs)
|
||||||
|
self.assertEqual(200, response.status_code)
|
||||||
|
return uuid
|
||||||
|
|
||||||
|
def test_server_tags_update_all(self):
|
||||||
|
self._put_server_tags()
|
||||||
|
|
||||||
|
def test_server_tags_show(self):
|
||||||
|
uuid = self._put_server_tags()
|
||||||
|
response = self._do_get('servers/%s/tags/%s' % (uuid, TAG))
|
||||||
|
self.assertEqual(204, response.status_code)
|
||||||
|
|
||||||
|
def test_server_tags_show_with_details_information(self):
|
||||||
|
uuid = self._put_server_tags()
|
||||||
|
response = self._do_get('servers/%s' % uuid)
|
||||||
|
subs = self._get_regexes()
|
||||||
|
subs['hostid'] = '[a-f0-9]+'
|
||||||
|
subs['tag'] = '[0-9a-zA-Z]+'
|
||||||
|
subs['access_ip_v4'] = '1.2.3.4'
|
||||||
|
subs['access_ip_v6'] = '80fe::'
|
||||||
|
self._verify_response('server-tags-show-details-resp',
|
||||||
|
subs, response, 200)
|
||||||
|
|
||||||
|
def test_server_tags_list_with_details_information(self):
|
||||||
|
self._put_server_tags()
|
||||||
|
response = self._do_get('servers/detail')
|
||||||
|
subs = self._get_regexes()
|
||||||
|
subs['hostid'] = '[a-f0-9]+'
|
||||||
|
subs['tag'] = '[0-9a-zA-Z]+'
|
||||||
|
subs['access_ip_v4'] = '1.2.3.4'
|
||||||
|
subs['access_ip_v6'] = '80fe::'
|
||||||
|
self._verify_response('servers-tags-details-resp', subs, response, 200)
|
||||||
|
|
||||||
|
def test_server_tags_index(self):
|
||||||
|
uuid = self._put_server_tags()
|
||||||
|
response = self._do_get('servers/%s/tags' % uuid)
|
||||||
|
subs = self._get_regexes()
|
||||||
|
subs['tag'] = '[0-9a-zA-Z]+'
|
||||||
|
self._verify_response('server-tags-index-resp', subs, response, 200)
|
||||||
|
|
||||||
|
def test_server_tags_update(self):
|
||||||
|
uuid = self._put_server_tags()
|
||||||
|
tag = models.Tag()
|
||||||
|
tag.resource_id = uuid
|
||||||
|
tag.tag = 'OtherTag'
|
||||||
|
response = self._do_put('servers/%s/tags/%s' % (uuid, tag.tag),
|
||||||
|
'server-tags-put-req', {})
|
||||||
|
self.assertEqual(201, response.status_code)
|
||||||
|
expected_location = "%s/servers/%s/tags/%s" % (
|
||||||
|
self._get_vers_compute_endpoint(), uuid, tag.tag)
|
||||||
|
self.assertEqual(expected_location, response.headers['Location'])
|
||||||
|
|
||||||
|
def test_server_tags_delete(self):
|
||||||
|
uuid = self._put_server_tags()
|
||||||
|
response = self._do_delete('servers/%s/tags/%s' % (uuid, TAG))
|
||||||
|
self.assertEqual(204, response.status_code)
|
||||||
|
self.assertEqual('', response.content)
|
||||||
|
|
||||||
|
def test_server_tags_delete_all(self):
|
||||||
|
uuid = self._put_server_tags()
|
||||||
|
response = self._do_delete('servers/%s/tags' % uuid)
|
||||||
|
self.assertEqual(204, response.status_code)
|
||||||
|
self.assertEqual('', response.content)
|
@@ -32,6 +32,8 @@ NON_EXISTING_UUID = '123'
|
|||||||
|
|
||||||
|
|
||||||
class ServerTagsTest(test.TestCase):
|
class ServerTagsTest(test.TestCase):
|
||||||
|
api_version = '2.26'
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(ServerTagsTest, self).setUp()
|
super(ServerTagsTest, self).setUp()
|
||||||
self.controller = server_tags.ServerTagsController()
|
self.controller = server_tags.ServerTagsController()
|
||||||
@@ -43,7 +45,7 @@ class ServerTagsTest(test.TestCase):
|
|||||||
return tag
|
return tag
|
||||||
|
|
||||||
def _get_request(self, url, method):
|
def _get_request(self, url, method):
|
||||||
request = fakes.HTTPRequest.blank(url)
|
request = fakes.HTTPRequest.blank(url, version=self.api_version)
|
||||||
request.method = method
|
request.method = method
|
||||||
return request
|
return request
|
||||||
|
|
||||||
|
@@ -1462,6 +1462,65 @@ class ServersControllerTestV219(ServersControllerTest):
|
|||||||
self._test_list_server_detail_with_descriptions('desc1', 'desc2')
|
self._test_list_server_detail_with_descriptions('desc1', 'desc2')
|
||||||
|
|
||||||
|
|
||||||
|
class ServersControllerTestV226(ControllerTest):
|
||||||
|
wsgi_api_version = '2.26'
|
||||||
|
|
||||||
|
@mock.patch.object(compute_api.API, 'get')
|
||||||
|
def test_get_server_with_tags_by_id(self, mock_get):
|
||||||
|
req = fakes.HTTPRequest.blank('/fake/servers/%s' % FAKE_UUID,
|
||||||
|
version=self.wsgi_api_version)
|
||||||
|
ctxt = req.environ['nova.context']
|
||||||
|
|
||||||
|
fake_server = fakes.stub_instance_obj(
|
||||||
|
ctxt, id=2, vm_state=vm_states.ACTIVE, progress=100)
|
||||||
|
|
||||||
|
tags = ['tag1', 'tag2']
|
||||||
|
tag_list = objects.TagList(objects=[
|
||||||
|
objects.Tag(resource_id=FAKE_UUID, tag=tag)
|
||||||
|
for tag in tags])
|
||||||
|
|
||||||
|
fake_server.tags = tag_list
|
||||||
|
mock_get.return_value = fake_server
|
||||||
|
|
||||||
|
res_dict = self.controller.show(req, FAKE_UUID)
|
||||||
|
|
||||||
|
self.assertIn('tags', res_dict['server'])
|
||||||
|
self.assertEqual(res_dict['server']['tags'], tags)
|
||||||
|
|
||||||
|
@mock.patch.object(compute_api.API, 'get_all')
|
||||||
|
def _test_get_servers_allows_tag_filters(self, filter_name, mock_get_all):
|
||||||
|
server_uuid = str(uuid.uuid4())
|
||||||
|
req = fakes.HTTPRequest.blank('/fake/servers?%s=t1,t2' % filter_name,
|
||||||
|
version=self.wsgi_api_version)
|
||||||
|
ctxt = req.environ['nova.context']
|
||||||
|
|
||||||
|
def fake_get_all(*a, **kw):
|
||||||
|
self.assertIsNotNone(kw['search_opts'])
|
||||||
|
self.assertIn(filter_name, kw['search_opts'])
|
||||||
|
self.assertEqual(kw['search_opts'][filter_name], ['t1', 't2'])
|
||||||
|
return objects.InstanceList(
|
||||||
|
objects=[fakes.stub_instance_obj(ctxt, uuid=server_uuid)])
|
||||||
|
|
||||||
|
mock_get_all.side_effect = fake_get_all
|
||||||
|
|
||||||
|
servers = self.controller.index(req)['servers']
|
||||||
|
|
||||||
|
self.assertEqual(len(servers), 1)
|
||||||
|
self.assertEqual(servers[0]['id'], server_uuid)
|
||||||
|
|
||||||
|
def test_get_servers_allows_tags_filter(self):
|
||||||
|
self._test_get_servers_allows_tag_filters('tags')
|
||||||
|
|
||||||
|
def test_get_servers_allows_tags_any_filter(self):
|
||||||
|
self._test_get_servers_allows_tag_filters('tags-any')
|
||||||
|
|
||||||
|
def test_get_servers_allows_not_tags_filter(self):
|
||||||
|
self._test_get_servers_allows_tag_filters('not-tags')
|
||||||
|
|
||||||
|
def test_get_servers_allows_not_tags_any_filter(self):
|
||||||
|
self._test_get_servers_allows_tag_filters('not-tags-any')
|
||||||
|
|
||||||
|
|
||||||
class ServersControllerDeleteTest(ControllerTest):
|
class ServersControllerDeleteTest(ControllerTest):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Microversion v2.26 allows to create/update/delete simple string tags.
|
||||||
|
They can be used for filtering servers by these tags.
|
@@ -142,6 +142,7 @@ nova.api.v21.extensions =
|
|||||||
server_metadata = nova.api.openstack.compute.server_metadata:ServerMetadata
|
server_metadata = nova.api.openstack.compute.server_metadata:ServerMetadata
|
||||||
server_migrations = nova.api.openstack.compute.server_migrations:ServerMigrations
|
server_migrations = nova.api.openstack.compute.server_migrations:ServerMigrations
|
||||||
server_password = nova.api.openstack.compute.server_password:ServerPassword
|
server_password = nova.api.openstack.compute.server_password:ServerPassword
|
||||||
|
server_tags = nova.api.openstack.compute.server_tags:ServerTags
|
||||||
server_usage = nova.api.openstack.compute.server_usage:ServerUsage
|
server_usage = nova.api.openstack.compute.server_usage:ServerUsage
|
||||||
server_groups = nova.api.openstack.compute.server_groups:ServerGroups
|
server_groups = nova.api.openstack.compute.server_groups:ServerGroups
|
||||||
servers = nova.api.openstack.compute.servers:Servers
|
servers = nova.api.openstack.compute.servers:Servers
|
||||||
|
Reference in New Issue
Block a user