Merge "Microversion 2.64 - Use new format policy in server group"

This commit is contained in:
Zuul 2018-07-16 15:41:56 +00:00 committed by Gerrit Code Review
commit ae40af621f
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.