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:
Yikun Jiang 2018-05-10 17:19:42 +08:00
parent 09f67d7789
commit 5cdb1ce26b
22 changed files with 520 additions and 34 deletions

View File

@ -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

View File

@ -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

View File

@ -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"
}
}

View File

@ -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"
}
]
}

View File

@ -0,0 +1,7 @@
{
"server_group": {
"name": "test",
"policy": "anti-affinity",
"rules": {"max_server_per_host": 3}
}
}

View File

@ -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"
}
}

View File

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

View File

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

View File

@ -11,7 +11,7 @@
"anti-affinity"
],
"policy": "anti-affinity",
"rules": {},
"rules": {"max_server_per_host": "3"},
"members": [],
"hosts": null
}

View File

@ -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

View File

@ -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.

View File

@ -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',

View File

@ -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)

View File

@ -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"
}
}

View File

@ -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"
}
]
}

View File

@ -0,0 +1,7 @@
{
"server_group": {
"name": "%(name)s",
"policy": "anti-affinity",
"rules": {"max_server_per_host": 3}
}
}

View File

@ -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"
}
}

View File

@ -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

View File

@ -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()

View File

@ -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))

View File

@ -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})

View File

@ -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.