Support tag instances when boot(4/4)

This is the 4th patch of the series,
this patch adds a new microversion
in API to support adding tags when
booting instances.

Implemetes: blueprint support-tag-instance-when-boot

Change-Id: Ifcaaf285c8f98a1d0e8bbbc87b2f57fbce057346
This commit is contained in:
Kevin_Zheng 2017-06-01 11:06:08 +08:00 committed by Matt Riedemann
parent 771dd5b121
commit b50b5a660e
26 changed files with 774 additions and 31 deletions

View File

@ -4963,6 +4963,28 @@ server_status:
in: body
required: true
type: string
server_tags_create:
description: |
A list of tags. Tags have the following restrictions:
- Tag is a Unicode bytestring no longer than 60 characters.
- Tag is a non-empty string.
- Tags are case sensitive.
- '/' is not allowed to be in a tag name
- Comma is not allowed to be in a tag name in order to simplify
requests that specify lists of tags
- All other characters are allowed to be in a tag name
- Each server can have up to 50 tags.
in: body
required: false
type: array
min_version: 2.52
server_usages:
description: |
A list of the server usage objects.

View File

@ -333,6 +333,7 @@ Request
- os:scheduler_hints: os:scheduler_hints
- OS-DCF:diskConfig: OS-DCF:diskConfig
- description: server_description
- tags: server_tags_create
**Example Create Server**

View File

@ -0,0 +1,31 @@
{
"server" : {
"accessIPv4": "1.2.3.4",
"accessIPv6": "80fe::",
"name" : "new-server-test",
"imageRef" : "70a599e0-31e7-49b7-b260-868f441e862b",
"flavorRef" : "http://openstack.example.com/flavors/1",
"availability_zone": "nova",
"OS-DCF:diskConfig": "AUTO",
"metadata" : {
"My Server Name" : "Apache1"
},
"personality": [
{
"path": "/etc/banner.txt",
"contents": "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp dCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5k IGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVs c2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4g QnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRo ZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlv dSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vy c2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6 b25zLiINCg0KLVJpY2hhcmQgQmFjaA=="
}
],
"security_groups": [
{
"name": "default"
}
],
"user_data" : "IyEvYmluL2Jhc2gKL2Jpbi9zdQplY2hvICJJIGFtIGluIHlvdSEiCg==",
"networks": "auto",
"tags": ["tag1", "tag2"]
},
"OS-SCH-HNT:scheduler_hints": {
"same_host": "48e6a9f6-30af-47e0-bc04-acaed113bb4e"
}
}

View File

@ -0,0 +1,22 @@
{
"server": {
"OS-DCF:diskConfig": "AUTO",
"adminPass": "S5wqy9sPYUvU",
"id": "97108291-2fd7-4dc2-a909-eaae0306a6a9",
"links": [
{
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/servers/97108291-2fd7-4dc2-a909-eaae0306a6a9",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/servers/97108291-2fd7-4dc2-a909-eaae0306a6a9",
"rel": "bookmark"
}
],
"security_groups": [
{
"name": "default"
}
]
}
}

View File

@ -0,0 +1,94 @@
{
"server": {
"OS-DCF:diskConfig": "AUTO",
"OS-EXT-AZ:availability_zone": "nova",
"OS-EXT-SRV-ATTR:host": "compute",
"OS-EXT-SRV-ATTR:hostname": "new-server-test",
"OS-EXT-SRV-ATTR:hypervisor_hostname": "fake-mini",
"OS-EXT-SRV-ATTR:instance_name": "instance-00000001",
"OS-EXT-SRV-ATTR:kernel_id": "",
"OS-EXT-SRV-ATTR:launch_index": 0,
"OS-EXT-SRV-ATTR:ramdisk_id": "",
"OS-EXT-SRV-ATTR:reservation_id": "r-ov3q80zj",
"OS-EXT-SRV-ATTR:root_device_name": "/dev/sda",
"OS-EXT-SRV-ATTR:user_data": "IyEvYmluL2Jhc2gKL2Jpbi9zdQplY2hvICJJIGFtIGluIHlvdSEiCg==",
"OS-EXT-STS:power_state": 1,
"OS-EXT-STS:task_state": null,
"OS-EXT-STS:vm_state": "active",
"OS-SRV-USG:launched_at": "2017-02-14T19:23:59.895661",
"OS-SRV-USG:terminated_at": null,
"accessIPv4": "1.2.3.4",
"accessIPv6": "80fe::",
"addresses": {
"private": [
{
"OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff",
"OS-EXT-IPS:type": "fixed",
"addr": "192.168.0.3",
"version": 4
}
]
},
"config_drive": "",
"created": "2017-02-14T19:23:58Z",
"description": null,
"flavor": {
"disk": 1,
"ephemeral": 0,
"extra_specs": {},
"original_name": "m1.tiny",
"ram": 512,
"swap": 0,
"vcpus": 1
},
"hostId": "2091634baaccdc4c5a1d57069c833e402921df696b7f970791b12ec6",
"host_status": "UP",
"id": "9168b536-cd40-4630-b43f-b259807c6e87",
"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.1/6f70656e737461636b20342065766572/servers/9168b536-cd40-4630-b43f-b259807c6e87",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/servers/9168b536-cd40-4630-b43f-b259807c6e87",
"rel": "bookmark"
}
],
"locked": false,
"metadata": {
"My Server Name": "Apache1"
},
"name": "new-server-test",
"os-extended-volumes:volumes_attached": [
{
"delete_on_termination": false,
"id": "volume_id1"
},
{
"delete_on_termination": false,
"id": "volume_id2"
}
],
"progress": 0,
"security_groups": [
{
"name": "default"
}
],
"status": "ACTIVE",
"tags": ["tag1", "tag2"],
"tenant_id": "6f70656e737461636b20342065766572",
"updated": "2017-02-14T19:24:00Z",
"user_id": "fake"
}
}

View File

@ -0,0 +1,96 @@
{
"servers": [
{
"OS-DCF:diskConfig": "AUTO",
"OS-EXT-AZ:availability_zone": "nova",
"OS-EXT-SRV-ATTR:host": "compute",
"OS-EXT-SRV-ATTR:hostname": "new-server-test",
"OS-EXT-SRV-ATTR:hypervisor_hostname": "fake-mini",
"OS-EXT-SRV-ATTR:instance_name": "instance-00000001",
"OS-EXT-SRV-ATTR:kernel_id": "",
"OS-EXT-SRV-ATTR:launch_index": 0,
"OS-EXT-SRV-ATTR:ramdisk_id": "",
"OS-EXT-SRV-ATTR:reservation_id": "r-iffothgx",
"OS-EXT-SRV-ATTR:root_device_name": "/dev/sda",
"OS-EXT-SRV-ATTR:user_data": "IyEvYmluL2Jhc2gKL2Jpbi9zdQplY2hvICJJIGFtIGluIHlvdSEiCg==",
"OS-EXT-STS:power_state": 1,
"OS-EXT-STS:task_state": null,
"OS-EXT-STS:vm_state": "active",
"OS-SRV-USG:launched_at": "2017-02-14T19:24:43.891568",
"OS-SRV-USG:terminated_at": null,
"accessIPv4": "1.2.3.4",
"accessIPv6": "80fe::",
"addresses": {
"private": [
{
"OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff",
"OS-EXT-IPS:type": "fixed",
"addr": "192.168.0.3",
"version": 4
}
]
},
"config_drive": "",
"created": "2017-02-14T19:24:42Z",
"description": null,
"flavor": {
"disk": 1,
"ephemeral": 0,
"extra_specs": {},
"original_name": "m1.tiny",
"ram": 512,
"swap": 0,
"vcpus": 1
},
"hostId": "2091634baaccdc4c5a1d57069c833e402921df696b7f970791b12ec6",
"host_status": "UP",
"id": "764e369e-a874-4401-b7ce-43e4760888da",
"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.1/6f70656e737461636b20342065766572/servers/764e369e-a874-4401-b7ce-43e4760888da",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/servers/764e369e-a874-4401-b7ce-43e4760888da",
"rel": "bookmark"
}
],
"locked": false,
"metadata": {
"My Server Name": "Apache1"
},
"name": "new-server-test",
"os-extended-volumes:volumes_attached": [
{
"delete_on_termination": false,
"id": "volume_id1"
},
{
"delete_on_termination": false,
"id": "volume_id2"
}
],
"progress": 0,
"security_groups": [
{
"name": "default"
}
],
"status": "ACTIVE",
"tags": ["tag1", "tag2"],
"tenant_id": "6f70656e737461636b20342065766572",
"updated": "2017-02-14T19:24:43Z",
"user_id": "fake"
}
]
}

View File

@ -0,0 +1,18 @@
{
"servers": [
{
"id": "6e3a87e6-a133-452e-86e1-a31291c1b1c8",
"links": [
{
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/servers/6e3a87e6-a133-452e-86e1-a31291c1b1c8",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/servers/6e3a87e6-a133-452e-86e1-a31291c1b1c8",
"rel": "bookmark"
}
],
"name": "new-server-test"
}
]
}

View File

@ -19,7 +19,7 @@
}
],
"status": "CURRENT",
"version": "2.51",
"version": "2.52",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}

View File

@ -22,7 +22,7 @@
}
],
"status": "CURRENT",
"version": "2.51",
"version": "2.52",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}

View File

@ -123,6 +123,7 @@ REST_API_VERSION_HISTORY = """REST API Version History:
* 2.51 - Adds new event name to external-events (volume-extended). Also,
non-admins can see instance action event details except for the
traceback field.
* 2.52 - Adds support for applying tags when creating a server.
"""
# The minimum and maximum versions of the API supported
@ -131,7 +132,7 @@ REST_API_VERSION_HISTORY = """REST API Version History:
# Note(cyeoh): This only applies for the v2.1 API once microversions
# support is fully merged. It does not affect the V2 API.
_MIN_API_VERSION = "2.1"
_MAX_API_VERSION = "2.51"
_MAX_API_VERSION = "2.52"
DEFAULT_API_VERSION = _MIN_API_VERSION
# Almost all proxy APIs which related to network, images and baremetal

View File

@ -227,6 +227,17 @@ user documentation.
A user can create, update, delete or check existence of simple string tags
for servers by the os-server-tags plugin.
Tags have the following schema restrictions:
* Tag is a Unicode bytestring no longer than 60 characters.
* Tag is a non-empty string.
* Tags are case sensitive.
* '/' is not allowed to be in a tag name
* Comma is not allowed to be in a tag name in order to simplify requests that
specify lists of tags
* All other characters are allowed to be in a tag name
* Each server can have up to 50 tags.
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
@ -610,3 +621,9 @@ user documentation.
useful for API users to monitor when a volume extend operation completes
for the given server instance. By default only users with the administrator
role will be able to see event ``traceback`` details.
2.52
----
Adds support for applying tags when creating a server. The tag schema is
the same as in the `2.26`_ microversion.

View File

@ -16,7 +16,7 @@ import copy
from nova.api.validation import parameter_types
from nova.api.validation.parameter_types import multi_params
from nova.objects import instance
base_create = {
'type': 'object',
@ -129,6 +129,16 @@ base_create_v242['properties']['server']['properties']['networks'] = {
]}
# 2.52 builds on 2.42 and makes the following changes:
# Allowing adding tags to instances when booting
base_create_v252 = copy.deepcopy(base_create_v242)
base_create_v252['properties']['server']['properties']['tags'] = {
"type": "array",
"items": parameter_types.tag,
"maxItems": instance.MAX_TAG_COUNT
}
base_update = {
'type': 'object',
'properties': {

View File

@ -83,6 +83,7 @@ class ServersController(wsgi.Controller):
schema_server_create_v232 = schema_servers.base_create_v232
schema_server_create_v237 = schema_servers.base_create_v237
schema_server_create_v242 = schema_servers.base_create_v242
schema_server_create_v252 = schema_servers.base_create_v252
# NOTE(alex_xu): Please do not add more items into this list. This list
# should be removed in the future.
@ -135,6 +136,7 @@ class ServersController(wsgi.Controller):
# TODO(alex_xu): The final goal is that merging all of
# extended json-schema into server main json-schema.
self._create_schema(self.schema_server_create_v252, '2.52')
self._create_schema(self.schema_server_create_v242, '2.42')
self._create_schema(self.schema_server_create_v237, '2.37')
self._create_schema(self.schema_server_create_v232, '2.32')
@ -446,7 +448,8 @@ class ServersController(wsgi.Controller):
@validation.schema(schema_server_create_v219, '2.19', '2.31')
@validation.schema(schema_server_create_v232, '2.32', '2.36')
@validation.schema(schema_server_create_v237, '2.37', '2.41')
@validation.schema(schema_server_create_v242, '2.42')
@validation.schema(schema_server_create_v242, '2.42', '2.51')
@validation.schema(schema_server_create_v252, '2.52')
def create(self, req, body):
"""Creates a new server for a given user."""
context = req.environ['nova.context']
@ -467,6 +470,9 @@ class ServersController(wsgi.Controller):
availability_zone = create_kwargs.pop("availability_zone", None)
if api_version_request.is_supported(req, min_version='2.52'):
create_kwargs['tags'] = server_dict.get('tags')
helpers.translate_attributes(helpers.CREATE,
server_dict, create_kwargs)

View File

@ -986,7 +986,7 @@ class API(base.Base):
max_count, base_options, boot_meta, security_groups,
block_device_mapping, shutdown_terminate,
instance_group, check_server_group_quota, filter_properties,
key_pair):
key_pair, tags):
# Reserve quotas
num_instances, quotas = self._check_num_instances_quota(
context, instance_type, min_count, max_count)
@ -1027,11 +1027,13 @@ class API(base.Base):
block_device_mapping = (
self._bdm_validate_set_size_and_instance(context,
instance, instance_type, block_device_mapping))
instance_tags = self._transform_tags(tags, instance.uuid)
build_request = objects.BuildRequest(context,
instance=instance, instance_uuid=instance.uuid,
project_id=instance.project_id,
block_device_mappings=block_device_mapping)
block_device_mappings=block_device_mapping,
tags=instance_tags)
build_request.create()
# Create an instance_mapping. The null cell_mapping indicates
@ -1179,7 +1181,7 @@ class API(base.Base):
requested_networks, config_drive,
block_device_mapping, auto_disk_config, filter_properties,
reservation_id=None, legacy_bdm=True, shutdown_terminate=False,
check_server_group_quota=False):
check_server_group_quota=False, tags=None):
"""Verify all the input parameters regardless of the provisioning
strategy being performed and schedule the instance(s) for
creation.
@ -1192,6 +1194,7 @@ class API(base.Base):
min_count = min_count or 1
max_count = max_count or min_count
block_device_mapping = block_device_mapping or []
tags = tags or []
if image_href:
image_id, boot_meta = self._get_image(context, image_href)
@ -1237,11 +1240,13 @@ class API(base.Base):
instance_group = self._get_requested_instance_group(context,
filter_properties)
instances_to_build = self._provision_instances(context, instance_type,
min_count, max_count, base_options, boot_meta, security_groups,
block_device_mapping, shutdown_terminate,
instance_group, check_server_group_quota, filter_properties,
key_pair)
tags = self._create_tag_list_obj(context, tags)
instances_to_build = self._provision_instances(
context, instance_type, min_count, max_count, base_options,
boot_meta, security_groups, block_device_mapping,
shutdown_terminate, instance_group, check_server_group_quota,
filter_properties, key_pair, tags)
instances = []
request_specs = []
@ -1276,7 +1281,8 @@ class API(base.Base):
admin_password=admin_password,
injected_files=injected_files,
requested_networks=requested_networks,
block_device_mapping=block_device_mapping)
block_device_mapping=block_device_mapping,
tags=tags)
return (instances, reservation_id)
@ -1583,6 +1589,33 @@ class API(base.Base):
return instance
def _create_tag_list_obj(self, context, tags):
"""Create TagList objects from simple string tags.
:param context: security context.
:param tags: simple string tags from API request.
:returns: TagList object.
"""
tag_list = [objects.Tag(context=context, tag=t) for t in tags]
tag_list_obj = objects.TagList(objects=tag_list)
return tag_list_obj
def _transform_tags(self, tags, resource_id):
"""Change the resource_id of the tags according to the input param.
Because this method can be called multiple times when more than one
instance is booted in a single request it makes a copy of the tags
list.
:param tags: TagList object.
:param resource_id: string.
:returns: TagList object.
"""
instance_tags = tags.obj_clone()
for tag in instance_tags:
tag.resource_id = resource_id
return instance_tags
# This method remains because cellsv1 uses it in the scheduler
def create_db_entry_for_new_instance(self, context, instance_type, image,
instance, security_group, block_device_mapping, num_instances,
@ -1640,7 +1673,7 @@ class API(base.Base):
access_ip_v4=None, access_ip_v6=None, requested_networks=None,
config_drive=None, auto_disk_config=None, scheduler_hints=None,
legacy_bdm=True, shutdown_terminate=False,
check_server_group_quota=False):
check_server_group_quota=False, tags=None):
"""Provision instances, sending instance information to the
scheduler. The scheduler will determine where the instance(s)
go and will handle creating the DB entries.
@ -1679,7 +1712,8 @@ class API(base.Base):
filter_properties=filter_properties,
legacy_bdm=legacy_bdm,
shutdown_terminate=shutdown_terminate,
check_server_group_quota=check_server_group_quota)
check_server_group_quota=check_server_group_quota,
tags=tags)
def _check_auto_disk_config(self, instance=None, image=None,
**extra_instance_updates):

View File

@ -126,11 +126,12 @@ class ComputeTaskAPI(object):
def schedule_and_build_instances(self, context, build_requests,
request_spec, image,
admin_password, injected_files,
requested_networks, block_device_mapping):
requested_networks, block_device_mapping,
tags=None):
self.conductor_compute_rpcapi.schedule_and_build_instances(
context, build_requests, request_spec, image,
admin_password, injected_files, requested_networks,
block_device_mapping)
block_device_mapping, tags)
def unshelve_instance(self, context, instance, request_spec=None):
self.conductor_compute_rpcapi.unshelve_instance(context,

View File

@ -295,6 +295,33 @@ class BuildRequestList(base.ObjectListBase, base.NovaObject):
if (k not in instance.metadata or
v != instance.metadata[k]):
return False
elif filter_key in (
'tags', 'tags-any', 'not-tags', 'not-tags-any'):
# Get the list of simple string tags first.
tags = ([tag.tag for tag in instance.tags]
if instance.tags else [])
if filter_key == 'tags':
for item in filter_val:
if item not in tags:
return False
elif filter_key == 'tags-any':
found = []
for item in filter_val:
if item in tags:
found.append(item)
if not found:
return False
elif filter_key == 'not-tags':
found = []
for item in filter_val:
if item in tags:
found.append(item)
if len(found) == len(filter_val):
return False
elif filter_key == 'not-tags-any':
for item in filter_val:
if item in tags:
return False
elif isinstance(filter_val, (list, tuple, set, frozenset)):
if not filter_val:
# Special value to indicate that nothing will match.
@ -364,10 +391,6 @@ class BuildRequestList(base.ObjectListBase, base.NovaObject):
build_requests = cls.get_all(context)
# Fortunately some filters do not apply here.
# 'tags' can not be applied at boot time so will not be set for an
# instance here.
# TODO(zhengzhenyu): Handle this when the API supports creating
# servers with tags.
# 'changes-since' works off of the updated_at field which has not yet
# been set at the point in the boot process where build_request still
# exists. So it can be ignored.
@ -381,7 +404,8 @@ class BuildRequestList(base.ObjectListBase, base.NovaObject):
exact_match_filter_names = ['project_id', 'user_id', 'image_ref',
'vm_state', 'instance_type_id', 'uuid',
'metadata', 'host', 'task_state',
'system_metadata']
'system_metadata', 'tags', 'tags-any',
'not-tags', 'not-tags-any']
exact_filters = {}
regex_filters = {}
for key, value in filters.items():

View File

@ -0,0 +1,31 @@
{
"server" : {
"accessIPv4": "%(access_ip_v4)s",
"accessIPv6": "%(access_ip_v6)s",
"name" : "new-server-test",
"imageRef" : "%(image_id)s",
"flavorRef" : "http://openstack.example.com/flavors/1",
"availability_zone": "nova",
"OS-DCF:diskConfig": "AUTO",
"metadata" : {
"My Server Name" : "Apache1"
},
"personality": [
{
"path": "/etc/banner.txt",
"contents": "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp dCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5k IGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVs c2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4g QnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRo ZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlv dSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vy c2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6 b25zLiINCg0KLVJpY2hhcmQgQmFjaA=="
}
],
"security_groups": [
{
"name": "default"
}
],
"user_data" : "%(user_data)s",
"networks": "auto",
"tags": ["tag1", "tag2"]
},
"OS-SCH-HNT:scheduler_hints": {
"same_host": "%(uuid)s"
}
}

View File

@ -0,0 +1,22 @@
{
"server": {
"OS-DCF:diskConfig": "AUTO",
"adminPass": "%(password)s",
"id": "%(id)s",
"links": [
{
"href": "%(versioned_compute_endpoint)s/servers/%(uuid)s",
"rel": "self"
},
{
"href": "%(compute_endpoint)s/servers/%(uuid)s",
"rel": "bookmark"
}
],
"security_groups": [
{
"name": "default"
}
]
}
}

View File

@ -0,0 +1,88 @@
{
"server": {
"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",
"description": null,
"host_status": "UP",
"locked": false,
"tags": ["tag1", "tag2"],
"flavor": {
"disk": 1,
"ephemeral": 0,
"extra_specs": {},
"original_name": "m1.tiny",
"ram": 512,
"swap": 0,
"vcpus": 1
},
"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/%(uuid)s",
"rel": "bookmark"
}
],
"metadata": {
"My Server Name": "Apache1"
},
"name": "new-server-test",
"config_drive": "%(cdrive)s",
"OS-DCF:diskConfig": "AUTO",
"OS-EXT-AZ:availability_zone": "nova",
"OS-EXT-SRV-ATTR:host": "%(compute_host)s",
"OS-EXT-SRV-ATTR:hostname": "%(hostname)s",
"OS-EXT-SRV-ATTR:hypervisor_hostname": "%(hypervisor_hostname)s",
"OS-EXT-SRV-ATTR:instance_name": "%(instance_name)s",
"OS-EXT-SRV-ATTR:kernel_id": "",
"OS-EXT-SRV-ATTR:launch_index": 0,
"OS-EXT-SRV-ATTR:ramdisk_id": "",
"OS-EXT-SRV-ATTR:reservation_id": "%(reservation_id)s",
"OS-EXT-SRV-ATTR:root_device_name": "/dev/sda",
"OS-EXT-SRV-ATTR:user_data": "%(user_data)s",
"OS-EXT-STS:power_state": 1,
"OS-EXT-STS:task_state": null,
"OS-EXT-STS:vm_state": "active",
"os-extended-volumes:volumes_attached": [
{"id": "volume_id1", "delete_on_termination": false},
{"id": "volume_id2", "delete_on_termination": false}
],
"OS-SRV-USG:launched_at": "%(strtime)s",
"OS-SRV-USG:terminated_at": null,
"progress": 0,
"security_groups": [
{
"name": "default"
}
],
"status": "ACTIVE",
"tenant_id": "6f70656e737461636b20342065766572",
"updated": "%(isotime)s",
"user_id": "fake"
}
}

View File

@ -0,0 +1,90 @@
{
"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",
"description": null,
"host_status": "UP",
"locked": false,
"tags": ["tag1", "tag2"],
"flavor": {
"disk": 1,
"ephemeral": 0,
"extra_specs": {},
"original_name": "m1.tiny",
"ram": 512,
"swap": 0,
"vcpus": 1
},
"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",
"config_drive": "%(cdrive)s",
"OS-DCF:diskConfig": "AUTO",
"OS-EXT-AZ:availability_zone": "nova",
"OS-EXT-SRV-ATTR:host": "%(compute_host)s",
"OS-EXT-SRV-ATTR:hostname": "%(hostname)s",
"OS-EXT-SRV-ATTR:hypervisor_hostname": "%(hypervisor_hostname)s",
"OS-EXT-SRV-ATTR:instance_name": "%(instance_name)s",
"OS-EXT-SRV-ATTR:kernel_id": "",
"OS-EXT-SRV-ATTR:launch_index": 0,
"OS-EXT-SRV-ATTR:ramdisk_id": "",
"OS-EXT-SRV-ATTR:reservation_id": "%(reservation_id)s",
"OS-EXT-SRV-ATTR:root_device_name": "/dev/sda",
"OS-EXT-SRV-ATTR:user_data": "%(user_data)s",
"OS-EXT-STS:power_state": 1,
"OS-EXT-STS:task_state": null,
"OS-EXT-STS:vm_state": "active",
"os-extended-volumes:volumes_attached": [
{"id": "volume_id1", "delete_on_termination": false},
{"id": "volume_id2", "delete_on_termination": false}
],
"OS-SRV-USG:launched_at": "%(strtime)s",
"OS-SRV-USG:terminated_at": null,
"progress": 0,
"security_groups": [
{
"name": "default"
}
],
"status": "ACTIVE",
"tenant_id": "6f70656e737461636b20342065766572",
"updated": "%(isotime)s",
"user_id": "fake"
}
]
}

View File

@ -0,0 +1,18 @@
{
"servers": [
{
"id": "%(id)s",
"links": [
{
"href": "%(versioned_compute_endpoint)s/servers/%(id)s",
"rel": "self"
},
{
"href": "%(compute_endpoint)s/servers/%(id)s",
"rel": "bookmark"
}
],
"name": "new-server-test"
}
]
}

View File

@ -230,6 +230,12 @@ class ServersSampleJson247Test(ServersSampleJsonTest):
self._verify_response('server-action-rebuild-resp', subs, resp, 202)
class ServersSampleJson252Test(ServersSampleJsonTest):
microversion = '2.52'
scenarios = [('v2_52', {'api_major_version': 'v2.1'})]
use_common_server_post = False
class ServersUpdateSampleJsonTest(ServersSampleBase):
def test_update_server(self):

View File

@ -183,3 +183,57 @@ class ServersPreSchedulingTestCase(test.TestCase):
def test_instance_list_from_buildrequests_old_service(self):
self._test_instance_list_from_buildrequests()
def test_instance_list_from_buildrequests_with_tags(self):
"""Creates two servers with two tags each, where the 2nd tag (tag2)
is the only intersection between the tags in both servers. This is
used to test the various tags filters working in the BuildRequestList.
"""
self.useFixture(nova_fixtures.AllServicesCurrent())
image_ref = fake_image.get_valid_image_id()
body = {
'server': {
'name': 'foo',
'imageRef': image_ref,
'flavorRef': '1',
'networks': 'none',
'tags': ['tag1', 'tag2']
}
}
inst1 = self.api.api_post('servers', body)
body['server']['name'] = 'bar'
body['server']['tags'] = ['tag2', 'tag3']
inst2 = self.api.api_post('servers', body)
# list servers using tags=tag1,tag2
list_resp = self.api.api_get(
'servers/detail?tags=tag1,tag2')
list_resp = list_resp.body['servers']
self.assertEqual(1, len(list_resp))
self.assertEqual(inst1.body['server']['id'], list_resp[0]['id'])
self.assertEqual('foo', list_resp[0]['name'])
# list servers using tags-any=tag1,tag3
list_resp = self.api.api_get(
'servers/detail?tags-any=tag1,tag3')
list_resp = list_resp.body['servers']
self.assertEqual(2, len(list_resp))
# Default sort is created_at desc, so last created is first
self.assertEqual(inst2.body['server']['id'], list_resp[0]['id'])
self.assertEqual('bar', list_resp[0]['name'])
self.assertEqual(inst1.body['server']['id'], list_resp[1]['id'])
self.assertEqual('foo', list_resp[1]['name'])
# list servers using not-tags=tag1,tag2
list_resp = self.api.api_get(
'servers/detail?not-tags=tag1,tag2')
list_resp = list_resp.body['servers']
self.assertEqual(1, len(list_resp))
self.assertEqual(inst2.body['server']['id'], list_resp[0]['id'])
self.assertEqual('bar', list_resp[0]['name'])
# list servers using not-tags-any=tag1,tag3
list_resp = self.api.api_get(
'servers/detail?not-tags-any=tag1,tag3')
list_resp = list_resp.body['servers']
self.assertEqual(0, len(list_resp))

View File

@ -16,6 +16,7 @@
import collections
import datetime
import ddt
import uuid
import fixtures
@ -56,6 +57,7 @@ from nova.image import glance
from nova.network import manager
from nova import objects
from nova.objects import instance as instance_obj
from nova.objects import tag
from nova.policies import servers as server_policies
from nova import policy
from nova import test
@ -3862,6 +3864,54 @@ class ServersControllerCreateTestV237(test.NoDBTestCase):
[{'uuid': uuid}])
@ddt.ddt
class ServersControllerCreateTestV252(test.NoDBTestCase):
def setUp(self):
super(ServersControllerCreateTestV252, self).setUp()
self.controller = servers.ServersController()
self.body = {
'server': {
'name': 'device-tagging-server',
'imageRef': '6b0edabb-8cde-4684-a3f4-978960a51378',
'flavorRef': '2',
'networks': [{
'uuid': 'ff608d40-75e9-48cb-b745-77bb55b5eaf2'
}]
}
}
self.req = fakes.HTTPRequestV21.blank('/fake/servers', version='2.52')
self.req.method = 'POST'
self.req.headers['content-type'] = 'application/json'
def _create_server(self, tags):
self.body['server']['tags'] = tags
self.req.body = jsonutils.dump_as_bytes(self.body)
return self.controller.create(self.req, body=self.body).obj['server']
def test_create_server_with_tags_pre_2_52_fails(self):
"""Negative test to make sure you can't pass 'tags' before 2.52"""
self.req.api_version_request = \
api_version_request.APIVersionRequest('2.51')
self.assertRaises(
exception.ValidationError, self._create_server, ['tag1'])
@ddt.data([','],
['/'],
['a' * (tag.MAX_TAG_LENGTH + 1)],
['a'] * (instance_obj.MAX_TAG_COUNT + 1),
[''],
[1, 2, 3],
{'tag': 'tag'})
def test_create_server_with_tags_incorrect_tags(self, tags):
"""Negative test to incorrect tags are not allowed"""
self.req.api_version_request = \
api_version_request.APIVersionRequest('2.52')
self.assertRaises(
exception.ValidationError, self._create_server, tags)
class ServersControllerCreateTestWithMock(test.TestCase):
image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
flavor_ref = 'http://localhost/123/flavors/3'

View File

@ -3643,7 +3643,7 @@ class _ComputeAPIUnitTestMixIn(object):
boot_meta, security_groups, block_device_mapping,
shutdown_terminate, instance_group,
check_server_group_quota, filter_properties,
None)
None, objects.TagList())
do_test()
@ -3692,7 +3692,8 @@ class _ComputeAPIUnitTestMixIn(object):
1, 1, mock.MagicMock(),
{}, None,
None, None, None, {}, None,
fake_keypair)
fake_keypair,
objects.TagList())
self.assertEqual(
'test',
mock_instance.return_value.keypairs.objects[0].name)
@ -3701,7 +3702,7 @@ class _ComputeAPIUnitTestMixIn(object):
1, 1, mock.MagicMock(),
{}, None,
None, None, None, {}, None,
None)
None, objects.TagList())
self.assertEqual(
0,
len(mock_instance.return_value.keypairs.objects))
@ -3764,6 +3765,7 @@ class _ComputeAPIUnitTestMixIn(object):
'device_name': 'vda',
'boot_index': 0,
}))])
instance_tags = objects.TagList(objects=[objects.Tag(tag='tag')])
shutdown_terminate = True
instance_group = None
check_server_group_quota = False
@ -3775,7 +3777,7 @@ class _ComputeAPIUnitTestMixIn(object):
min_count, max_count, base_options, boot_meta,
security_groups, block_device_mappings, shutdown_terminate,
instance_group, check_server_group_quota,
filter_properties, None)
filter_properties, None, instance_tags)
for rs, br, im in instances_to_build:
self.assertIsInstance(br.instance, objects.Instance)
@ -3783,6 +3785,7 @@ class _ComputeAPIUnitTestMixIn(object):
self.assertEqual(base_options['project_id'],
br.instance.project_id)
self.assertEqual(1, br.block_device_mappings[0].id)
self.assertEqual(br.instance.uuid, br.tags[0].resource_id)
br.create.assert_called_with()
do_test()
@ -3855,7 +3858,7 @@ class _ComputeAPIUnitTestMixIn(object):
min_count, max_count, base_options, boot_meta,
security_groups, block_device_mapping, shutdown_terminate,
instance_group, check_server_group_quota,
filter_properties, None))
filter_properties, None, objects.TagList()))
rs, br, im = instances_to_build[0]
self.assertTrue(uuidutils.is_uuid_like(br.instance.uuid))
self.assertEqual(br.instance_uuid, im.instance_uuid)
@ -3943,14 +3946,14 @@ class _ComputeAPIUnitTestMixIn(object):
check_server_group_quota = False
filter_properties = {'scheduler_hints': None,
'instance_type': flavor}
tags = objects.TagList()
self.assertRaises(exception.InvalidVolume,
self.compute_api._provision_instances, ctxt,
flavor, min_count, max_count, base_options,
boot_meta, security_groups, block_device_mapping,
shutdown_terminate, instance_group,
check_server_group_quota, filter_properties,
None)
None, tags)
# First instance, build_req, mapping is created and destroyed
self.assertTrue(build_req_mocks[0].create.called)
self.assertTrue(build_req_mocks[0].destroy.called)
@ -3982,7 +3985,7 @@ class _ComputeAPIUnitTestMixIn(object):
self.compute_api._provision_instances(ctxt, None, None, None,
mock.MagicMock(), None, None,
[], None, None, None, None,
None)
None, objects.TagList())
secgroups = mock_secgroup.populate_security_groups.return_value
mock_objects.RequestSpec.from_components.assert_called_once_with(
mock.ANY, mock.ANY, mock.ANY, mock.ANY, mock.ANY, mock.ANY,

View File

@ -0,0 +1,4 @@
---
features:
- Adds support for applying tags when creating a server.
The tag schema is the same as in the 2.26 microversion.