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
|
||||
- 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…
x
Reference in New Issue
Block a user