Merge "Microversion 2.64 - Use new format policy in server group"
This commit is contained in:
commit
ae40af621f
|
@ -38,13 +38,15 @@ Response
|
||||||
- name: name_server_group
|
- name: name_server_group
|
||||||
- policies: policies
|
- policies: policies
|
||||||
- members: members
|
- members: members
|
||||||
- metadata: metadata_object
|
- metadata: metadata_server_group_max_2_63
|
||||||
- project_id: project_id_server_group
|
- project_id: project_id_server_group
|
||||||
- user_id: user_id_server_group
|
- user_id: user_id_server_group
|
||||||
|
- policy: policy_name
|
||||||
|
- rules: policy_rules
|
||||||
|
|
||||||
**Example List Server Groups: JSON response**
|
**Example List Server Groups: JSON response**
|
||||||
|
|
||||||
.. literalinclude:: ../../doc/api_samples/os-server-groups/server-groups-list-resp.json
|
.. literalinclude:: ../../doc/api_samples/os-server-groups/v2.64/server-groups-list-resp.json
|
||||||
:language: javascript
|
:language: javascript
|
||||||
|
|
||||||
Create Server Group
|
Create Server Group
|
||||||
|
@ -56,7 +58,7 @@ Creates a server group.
|
||||||
|
|
||||||
Normal response codes: 200
|
Normal response codes: 200
|
||||||
|
|
||||||
Error response codes: badRequest(400), unauthorized(401), forbidden(403)
|
Error response codes: badRequest(400), unauthorized(401), forbidden(403), conflict(409)
|
||||||
|
|
||||||
Request
|
Request
|
||||||
-------
|
-------
|
||||||
|
@ -66,10 +68,12 @@ Request
|
||||||
- server_group: server_group
|
- server_group: server_group
|
||||||
- name: name_server_group
|
- name: name_server_group
|
||||||
- policies: policies
|
- policies: policies
|
||||||
|
- policy: policy_name
|
||||||
|
- rules: policy_rules_optional
|
||||||
|
|
||||||
**Example Create Server Group: JSON request**
|
**Example Create Server Group: JSON request**
|
||||||
|
|
||||||
.. literalinclude:: ../../doc/api_samples/os-server-groups/server-groups-post-req.json
|
.. literalinclude:: ../../doc/api_samples/os-server-groups/v2.64/server-groups-post-req.json
|
||||||
:language: javascript
|
:language: javascript
|
||||||
|
|
||||||
Response
|
Response
|
||||||
|
@ -82,13 +86,15 @@ Response
|
||||||
- name: name_server_group
|
- name: name_server_group
|
||||||
- policies: policies
|
- policies: policies
|
||||||
- members: members
|
- members: members
|
||||||
- metadata: metadata_object
|
- metadata: metadata_server_group_max_2_63
|
||||||
- project_id: project_id_server_group
|
- project_id: project_id_server_group
|
||||||
- user_id: user_id_server_group
|
- user_id: user_id_server_group
|
||||||
|
- policy: policy_name
|
||||||
|
- rules: policy_rules
|
||||||
|
|
||||||
**Example Create Server Group: JSON response**
|
**Example Create Server Group: JSON response**
|
||||||
|
|
||||||
.. literalinclude:: ../../doc/api_samples/os-server-groups/server-groups-post-resp.json
|
.. literalinclude:: ../../doc/api_samples/os-server-groups/v2.64/server-groups-post-resp.json
|
||||||
:language: javascript
|
:language: javascript
|
||||||
|
|
||||||
Show Server Group Details
|
Show Server Group Details
|
||||||
|
@ -119,13 +125,15 @@ Response
|
||||||
- name: name_server_group
|
- name: name_server_group
|
||||||
- policies: policies
|
- policies: policies
|
||||||
- members: members
|
- members: members
|
||||||
- metadata: metadata_object
|
- metadata: metadata_server_group_max_2_63
|
||||||
- project_id: project_id_server_group
|
- project_id: project_id_server_group
|
||||||
- user_id: user_id_server_group
|
- user_id: user_id_server_group
|
||||||
|
- policy: policy_name
|
||||||
|
- rules: policy_rules
|
||||||
|
|
||||||
**Example Show Server Group Details: JSON response**
|
**Example Show Server Group Details: JSON response**
|
||||||
|
|
||||||
.. literalinclude:: ../../doc/api_samples/os-server-groups/server-groups-get-resp.json
|
.. literalinclude:: ../../doc/api_samples/os-server-groups/v2.64/server-groups-get-resp.json
|
||||||
:language: javascript
|
:language: javascript
|
||||||
|
|
||||||
Delete Server Group
|
Delete Server Group
|
||||||
|
|
|
@ -4299,6 +4299,14 @@ metadata_object:
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
type: object
|
type: object
|
||||||
|
metadata_server_group_max_2_63:
|
||||||
|
description: |
|
||||||
|
Metadata key and value pairs. The maximum size for each metadata key and value
|
||||||
|
pair is 255 bytes. It's always empty and only used for keeping compatibility.
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
type: object
|
||||||
|
max_version: 2.63
|
||||||
migrate:
|
migrate:
|
||||||
description: |
|
description: |
|
||||||
The action to cold migrate a server.
|
The action to cold migrate a server.
|
||||||
|
@ -5111,6 +5119,50 @@ policies:
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
type: array
|
type: array
|
||||||
|
max_version: 2.63
|
||||||
|
policy_name:
|
||||||
|
description: |
|
||||||
|
The ``policy`` field represents the name of the policy. The current
|
||||||
|
valid policy names are:
|
||||||
|
|
||||||
|
- ``anti-affinity`` - servers in this group must be scheduled to
|
||||||
|
different hosts.
|
||||||
|
- ``affinity`` - servers in this group must be scheduled to the same host.
|
||||||
|
- ``soft-anti-affinity`` - servers in this group should be scheduled to
|
||||||
|
different hosts if possible, but if not possible then they should still
|
||||||
|
be scheduled instead of resulting in a build failure.
|
||||||
|
- ``soft-affinity`` - servers in this group should be scheduled to the same
|
||||||
|
host if possible, but if not possible then they should still be scheduled
|
||||||
|
instead of resulting in a build failure.
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
type: object
|
||||||
|
min_version: 2.64
|
||||||
|
policy_rules:
|
||||||
|
description: |
|
||||||
|
The ``rules`` field, which is a dict, can be applied to the policy.
|
||||||
|
Currently, only the ``max_server_per_host`` rule is supported for the
|
||||||
|
``anti-affinity`` policy. The ``max_server_per_host`` rule allows
|
||||||
|
specifying how many members of the anti-affinity group can reside on the
|
||||||
|
same compute host. If not specified, only one member from the same
|
||||||
|
anti-affinity group can reside on a given host.
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
type: object
|
||||||
|
min_version: 2.64
|
||||||
|
policy_rules_optional:
|
||||||
|
description: |
|
||||||
|
The ``rules`` field, which is a dict, can be applied to the policy.
|
||||||
|
Currently, only the ``max_server_per_host`` rule is supported for the
|
||||||
|
``anti-affinity`` policy. The ``max_server_per_host`` rule allows
|
||||||
|
specifying how many members of the anti-affinity group can reside on the
|
||||||
|
same compute host. If not specified, only one member from the same
|
||||||
|
anti-affinity group can reside on a given host. Requesting policy rules
|
||||||
|
with any other policy than ``anti-affinity`` will be 400.
|
||||||
|
in: body
|
||||||
|
required: false
|
||||||
|
type: object
|
||||||
|
min_version: 2.64
|
||||||
pool:
|
pool:
|
||||||
description: |
|
description: |
|
||||||
Pool from which to allocate the IP address. If you omit this parameter, the call
|
Pool from which to allocate the IP address. If you omit this parameter, the call
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"server_group": {
|
||||||
|
"id": "5bbcc3c4-1da2-4437-a48a-66f15b1b13f9",
|
||||||
|
"name": "test",
|
||||||
|
"policy": "anti-affinity",
|
||||||
|
"rules": {"max_server_per_host": 3},
|
||||||
|
"members": [],
|
||||||
|
"project_id": "6f70656e737461636b20342065766572",
|
||||||
|
"user_id": "fake"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"server_groups": [
|
||||||
|
{
|
||||||
|
"id": "616fb98f-46ca-475e-917e-2563e5a8cd19",
|
||||||
|
"name": "test",
|
||||||
|
"policy": "anti-affinity",
|
||||||
|
"rules": {"max_server_per_host": 3},
|
||||||
|
"members": [],
|
||||||
|
"project_id": "6f70656e737461636b20342065766572",
|
||||||
|
"user_id": "fake"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"server_group": {
|
||||||
|
"name": "test",
|
||||||
|
"policy": "anti-affinity",
|
||||||
|
"rules": {"max_server_per_host": 3}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"server_group": {
|
||||||
|
"id": "5bbcc3c4-1da2-4437-a48a-66f15b1b13f9",
|
||||||
|
"name": "test",
|
||||||
|
"policy": "anti-affinity",
|
||||||
|
"rules": {"max_server_per_host": 3},
|
||||||
|
"members": [],
|
||||||
|
"project_id": "6f70656e737461636b20342065766572",
|
||||||
|
"user_id": "fake"
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,7 +19,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"status": "CURRENT",
|
"status": "CURRENT",
|
||||||
"version": "2.63",
|
"version": "2.64",
|
||||||
"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.63",
|
"version": "2.64",
|
||||||
"min_version": "2.1",
|
"min_version": "2.1",
|
||||||
"updated": "2013-07-23T11:33:21Z"
|
"updated": "2013-07-23T11:33:21Z"
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
"anti-affinity"
|
"anti-affinity"
|
||||||
],
|
],
|
||||||
"policy": "anti-affinity",
|
"policy": "anti-affinity",
|
||||||
"rules": {},
|
"rules": {"max_server_per_host": "3"},
|
||||||
"members": [],
|
"members": [],
|
||||||
"hosts": null
|
"hosts": null
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,6 +150,12 @@ REST_API_VERSION_HISTORY = """REST API Version History:
|
||||||
responses.
|
responses.
|
||||||
* 2.63 - Add support for applying trusted certificates when creating or
|
* 2.63 - Add support for applying trusted certificates when creating or
|
||||||
rebuilding a server.
|
rebuilding a server.
|
||||||
|
* 2.64 - Add support for the "max_server_per_host" policy rule for
|
||||||
|
``anti-affinity`` server group policy, the ``policies`` and
|
||||||
|
``metadata`` fields are removed and the ``policy`` (required)
|
||||||
|
and ``rules`` (optional) fields are added in response body of
|
||||||
|
GET, POST /os-server-groups APIs and GET
|
||||||
|
/os-server-groups/{group_id} API.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# The minimum and maximum versions of the API supported
|
# The minimum and maximum versions of the API supported
|
||||||
|
@ -158,7 +164,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.63"
|
_MAX_API_VERSION = "2.64"
|
||||||
DEFAULT_API_VERSION = _MIN_API_VERSION
|
DEFAULT_API_VERSION = _MIN_API_VERSION
|
||||||
|
|
||||||
# Almost all proxy APIs which are related to network, images and baremetal
|
# Almost all proxy APIs which are related to network, images and baremetal
|
||||||
|
|
|
@ -818,3 +818,21 @@ the following APIs:
|
||||||
* ``GET /servers/{server_id}``
|
* ``GET /servers/{server_id}``
|
||||||
* ``PUT /servers/{server_id}``
|
* ``PUT /servers/{server_id}``
|
||||||
* ``POST /servers/{server_id}/action (rebuild)``
|
* ``POST /servers/{server_id}/action (rebuild)``
|
||||||
|
|
||||||
|
2.64
|
||||||
|
----
|
||||||
|
|
||||||
|
Enable users to define the policy rules on server group policy to meet more
|
||||||
|
advanced policy requirement. This microversion brings the following changes
|
||||||
|
in server group APIs:
|
||||||
|
|
||||||
|
* Add ``policy`` and ``rules`` fields in the request of POST
|
||||||
|
``/os-server-groups``. The ``policy`` represents the name of policy. The
|
||||||
|
``rules`` field, which is a dict, can be applied to the policy, which
|
||||||
|
currently only support ``max_server_per_host`` for ``anti-affinity`` policy.
|
||||||
|
* The ``policy`` and ``rules`` fields will be returned in response
|
||||||
|
body of POST, GET ``/os-server-groups`` API and GET
|
||||||
|
``/os-server-groups/{server_group_id}`` API.
|
||||||
|
* The ``policies`` and ``metadata`` fields have been removed from the response
|
||||||
|
body of POST, GET ``/os-server-groups`` API and GET
|
||||||
|
``/os-server-groups/{server_group_id}`` API.
|
|
@ -51,6 +51,25 @@ create_v215 = copy.deepcopy(create)
|
||||||
policies = create_v215['properties']['server_group']['properties']['policies']
|
policies = create_v215['properties']['server_group']['properties']['policies']
|
||||||
policies['items'][0]['enum'].extend(['soft-anti-affinity', 'soft-affinity'])
|
policies['items'][0]['enum'].extend(['soft-anti-affinity', 'soft-affinity'])
|
||||||
|
|
||||||
|
create_v264 = copy.deepcopy(create_v215)
|
||||||
|
del create_v264['properties']['server_group']['properties']['policies']
|
||||||
|
sg_properties = create_v264['properties']['server_group']
|
||||||
|
sg_properties['required'].remove('policies')
|
||||||
|
sg_properties['required'].append('policy')
|
||||||
|
sg_properties['properties']['policy'] = {
|
||||||
|
'type': 'string',
|
||||||
|
'enum': ['anti-affinity', 'affinity',
|
||||||
|
'soft-anti-affinity', 'soft-affinity'],
|
||||||
|
}
|
||||||
|
|
||||||
|
sg_properties['properties']['rules'] = {
|
||||||
|
'type': 'object',
|
||||||
|
'properties': {
|
||||||
|
'max_server_per_host':
|
||||||
|
parameter_types.positive_integer,
|
||||||
|
},
|
||||||
|
'additionalProperties': False,
|
||||||
|
}
|
||||||
|
|
||||||
server_groups_query_param = {
|
server_groups_query_param = {
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
|
|
|
@ -31,6 +31,7 @@ from nova import context as nova_context
|
||||||
import nova.exception
|
import nova.exception
|
||||||
from nova.i18n import _
|
from nova.i18n import _
|
||||||
from nova import objects
|
from nova import objects
|
||||||
|
from nova.objects import service
|
||||||
from nova.policies import server_groups as sg_policies
|
from nova.policies import server_groups as sg_policies
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
@ -38,6 +39,9 @@ LOG = logging.getLogger(__name__)
|
||||||
CONF = nova.conf.CONF
|
CONF = nova.conf.CONF
|
||||||
|
|
||||||
|
|
||||||
|
GROUP_POLICY_OBJ_MICROVERSION = "2.64"
|
||||||
|
|
||||||
|
|
||||||
def _authorize_context(req, action):
|
def _authorize_context(req, action):
|
||||||
context = req.environ['nova.context']
|
context = req.environ['nova.context']
|
||||||
context.can(sg_policies.POLICY_ROOT % action)
|
context.can(sg_policies.POLICY_ROOT % action)
|
||||||
|
@ -78,6 +82,15 @@ def _get_not_deleted(context, uuids):
|
||||||
return found_inst_uuids
|
return found_inst_uuids
|
||||||
|
|
||||||
|
|
||||||
|
def _should_enable_custom_max_server_rules(context, rules):
|
||||||
|
if rules and int(rules.get('max_server_per_host', 1)) > 1:
|
||||||
|
minver = service.get_minimum_version_all_cells(
|
||||||
|
context, ['nova-compute'])
|
||||||
|
if minver < 33:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class ServerGroupController(wsgi.Controller):
|
class ServerGroupController(wsgi.Controller):
|
||||||
"""The Server group API controller for the OpenStack API."""
|
"""The Server group API controller for the OpenStack API."""
|
||||||
|
|
||||||
|
@ -89,9 +102,14 @@ class ServerGroupController(wsgi.Controller):
|
||||||
server_group = {}
|
server_group = {}
|
||||||
server_group['id'] = group.uuid
|
server_group['id'] = group.uuid
|
||||||
server_group['name'] = group.name
|
server_group['name'] = group.name
|
||||||
|
if api_version_request.is_supported(
|
||||||
|
req, min_version=GROUP_POLICY_OBJ_MICROVERSION):
|
||||||
|
server_group['policy'] = group.policy
|
||||||
|
server_group['rules'] = group.rules
|
||||||
|
else:
|
||||||
server_group['policies'] = group.policies or []
|
server_group['policies'] = group.policies or []
|
||||||
# NOTE(danms): This has been exposed to the user, but never used.
|
# NOTE(yikun): Before v2.64, a empty metadata is exposed to the
|
||||||
# Since we can't remove it, just make sure it's always empty.
|
# user, and it is removed since v2.64.
|
||||||
server_group['metadata'] = {}
|
server_group['metadata'] = {}
|
||||||
members = []
|
members = []
|
||||||
if group.members:
|
if group.members:
|
||||||
|
@ -146,9 +164,10 @@ class ServerGroupController(wsgi.Controller):
|
||||||
return {'server_groups': result}
|
return {'server_groups': result}
|
||||||
|
|
||||||
@wsgi.Controller.api_version("2.1")
|
@wsgi.Controller.api_version("2.1")
|
||||||
@wsgi.expected_errors((400, 403))
|
@wsgi.expected_errors((400, 403, 409))
|
||||||
@validation.schema(schema.create, "2.0", "2.14")
|
@validation.schema(schema.create, "2.0", "2.14")
|
||||||
@validation.schema(schema.create_v215, "2.15")
|
@validation.schema(schema.create_v215, "2.15", "2.63")
|
||||||
|
@validation.schema(schema.create_v264, GROUP_POLICY_OBJ_MICROVERSION)
|
||||||
def create(self, req, body):
|
def create(self, req, body):
|
||||||
"""Creates a new server group."""
|
"""Creates a new server group."""
|
||||||
context = _authorize_context(req, 'create')
|
context = _authorize_context(req, 'create')
|
||||||
|
@ -161,13 +180,28 @@ class ServerGroupController(wsgi.Controller):
|
||||||
raise exc.HTTPForbidden(explanation=msg)
|
raise exc.HTTPForbidden(explanation=msg)
|
||||||
|
|
||||||
vals = body['server_group']
|
vals = body['server_group']
|
||||||
sg = objects.InstanceGroup(context)
|
|
||||||
sg.project_id = context.project_id
|
if api_version_request.is_supported(
|
||||||
sg.user_id = context.user_id
|
req, GROUP_POLICY_OBJ_MICROVERSION):
|
||||||
|
policy = vals['policy']
|
||||||
|
rules = vals.get('rules', {})
|
||||||
|
if policy != 'anti-affinity' and rules:
|
||||||
|
msg = _("Only anti-affinity policy supports rules.")
|
||||||
|
raise exc.HTTPBadRequest(explanation=msg)
|
||||||
|
# NOTE(yikun): This should be removed in Stein version.
|
||||||
|
if not _should_enable_custom_max_server_rules(context, rules):
|
||||||
|
msg = _("Creating an anti-affinity group with rule "
|
||||||
|
"max_server_per_host > 1 is not yet supported.")
|
||||||
|
raise exc.HTTPConflict(explanation=msg)
|
||||||
|
sg = objects.InstanceGroup(context, policy=policy,
|
||||||
|
rules=rules)
|
||||||
|
else:
|
||||||
|
policies = vals.get('policies')
|
||||||
|
sg = objects.InstanceGroup(context, policy=policies[0])
|
||||||
try:
|
try:
|
||||||
sg.name = vals.get('name')
|
sg.name = vals.get('name')
|
||||||
policies = vals.get('policies')
|
sg.project_id = context.project_id
|
||||||
sg.policy = policies[0]
|
sg.user_id = context.user_id
|
||||||
sg.create()
|
sg.create()
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise exc.HTTPBadRequest(explanation=e)
|
raise exc.HTTPBadRequest(explanation=e)
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"server_group": {
|
||||||
|
"id": "%(id)s",
|
||||||
|
"name": "%(name)s",
|
||||||
|
"policy": "anti-affinity",
|
||||||
|
"rules": {"max_server_per_host": 3},
|
||||||
|
"members": [],
|
||||||
|
"project_id": "6f70656e737461636b20342065766572",
|
||||||
|
"user_id": "fake"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"server_groups": [
|
||||||
|
{
|
||||||
|
"id": "%(id)s",
|
||||||
|
"name": "test",
|
||||||
|
"policy": "anti-affinity",
|
||||||
|
"rules": {"max_server_per_host": 3},
|
||||||
|
"members": [],
|
||||||
|
"project_id": "6f70656e737461636b20342065766572",
|
||||||
|
"user_id": "fake"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"server_group": {
|
||||||
|
"name": "%(name)s",
|
||||||
|
"policy": "anti-affinity",
|
||||||
|
"rules": {"max_server_per_host": 3}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"server_group": {
|
||||||
|
"id": "%(id)s",
|
||||||
|
"name": "%(name)s",
|
||||||
|
"policy": "anti-affinity",
|
||||||
|
"rules": {"max_server_per_host": 3},
|
||||||
|
"members": [],
|
||||||
|
"project_id": "6f70656e737461636b20342065766572",
|
||||||
|
"user_id": "fake"
|
||||||
|
}
|
||||||
|
}
|
|
@ -68,3 +68,13 @@ class ServerGroupsV213SampleJsonTest(ServerGroupsSampleJsonTest):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(ServerGroupsV213SampleJsonTest, self).setUp()
|
super(ServerGroupsV213SampleJsonTest, self).setUp()
|
||||||
self.api.microversion = self.microversion
|
self.api.microversion = self.microversion
|
||||||
|
|
||||||
|
|
||||||
|
class ServerGroupsV264SampleJsonTest(ServerGroupsV213SampleJsonTest):
|
||||||
|
scenarios = [
|
||||||
|
("v2_64", {'api_major_version': 'v2.1', 'microversion': '2.64'})
|
||||||
|
]
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(ServerGroupsV264SampleJsonTest, self).setUp()
|
||||||
|
self.api.microversion = self.microversion
|
||||||
|
|
|
@ -27,7 +27,9 @@ class TestServerGroupNotificationSample(
|
||||||
def test_server_group_create_delete(self):
|
def test_server_group_create_delete(self):
|
||||||
group_req = {
|
group_req = {
|
||||||
"name": "test-server-group",
|
"name": "test-server-group",
|
||||||
"policies": ["anti-affinity"]}
|
"policy": "anti-affinity",
|
||||||
|
"rules": {"max_server_per_host": 3}
|
||||||
|
}
|
||||||
group = self.api.post_server_groups(group_req)
|
group = self.api.post_server_groups(group_req)
|
||||||
|
|
||||||
self.assertEqual(1, len(fake_notifier.VERSIONED_NOTIFICATIONS))
|
self.assertEqual(1, len(fake_notifier.VERSIONED_NOTIFICATIONS))
|
||||||
|
@ -48,7 +50,9 @@ class TestServerGroupNotificationSample(
|
||||||
def test_server_group_add_member(self):
|
def test_server_group_add_member(self):
|
||||||
group_req = {
|
group_req = {
|
||||||
"name": "test-server-group",
|
"name": "test-server-group",
|
||||||
"policies": ["anti-affinity"]}
|
"policy": "anti-affinity",
|
||||||
|
"rules": {"max_server_per_host": 3}
|
||||||
|
}
|
||||||
group = self.api.post_server_groups(group_req)
|
group = self.api.post_server_groups(group_req)
|
||||||
fake_notifier.reset()
|
fake_notifier.reset()
|
||||||
|
|
||||||
|
|
|
@ -93,8 +93,9 @@ class ServerGroupTestBase(test.TestCase,
|
||||||
|
|
||||||
def _boot_a_server_to_group(self, group,
|
def _boot_a_server_to_group(self, group,
|
||||||
expected_status='ACTIVE', flavor=None):
|
expected_status='ACTIVE', flavor=None):
|
||||||
server = self._build_minimal_create_server_request(self.api,
|
server = self._build_minimal_create_server_request(
|
||||||
'some-server')
|
self.api, 'some-server',
|
||||||
|
image_uuid='a2459075-d96c-40d5-893e-577ff92e721c', networks=[])
|
||||||
if flavor:
|
if flavor:
|
||||||
server['flavorRef'] = ('http://fake.server/%s'
|
server['flavorRef'] = ('http://fake.server/%s'
|
||||||
% flavor['id'])
|
% flavor['id'])
|
||||||
|
@ -686,6 +687,11 @@ class ServerGroupTestV215(ServerGroupTestV21):
|
||||||
|
|
||||||
host.start()
|
host.start()
|
||||||
|
|
||||||
|
def _check_group_format(self, group, created_group):
|
||||||
|
self.assertEqual(group['policies'], created_group['policies'])
|
||||||
|
self.assertEqual({}, created_group['metadata'])
|
||||||
|
self.assertNotIn('rules', created_group)
|
||||||
|
|
||||||
def test_create_and_delete_groups(self):
|
def test_create_and_delete_groups(self):
|
||||||
groups = [self.anti_affinity,
|
groups = [self.anti_affinity,
|
||||||
self.affinity,
|
self.affinity,
|
||||||
|
@ -698,9 +704,8 @@ class ServerGroupTestV215(ServerGroupTestV21):
|
||||||
created_group = self.api.post_server_groups(group)
|
created_group = self.api.post_server_groups(group)
|
||||||
created_groups.append(created_group)
|
created_groups.append(created_group)
|
||||||
self.assertEqual(group['name'], created_group['name'])
|
self.assertEqual(group['name'], created_group['name'])
|
||||||
self.assertEqual(group['policies'], created_group['policies'])
|
self._check_group_format(group, created_group)
|
||||||
self.assertEqual([], created_group['members'])
|
self.assertEqual([], created_group['members'])
|
||||||
self.assertEqual({}, created_group['metadata'])
|
|
||||||
self.assertIn('id', created_group)
|
self.assertIn('id', created_group)
|
||||||
|
|
||||||
group_details = self.api.get_server_group(created_group['id'])
|
group_details = self.api.get_server_group(created_group['id'])
|
||||||
|
@ -845,3 +850,47 @@ class ServerGroupTestV215(ServerGroupTestV21):
|
||||||
|
|
||||||
def test_soft_affinity_not_supported(self):
|
def test_soft_affinity_not_supported(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ServerGroupTestV264(ServerGroupTestV215):
|
||||||
|
api_major_version = 'v2.1'
|
||||||
|
microversion = '2.64'
|
||||||
|
anti_affinity = {'name': 'fake-name-1', 'policy': 'anti-affinity'}
|
||||||
|
affinity = {'name': 'fake-name-2', 'policy': 'affinity'}
|
||||||
|
soft_anti_affinity = {'name': 'fake-name-3',
|
||||||
|
'policy': 'soft-anti-affinity'}
|
||||||
|
soft_affinity = {'name': 'fake-name-4', 'policy': 'soft-affinity'}
|
||||||
|
|
||||||
|
def _check_group_format(self, group, created_group):
|
||||||
|
self.assertEqual(group['policy'], created_group['policy'])
|
||||||
|
self.assertEqual(group.get('rules', {}), created_group['rules'])
|
||||||
|
self.assertNotIn('metadata', created_group)
|
||||||
|
self.assertNotIn('policies', created_group)
|
||||||
|
|
||||||
|
def test_boot_server_with_anti_affinity_rules(self):
|
||||||
|
anti_affinity_max_2 = {
|
||||||
|
'name': 'fake-name-1',
|
||||||
|
'policy': 'anti-affinity',
|
||||||
|
'rules': {'max_server_per_host': 2}
|
||||||
|
}
|
||||||
|
created_group = self.api.post_server_groups(anti_affinity_max_2)
|
||||||
|
servers1st = self._boot_servers_to_group(created_group)
|
||||||
|
servers2nd = self._boot_servers_to_group(created_group)
|
||||||
|
|
||||||
|
# We have 2 computes so the fifth server won't fit into the same group
|
||||||
|
failed_server = self._boot_a_server_to_group(created_group,
|
||||||
|
expected_status='ERROR')
|
||||||
|
self.assertEqual('No valid host was found. '
|
||||||
|
'There are not enough hosts available.',
|
||||||
|
failed_server['fault']['message'])
|
||||||
|
|
||||||
|
hosts = map(lambda x: x['OS-EXT-SRV-ATTR:host'],
|
||||||
|
servers1st + servers2nd)
|
||||||
|
hosts = [h for h in hosts]
|
||||||
|
# 4 servers
|
||||||
|
self.assertEqual(4, len(hosts))
|
||||||
|
# schedule to 2 host
|
||||||
|
self.assertEqual(2, len(set(hosts)))
|
||||||
|
# each host has 2 servers
|
||||||
|
for host in set(hosts):
|
||||||
|
self.assertEqual(2, hosts.count(host))
|
||||||
|
|
|
@ -13,10 +13,13 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import copy
|
||||||
import mock
|
import mock
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
|
import six
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
|
from nova.api.openstack import api_version_request as avr
|
||||||
from nova.api.openstack.compute import server_groups as sg_v21
|
from nova.api.openstack.compute import server_groups as sg_v21
|
||||||
from nova import context
|
from nova import context
|
||||||
from nova import exception
|
from nova import exception
|
||||||
|
@ -43,13 +46,14 @@ def server_group_template(**kwargs):
|
||||||
def server_group_resp_template(**kwargs):
|
def server_group_resp_template(**kwargs):
|
||||||
sgroup = kwargs.copy()
|
sgroup = kwargs.copy()
|
||||||
sgroup.setdefault('name', 'test')
|
sgroup.setdefault('name', 'test')
|
||||||
|
if 'policy' not in kwargs:
|
||||||
sgroup.setdefault('policies', [])
|
sgroup.setdefault('policies', [])
|
||||||
sgroup.setdefault('members', [])
|
sgroup.setdefault('members', [])
|
||||||
return sgroup
|
return sgroup
|
||||||
|
|
||||||
|
|
||||||
def server_group_db(sg):
|
def server_group_db(sg):
|
||||||
attrs = sg.copy()
|
attrs = copy.deepcopy(sg)
|
||||||
if 'id' in attrs:
|
if 'id' in attrs:
|
||||||
attrs['uuid'] = attrs.pop('id')
|
attrs['uuid'] = attrs.pop('id')
|
||||||
if 'policies' in attrs:
|
if 'policies' in attrs:
|
||||||
|
@ -57,6 +61,8 @@ def server_group_db(sg):
|
||||||
attrs['policies'] = policies
|
attrs['policies'] = policies
|
||||||
else:
|
else:
|
||||||
attrs['policies'] = []
|
attrs['policies'] = []
|
||||||
|
if 'policy' in attrs:
|
||||||
|
del attrs['policies']
|
||||||
if 'members' in attrs:
|
if 'members' in attrs:
|
||||||
members = attrs.pop('members')
|
members = attrs.pop('members')
|
||||||
attrs['members'] = members
|
attrs['members'] = members
|
||||||
|
@ -111,7 +117,8 @@ class ServerGroupTestV21(test.NoDBTestCase):
|
||||||
self.assertRaises(self.validation_error, self.controller.create,
|
self.assertRaises(self.validation_error, self.controller.create,
|
||||||
self.req, body={'server_group': sgroup})
|
self.req, body={'server_group': sgroup})
|
||||||
|
|
||||||
def _create_server_group_normal(self, policies):
|
def _create_server_group_normal(self, policies=None, policy=None,
|
||||||
|
rules=None):
|
||||||
sgroup = server_group_template()
|
sgroup = server_group_template()
|
||||||
sgroup['policies'] = policies
|
sgroup['policies'] = policies
|
||||||
res_dict = self.controller.create(self.req,
|
res_dict = self.controller.create(self.req,
|
||||||
|
@ -120,10 +127,33 @@ class ServerGroupTestV21(test.NoDBTestCase):
|
||||||
self.assertTrue(uuidutils.is_uuid_like(res_dict['server_group']['id']))
|
self.assertTrue(uuidutils.is_uuid_like(res_dict['server_group']['id']))
|
||||||
self.assertEqual(res_dict['server_group']['policies'], policies)
|
self.assertEqual(res_dict['server_group']['policies'], policies)
|
||||||
|
|
||||||
|
def test_create_server_group_with_new_policy_before_264(self):
|
||||||
|
req = fakes.HTTPRequest.blank('', version='2.63')
|
||||||
|
policy = 'anti-affinity'
|
||||||
|
rules = {'max_server_per_host': 3}
|
||||||
|
# 'policy' isn't an acceptable request key before 2.64
|
||||||
|
sgroup = server_group_template(policy=policy)
|
||||||
|
result = self.assertRaises(
|
||||||
|
self.validation_error, self.controller.create,
|
||||||
|
req, body={'server_group': sgroup})
|
||||||
|
self.assertIn(
|
||||||
|
"Invalid input for field/attribute server_group",
|
||||||
|
six.text_type(result)
|
||||||
|
)
|
||||||
|
# 'rules' isn't an acceptable request key before 2.64
|
||||||
|
sgroup = server_group_template(rules=rules)
|
||||||
|
result = self.assertRaises(
|
||||||
|
self.validation_error, self.controller.create,
|
||||||
|
req, body={'server_group': sgroup})
|
||||||
|
self.assertIn(
|
||||||
|
"Invalid input for field/attribute server_group",
|
||||||
|
six.text_type(result)
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_server_group(self):
|
def test_create_server_group(self):
|
||||||
policies = ['affinity', 'anti-affinity']
|
policies = ['affinity', 'anti-affinity']
|
||||||
for policy in policies:
|
for policy in policies:
|
||||||
self._create_server_group_normal([policy])
|
self._create_server_group_normal(policies=[policy])
|
||||||
|
|
||||||
def test_create_server_group_rbac_default(self):
|
def test_create_server_group_rbac_default(self):
|
||||||
sgroup = server_group_template()
|
sgroup = server_group_template()
|
||||||
|
@ -204,12 +234,29 @@ class ServerGroupTestV21(test.NoDBTestCase):
|
||||||
def _test_list_server_group(self, mock_get_all, mock_get_by_project,
|
def _test_list_server_group(self, mock_get_all, mock_get_by_project,
|
||||||
path, api_version='2.1', limited=None):
|
path, api_version='2.1', limited=None):
|
||||||
policies = ['anti-affinity']
|
policies = ['anti-affinity']
|
||||||
|
policy = "anti-affinity"
|
||||||
members = []
|
members = []
|
||||||
metadata = {} # always empty
|
metadata = {} # always empty
|
||||||
names = ['default-x', 'test']
|
names = ['default-x', 'test']
|
||||||
p_id = fakes.FAKE_PROJECT_ID
|
p_id = fakes.FAKE_PROJECT_ID
|
||||||
u_id = fakes.FAKE_USER_ID
|
u_id = fakes.FAKE_USER_ID
|
||||||
if api_version >= '2.13':
|
ver = avr.APIVersionRequest(api_version)
|
||||||
|
if ver >= avr.APIVersionRequest("2.64"):
|
||||||
|
sg1 = server_group_resp_template(id=uuidsentinel.sg1_id,
|
||||||
|
name=names[0],
|
||||||
|
policy=policy,
|
||||||
|
rules={},
|
||||||
|
members=members,
|
||||||
|
project_id=p_id,
|
||||||
|
user_id=u_id)
|
||||||
|
sg2 = server_group_resp_template(id=uuidsentinel.sg2_id,
|
||||||
|
name=names[1],
|
||||||
|
policy=policy,
|
||||||
|
rules={},
|
||||||
|
members=members,
|
||||||
|
project_id=p_id,
|
||||||
|
user_id=u_id)
|
||||||
|
elif ver >= avr.APIVersionRequest("2.13"):
|
||||||
sg1 = server_group_resp_template(id=uuidsentinel.sg1_id,
|
sg1 = server_group_resp_template(id=uuidsentinel.sg1_id,
|
||||||
name=names[0],
|
name=names[0],
|
||||||
policies=policies,
|
policies=policies,
|
||||||
|
@ -664,3 +711,140 @@ class ServerGroupTestV213(ServerGroupTestV21):
|
||||||
|
|
||||||
def test_list_server_group_by_tenant(self):
|
def test_list_server_group_by_tenant(self):
|
||||||
self._test_list_server_group_by_tenant(api_version='2.13')
|
self._test_list_server_group_by_tenant(api_version='2.13')
|
||||||
|
|
||||||
|
|
||||||
|
class ServerGroupTestV264(ServerGroupTestV213):
|
||||||
|
wsgi_api_version = '2.64'
|
||||||
|
|
||||||
|
def _setup_controller(self):
|
||||||
|
self.controller = sg_v21.ServerGroupController()
|
||||||
|
|
||||||
|
def _create_server_group_normal(self, policies=None, policy=None,
|
||||||
|
rules=None):
|
||||||
|
req = fakes.HTTPRequest.blank('', version=self.wsgi_api_version)
|
||||||
|
sgroup = server_group_template()
|
||||||
|
sgroup['rules'] = rules or {}
|
||||||
|
sgroup['policy'] = policy
|
||||||
|
res_dict = self.controller.create(req,
|
||||||
|
body={'server_group': sgroup})
|
||||||
|
self.assertEqual(res_dict['server_group']['name'], 'test')
|
||||||
|
self.assertTrue(uuidutils.is_uuid_like(res_dict['server_group']['id']))
|
||||||
|
self.assertEqual(res_dict['server_group']['policy'], policy)
|
||||||
|
self.assertEqual(res_dict['server_group']['rules'], rules or {})
|
||||||
|
return res_dict['server_group']['id']
|
||||||
|
|
||||||
|
def test_list_server_group_all(self):
|
||||||
|
self._test_list_server_group_all(api_version=self.wsgi_api_version)
|
||||||
|
|
||||||
|
def test_create_and_show_server_group(self):
|
||||||
|
policies = ['affinity', 'anti-affinity']
|
||||||
|
for policy in policies:
|
||||||
|
g_uuid = self._create_server_group_normal(
|
||||||
|
policy=policy)
|
||||||
|
res_dict = self._display_server_group(g_uuid)
|
||||||
|
self.assertEqual(res_dict['server_group']['policy'], policy)
|
||||||
|
self.assertEqual(res_dict['server_group']['rules'], {})
|
||||||
|
|
||||||
|
def _display_server_group(self, uuid):
|
||||||
|
req = fakes.HTTPRequest.blank('', version=self.wsgi_api_version)
|
||||||
|
group = self.controller.show(req, uuid)
|
||||||
|
return group
|
||||||
|
|
||||||
|
@mock.patch('nova.objects.service.get_minimum_version_all_cells',
|
||||||
|
return_value=33)
|
||||||
|
def test_create_and_show_server_group_with_rules(self, mock_get_v):
|
||||||
|
policy = 'anti-affinity'
|
||||||
|
rules = {'max_server_per_host': 3}
|
||||||
|
g_uuid = self._create_server_group_normal(
|
||||||
|
policy=policy, rules=rules)
|
||||||
|
res_dict = self._display_server_group(g_uuid)
|
||||||
|
self.assertEqual(res_dict['server_group']['policy'], policy)
|
||||||
|
self.assertEqual(res_dict['server_group']['rules'], rules)
|
||||||
|
|
||||||
|
def test_create_affinity_server_group_with_invalid_policy(self):
|
||||||
|
req = fakes.HTTPRequest.blank('', version=self.wsgi_api_version)
|
||||||
|
sgroup = server_group_template(policy='affinity',
|
||||||
|
rules={'max_server_per_host': 3})
|
||||||
|
result = self.assertRaises(webob.exc.HTTPBadRequest,
|
||||||
|
self.controller.create, req, body={'server_group': sgroup})
|
||||||
|
self.assertIn("Only anti-affinity policy supports rules",
|
||||||
|
six.text_type(result))
|
||||||
|
|
||||||
|
def test_create_anti_affinity_server_group_with_invalid_rules(self):
|
||||||
|
req = fakes.HTTPRequest.blank('', version=self.wsgi_api_version)
|
||||||
|
# A negative test for key is unknown, the value is not positive
|
||||||
|
# and not integer
|
||||||
|
invalid_rules = [{'unknown_key': '3'},
|
||||||
|
{'max_server_per_host': 0},
|
||||||
|
{'max_server_per_host': 'foo'}]
|
||||||
|
|
||||||
|
for r in invalid_rules:
|
||||||
|
sgroup = server_group_template(policy='anti-affinity', rules=r)
|
||||||
|
result = self.assertRaises(
|
||||||
|
self.validation_error, self.controller.create,
|
||||||
|
req, body={'server_group': sgroup})
|
||||||
|
self.assertIn(
|
||||||
|
"Invalid input for field/attribute", six.text_type(result)
|
||||||
|
)
|
||||||
|
|
||||||
|
@mock.patch('nova.objects.service.get_minimum_version_all_cells',
|
||||||
|
return_value=32)
|
||||||
|
def test_create_server_group_with_low_version_compute_service(self,
|
||||||
|
mock_get_v):
|
||||||
|
req = fakes.HTTPRequest.blank('', version=self.wsgi_api_version)
|
||||||
|
sgroup = server_group_template(policy='anti-affinity',
|
||||||
|
rules={'max_server_per_host': 3})
|
||||||
|
result = self.assertRaises(
|
||||||
|
webob.exc.HTTPConflict,
|
||||||
|
self.controller.create, req, body={'server_group': sgroup})
|
||||||
|
self.assertIn("Creating an anti-affinity group with rule "
|
||||||
|
"max_server_per_host > 1 is not yet supported.",
|
||||||
|
six.text_type(result))
|
||||||
|
|
||||||
|
def test_create_server_group(self):
|
||||||
|
policies = ['affinity', 'anti-affinity']
|
||||||
|
for policy in policies:
|
||||||
|
self._create_server_group_normal(policy=policy)
|
||||||
|
|
||||||
|
def test_policies_since_264(self):
|
||||||
|
req = fakes.HTTPRequest.blank('', version=self.wsgi_api_version)
|
||||||
|
# 'policies' isn't allowed in request >= 2.64
|
||||||
|
sgroup = server_group_template(policies=['anti-affinity'])
|
||||||
|
self.assertRaises(
|
||||||
|
self.validation_error, self.controller.create,
|
||||||
|
req, body={'server_group': sgroup})
|
||||||
|
|
||||||
|
def test_create_server_group_without_policy(self):
|
||||||
|
req = fakes.HTTPRequest.blank('', version=self.wsgi_api_version)
|
||||||
|
# 'policy' is required request key in request >= 2.64
|
||||||
|
sgroup = server_group_template()
|
||||||
|
self.assertRaises(self.validation_error, self.controller.create,
|
||||||
|
req, body={'server_group': sgroup})
|
||||||
|
|
||||||
|
def test_create_server_group_with_illegal_policies(self):
|
||||||
|
req = fakes.HTTPRequest.blank('', version=self.wsgi_api_version)
|
||||||
|
# blank policy
|
||||||
|
sgroup = server_group_template(policy='')
|
||||||
|
self.assertRaises(self.validation_error, self.controller.create,
|
||||||
|
req, body={'server_group': sgroup})
|
||||||
|
|
||||||
|
# policy as integer
|
||||||
|
sgroup = server_group_template(policy=7)
|
||||||
|
self.assertRaises(self.validation_error, self.controller.create,
|
||||||
|
req, body={'server_group': sgroup})
|
||||||
|
|
||||||
|
# policy as string
|
||||||
|
sgroup = server_group_template(policy='invalid')
|
||||||
|
self.assertRaises(self.validation_error, self.controller.create,
|
||||||
|
req, body={'server_group': sgroup})
|
||||||
|
|
||||||
|
# policy as None
|
||||||
|
sgroup = server_group_template(policy=None)
|
||||||
|
self.assertRaises(self.validation_error, self.controller.create,
|
||||||
|
req, body={'server_group': sgroup})
|
||||||
|
|
||||||
|
def test_additional_params(self):
|
||||||
|
req = fakes.HTTPRequest.blank('', version=self.wsgi_api_version)
|
||||||
|
sgroup = server_group_template(unknown='unknown')
|
||||||
|
self.assertRaises(self.validation_error, self.controller.create,
|
||||||
|
req, body={'server_group': sgroup})
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Enable users to define the policy rules on server group policy to meet
|
||||||
|
more advanced policy requirement. This microversion 2.64 brings the
|
||||||
|
following changes in server group APIs:
|
||||||
|
|
||||||
|
* Add ``policy`` and ``rules`` fields in the request of POST
|
||||||
|
``/os-server-groups``. The ``policy`` represents the name of policy. The
|
||||||
|
``rules`` field, which is a dict, can be applied to the policy, which
|
||||||
|
currently only supports ``max_server_per_host`` for ``anti-affinity``
|
||||||
|
policy.
|
||||||
|
* The ``policy`` and ``rules`` fields will be returned in response
|
||||||
|
body of POST, GET ``/os-server-groups`` API and GET
|
||||||
|
``/os-server-groups/{server_group_id}`` API.
|
||||||
|
* The ``policies`` and ``metadata`` fields have been removed from the
|
||||||
|
response body of POST, GET ``/os-server-groups`` API and GET
|
||||||
|
``/os-server-groups/{server_group_id}`` API.
|
Loading…
Reference in New Issue