Microversion 2.64 - Use new format policy in server group
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`` 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. Part of blueprint: complex-anti-affinity-policies Change-Id: I6911e97bd7f8df92511e90518dba21c127e106a5
This commit is contained in:
parent
09f67d7789
commit
5cdb1ce26b
@ -38,13 +38,15 @@ Response
|
||||
- name: name_server_group
|
||||
- policies: policies
|
||||
- members: members
|
||||
- metadata: metadata_object
|
||||
- metadata: metadata_server_group_max_2_63
|
||||
- project_id: project_id_server_group
|
||||
- user_id: user_id_server_group
|
||||
- policy: policy_name
|
||||
- rules: policy_rules
|
||||
|
||||
**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
|
||||
|
||||
Create Server Group
|
||||
@ -56,7 +58,7 @@ Creates a server group.
|
||||
|
||||
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
|
||||
-------
|
||||
@ -66,10 +68,12 @@ Request
|
||||
- server_group: server_group
|
||||
- name: name_server_group
|
||||
- policies: policies
|
||||
- policy: policy_name
|
||||
- rules: policy_rules_optional
|
||||
|
||||
**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
|
||||
|
||||
Response
|
||||
@ -82,13 +86,15 @@ Response
|
||||
- name: name_server_group
|
||||
- policies: policies
|
||||
- members: members
|
||||
- metadata: metadata_object
|
||||
- metadata: metadata_server_group_max_2_63
|
||||
- project_id: project_id_server_group
|
||||
- user_id: user_id_server_group
|
||||
- policy: policy_name
|
||||
- rules: policy_rules
|
||||
|
||||
**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
|
||||
|
||||
Show Server Group Details
|
||||
@ -119,13 +125,15 @@ Response
|
||||
- name: name_server_group
|
||||
- policies: policies
|
||||
- members: members
|
||||
- metadata: metadata_object
|
||||
- metadata: metadata_server_group_max_2_63
|
||||
- project_id: project_id_server_group
|
||||
- user_id: user_id_server_group
|
||||
- policy: policy_name
|
||||
- rules: policy_rules
|
||||
|
||||
**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
|
||||
|
||||
Delete Server Group
|
||||
|
@ -4299,6 +4299,14 @@ metadata_object:
|
||||
in: body
|
||||
required: true
|
||||
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:
|
||||
description: |
|
||||
The action to cold migrate a server.
|
||||
@ -5111,6 +5119,50 @@ policies:
|
||||
in: body
|
||||
required: true
|
||||
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:
|
||||
description: |
|
||||
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",
|
||||
"version": "2.63",
|
||||
"version": "2.64",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z"
|
||||
}
|
||||
|
@ -22,7 +22,7 @@
|
||||
}
|
||||
],
|
||||
"status": "CURRENT",
|
||||
"version": "2.63",
|
||||
"version": "2.64",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z"
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
"anti-affinity"
|
||||
],
|
||||
"policy": "anti-affinity",
|
||||
"rules": {},
|
||||
"rules": {"max_server_per_host": "3"},
|
||||
"members": [],
|
||||
"hosts": null
|
||||
}
|
||||
|
@ -150,6 +150,12 @@ REST_API_VERSION_HISTORY = """REST API Version History:
|
||||
responses.
|
||||
* 2.63 - Add support for applying trusted certificates when creating or
|
||||
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
|
||||
@ -158,7 +164,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.63"
|
||||
_MAX_API_VERSION = "2.64"
|
||||
DEFAULT_API_VERSION = _MIN_API_VERSION
|
||||
|
||||
# Almost all proxy APIs which are related to network, images and baremetal
|
||||
|
@ -818,3 +818,21 @@ the following APIs:
|
||||
* ``GET /servers/{server_id}``
|
||||
* ``PUT /servers/{server_id}``
|
||||
* ``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['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 = {
|
||||
'type': 'object',
|
||||
|
@ -31,6 +31,7 @@ from nova import context as nova_context
|
||||
import nova.exception
|
||||
from nova.i18n import _
|
||||
from nova import objects
|
||||
from nova.objects import service
|
||||
from nova.policies import server_groups as sg_policies
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -38,6 +39,9 @@ LOG = logging.getLogger(__name__)
|
||||
CONF = nova.conf.CONF
|
||||
|
||||
|
||||
GROUP_POLICY_OBJ_MICROVERSION = "2.64"
|
||||
|
||||
|
||||
def _authorize_context(req, action):
|
||||
context = req.environ['nova.context']
|
||||
context.can(sg_policies.POLICY_ROOT % action)
|
||||
@ -78,6 +82,15 @@ def _get_not_deleted(context, 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):
|
||||
"""The Server group API controller for the OpenStack API."""
|
||||
|
||||
@ -89,10 +102,15 @@ class ServerGroupController(wsgi.Controller):
|
||||
server_group = {}
|
||||
server_group['id'] = group.uuid
|
||||
server_group['name'] = group.name
|
||||
server_group['policies'] = group.policies or []
|
||||
# NOTE(danms): This has been exposed to the user, but never used.
|
||||
# Since we can't remove it, just make sure it's always empty.
|
||||
server_group['metadata'] = {}
|
||||
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 []
|
||||
# NOTE(yikun): Before v2.64, a empty metadata is exposed to the
|
||||
# user, and it is removed since v2.64.
|
||||
server_group['metadata'] = {}
|
||||
members = []
|
||||
if group.members:
|
||||
# Display the instances that are not deleted.
|
||||
@ -146,9 +164,10 @@ class ServerGroupController(wsgi.Controller):
|
||||
return {'server_groups': result}
|
||||
|
||||
@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_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):
|
||||
"""Creates a new server group."""
|
||||
context = _authorize_context(req, 'create')
|
||||
@ -161,13 +180,28 @@ class ServerGroupController(wsgi.Controller):
|
||||
raise exc.HTTPForbidden(explanation=msg)
|
||||
|
||||
vals = body['server_group']
|
||||
sg = objects.InstanceGroup(context)
|
||||
sg.project_id = context.project_id
|
||||
sg.user_id = context.user_id
|
||||
|
||||
if api_version_request.is_supported(
|
||||
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:
|
||||
sg.name = vals.get('name')
|
||||
policies = vals.get('policies')
|
||||
sg.policy = policies[0]
|
||||
sg.project_id = context.project_id
|
||||
sg.user_id = context.user_id
|
||||
sg.create()
|
||||
except ValueError as 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):
|
||||
super(ServerGroupsV213SampleJsonTest, self).setUp()
|
||||
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):
|
||||
group_req = {
|
||||
"name": "test-server-group",
|
||||
"policies": ["anti-affinity"]}
|
||||
"policy": "anti-affinity",
|
||||
"rules": {"max_server_per_host": 3}
|
||||
}
|
||||
group = self.api.post_server_groups(group_req)
|
||||
|
||||
self.assertEqual(1, len(fake_notifier.VERSIONED_NOTIFICATIONS))
|
||||
@ -48,7 +50,9 @@ class TestServerGroupNotificationSample(
|
||||
def test_server_group_add_member(self):
|
||||
group_req = {
|
||||
"name": "test-server-group",
|
||||
"policies": ["anti-affinity"]}
|
||||
"policy": "anti-affinity",
|
||||
"rules": {"max_server_per_host": 3}
|
||||
}
|
||||
group = self.api.post_server_groups(group_req)
|
||||
fake_notifier.reset()
|
||||
|
||||
|
@ -93,8 +93,9 @@ class ServerGroupTestBase(test.TestCase,
|
||||
|
||||
def _boot_a_server_to_group(self, group,
|
||||
expected_status='ACTIVE', flavor=None):
|
||||
server = self._build_minimal_create_server_request(self.api,
|
||||
'some-server')
|
||||
server = self._build_minimal_create_server_request(
|
||||
self.api, 'some-server',
|
||||
image_uuid='a2459075-d96c-40d5-893e-577ff92e721c', networks=[])
|
||||
if flavor:
|
||||
server['flavorRef'] = ('http://fake.server/%s'
|
||||
% flavor['id'])
|
||||
@ -686,6 +687,11 @@ class ServerGroupTestV215(ServerGroupTestV21):
|
||||
|
||||
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):
|
||||
groups = [self.anti_affinity,
|
||||
self.affinity,
|
||||
@ -698,9 +704,8 @@ class ServerGroupTestV215(ServerGroupTestV21):
|
||||
created_group = self.api.post_server_groups(group)
|
||||
created_groups.append(created_group)
|
||||
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['metadata'])
|
||||
self.assertIn('id', created_group)
|
||||
|
||||
group_details = self.api.get_server_group(created_group['id'])
|
||||
@ -845,3 +850,47 @@ class ServerGroupTestV215(ServerGroupTestV21):
|
||||
|
||||
def test_soft_affinity_not_supported(self):
|
||||
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
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
import mock
|
||||
from oslo_utils import uuidutils
|
||||
import six
|
||||
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 import context
|
||||
from nova import exception
|
||||
@ -43,13 +46,14 @@ def server_group_template(**kwargs):
|
||||
def server_group_resp_template(**kwargs):
|
||||
sgroup = kwargs.copy()
|
||||
sgroup.setdefault('name', 'test')
|
||||
sgroup.setdefault('policies', [])
|
||||
if 'policy' not in kwargs:
|
||||
sgroup.setdefault('policies', [])
|
||||
sgroup.setdefault('members', [])
|
||||
return sgroup
|
||||
|
||||
|
||||
def server_group_db(sg):
|
||||
attrs = sg.copy()
|
||||
attrs = copy.deepcopy(sg)
|
||||
if 'id' in attrs:
|
||||
attrs['uuid'] = attrs.pop('id')
|
||||
if 'policies' in attrs:
|
||||
@ -57,6 +61,8 @@ def server_group_db(sg):
|
||||
attrs['policies'] = policies
|
||||
else:
|
||||
attrs['policies'] = []
|
||||
if 'policy' in attrs:
|
||||
del attrs['policies']
|
||||
if 'members' in attrs:
|
||||
members = attrs.pop('members')
|
||||
attrs['members'] = members
|
||||
@ -111,7 +117,8 @@ class ServerGroupTestV21(test.NoDBTestCase):
|
||||
self.assertRaises(self.validation_error, self.controller.create,
|
||||
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['policies'] = policies
|
||||
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.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):
|
||||
policies = ['affinity', 'anti-affinity']
|
||||
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):
|
||||
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,
|
||||
path, api_version='2.1', limited=None):
|
||||
policies = ['anti-affinity']
|
||||
policy = "anti-affinity"
|
||||
members = []
|
||||
metadata = {} # always empty
|
||||
names = ['default-x', 'test']
|
||||
p_id = fakes.FAKE_PROJECT_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,
|
||||
name=names[0],
|
||||
policies=policies,
|
||||
@ -664,3 +711,140 @@ class ServerGroupTestV213(ServerGroupTestV21):
|
||||
|
||||
def test_list_server_group_by_tenant(self):
|
||||
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
Block a user