Add REST API for instance group api extension

Support the Creation, Read, Delete, and List of server groups.

Refactored the code to use objects (https://review.openstack.org/#/c/38979/
Renamed from "instance group" to "server group".

Implements: blueprint instance-group-api-extension

Change-Id: I650a8f191dea5eab5b4b1828f0b9f65e33edea2a
This commit is contained in:
Debo Dutta 2013-08-15 02:39:17 -07:00 committed by Xinyuan Huang
parent c08cc38b81
commit aeda1f6e64
25 changed files with 864 additions and 0 deletions

View File

@ -432,6 +432,14 @@
"namespace": "http://docs.openstack.org/compute/ext/server-external-events/api/v2",
"updated": "2014-02-18T00:00:00-00:00"
},
{
"alias": "os-server-groups",
"description": "Server group support.",
"links": [],
"name": "ServerGroups",
"namespace": "http://docs.openstack.org/compute/ext/servergroups/api/v2",
"updated": "2013-06-01T00:00:00+00:00"
},
{
"alias": "os-instance_usage_audit_log",
"description": "Admin-only Task Log Monitoring.",

View File

@ -182,6 +182,9 @@
<extension alias="os-server-external-events" updated="2014-02-18T00:00:00-00:00" namespace="http://docs.openstack.org/compute/ext/server-external-events/api/v2" name="ServerExternalEvents">
<description>Server External Event Triggers.</description>
</extension>
<extension alias="os-server-groups" updated="2013-06-12T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/servergroups/api/v2" name="ServerGroups">
<description>Server group support.</description>
</extension>
<extension alias="os-instance_usage_audit_log" updated="2012-07-06T01:00:00+00:00" namespace="http://docs.openstack.org/ext/services/api/v1.1" name="OSInstanceUsageAuditLog">
<description>Admin-only Task Log Monitoring.</description>
</extension>

View File

@ -0,0 +1,9 @@
{
"server_group": {
"id": "5bbcc3c4-1da2-4437-a48a-66f15b1b13f9",
"name": "test",
"policies": ["test_policy"],
"members": [],
"metadata": {}
}
}

View File

@ -0,0 +1,8 @@
<?xml version='1.0' encoding='UTF-8'?>
<server_group xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1" id="5bbcc3c4-1da2-4437-a48a-66f15b1b13f9" name="test">
<policies>
<policy>test_policy</policy>
</policies>
<members/>
<metadata/>
</server_group>

View File

@ -0,0 +1,11 @@
{
"server_groups": [
{
"id": "616fb98f-46ca-475e-917e-2563e5a8cd19",
"name": "test",
"policies": ["test_policy"],
"members": [],
"metadata": {}
}
]
}

View File

@ -0,0 +1,10 @@
<?xml version='1.0' encoding='UTF-8'?>
<server_groups xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1">
<server_group id="5bbcc3c4-1da2-4437-a48a-66f15b1b13f9" name="test">
<policies>
<policy>test_policy</policy>
</policies>
<members/>
<metadata/>
</server_group>
</server_groups>

View File

@ -0,0 +1,6 @@
{
"server_group": {
"name": "test",
"policies": ["test_policy"]
}
}

View File

@ -0,0 +1,5 @@
<server_group name="test">
<policies>
<policy>test_policy</policy>
</policies>
</server_group>

View File

@ -0,0 +1,9 @@
{
"server_group": {
"id": "5bbcc3c4-1da2-4437-a48a-66f15b1b13f9",
"name": "test",
"policies": ["test_policy"],
"members": [],
"metadata": {}
}
}

View File

@ -0,0 +1,8 @@
<?xml version='1.0' encoding='UTF-8'?>
<server_group xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1" id="5bbcc3c4-1da2-4437-a48a-66f15b1b13f9" name="test">
<policies>
<policy>test_policy</policy>
</policies>
<members/>
<metadata/>
</server_group>

View File

@ -211,6 +211,7 @@
"compute_extension:server_diagnostics": "rule:admin_api",
"compute_extension:v3:os-server-diagnostics": "rule:admin_api",
"compute_extension:v3:os-server-diagnostics:discoverable": "",
"compute_extension:server_groups": "",
"compute_extension:server_password": "",
"compute_extension:v3:os-server-password": "",
"compute_extension:v3:os-server-password:discoverable": "",

View File

@ -0,0 +1,263 @@
# Copyright (c) 2014 Cisco Systems, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""The Server Group API Extension."""
import webob
from webob import exc
from nova.api.openstack import common
from nova.api.openstack import extensions
from nova.api.openstack import wsgi
from nova.api.openstack import xmlutil
import nova.exception
from nova.objects import instance_group as instance_group_obj
from nova.openstack.common.gettextutils import _
from nova.openstack.common import log as logging
from nova import utils
LOG = logging.getLogger(__name__)
authorize = extensions.extension_authorizer('compute', 'server_groups')
def make_policy(elem):
elem.text = str
def make_member(elem):
elem.text = str
def make_group(elem):
elem.set('name')
elem.set('id')
policies = xmlutil.SubTemplateElement(elem, 'policies')
policy = xmlutil.SubTemplateElement(policies, 'policy',
selector='policies')
make_policy(policy)
members = xmlutil.SubTemplateElement(elem, 'members')
member = xmlutil.SubTemplateElement(members, 'member',
selector='members')
make_member(member)
elem.append(common.MetadataTemplate())
server_group_nsmap = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM}
def _authorize_context(req):
context = req.environ['nova.context']
authorize(context)
return context
class ServerGroupTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('server_group',
selector='server_group')
make_group(root)
return xmlutil.MasterTemplate(root, 1, nsmap=server_group_nsmap)
class ServerGroupsTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('server_groups')
elem = xmlutil.SubTemplateElement(root, 'server_group',
selector='server_groups')
# Note: listing server groups only shows name and uuid
make_group(elem)
return xmlutil.MasterTemplate(root, 1, nsmap=server_group_nsmap)
class ServerGroupXMLDeserializer(wsgi.MetadataXMLDeserializer):
"""Deserializer to handle xml-formatted server group requests."""
metadata_deserializer = common.MetadataXMLDeserializer()
def default(self, string):
"""Deserialize an xml-formatted server group create request."""
dom = xmlutil.safe_minidom_parse_string(string)
server_group = self._extract_server_group(dom)
return {'body': {'server_group': server_group}}
def _extract_server_group(self, node):
"""Marshal the instance attribute of a parsed request."""
server_group = {}
sg_node = self.find_first_child_named(node, 'server_group')
if sg_node is not None:
if sg_node.hasAttribute('name'):
server_group['name'] = sg_node.getAttribute('name')
if sg_node.hasAttribute('id'):
server_group['id'] = sg_node.getAttribute('id')
policies = self._extract_policies(sg_node)
server_group['policies'] = policies or []
return server_group
def _extract_policies(self, server_group_node):
"""Marshal the server group policies element of a parsed request."""
policies_node = self.find_first_child_named(server_group_node,
'policies')
if policies_node is not None:
policy_nodes = self.find_children_named(policies_node,
'policy')
policies = []
if policy_nodes is not None:
for node in policy_nodes:
policies.append(node.firstChild.nodeValue)
return policies
def _extract_members(self, server_group_node):
"""Marshal the server group members element of a parsed request."""
members_node = self.find_first_child_named(server_group_node,
'members')
if members_node is not None:
member_nodes = self.find_children_named(members_node,
'member')
members = []
if member_nodes is not None:
for node in member_nodes:
members.append(node.firstChild.nodeValue)
return members
class ServerGroupController(wsgi.Controller):
"""The Server group API controller for the OpenStack API."""
def _format_server_group(self, context, group):
# the id field has its value as the uuid of the server group
# There is no 'uuid' key in server_group seen by clients.
# In addition, clients see policies as a ["policy-name"] list;
# and they see members as a ["server-id"] list.
server_group = {}
server_group['id'] = group.uuid
server_group['name'] = group.name
server_group['policies'] = group.policies or []
server_group['members'] = group.members or []
server_group['metadata'] = group.metadetails or {}
return server_group
def _validate_input_body(self, body, entity_name):
if not self.is_valid_body(body, entity_name):
msg = _("the body is invalid.")
raise nova.exception.InvalidInput(reason=msg)
subbody = dict(body[entity_name])
expected_fields = ['name', 'policies']
for field in expected_fields:
value = subbody.pop(field, None)
if not value:
msg = _("'%s' is either missing or empty.") % field
raise nova.exception.InvalidInput(reason=msg)
if isinstance(value, basestring):
utils.check_string_length(value, field,
min_length=1, max_length=255)
elif isinstance(value, list):
[utils.check_string_length(v, field,
min_length=1, max_length=255) for v in value]
if subbody:
msg = _("unsupported fields: %s") % subbody.keys()
raise nova.exception.InvalidInput(reason=msg)
@wsgi.serializers(xml=ServerGroupTemplate)
def show(self, req, id):
"""Return data about the given server group."""
context = _authorize_context(req)
try:
sg = instance_group_obj.InstanceGroup.get_by_uuid(context, id)
except nova.exception.InstanceGroupNotFound as e:
raise webob.exc.HTTPNotFound(explanation=e.format_message())
return {'server_group': self._format_server_group(context, sg)}
def delete(self, req, id):
"""Delete an server group."""
context = _authorize_context(req)
try:
sg = instance_group_obj.InstanceGroup.get_by_uuid(context, id)
sg.destroy(context)
except nova.exception.InstanceGroupNotFound as e:
raise webob.exc.HTTPNotFound(explanation=e.format_message())
return webob.Response(status_int=204)
@wsgi.serializers(xml=ServerGroupsTemplate)
def index(self, req):
"""Returns a list of server groups."""
context = _authorize_context(req)
project_id = context.project_id
if 'all_projects' in req.GET and context.is_admin:
sgs = instance_group_obj.InstanceGroupList.get_all(context)
else:
sgs = instance_group_obj.InstanceGroupList.get_by_project_id(
context, project_id)
limited_list = common.limited(sgs.objects, req)
result = [self._format_server_group(context, group)
for group in limited_list]
return {'server_groups': result}
@wsgi.serializers(xml=ServerGroupTemplate)
@wsgi.deserializers(xml=ServerGroupXMLDeserializer)
def create(self, req, body):
"""Creates a new server group."""
context = _authorize_context(req)
try:
self._validate_input_body(body, 'server_group')
except nova.exception.InvalidInput as e:
raise exc.HTTPBadRequest(explanation=e.format_message())
vals = body['server_group']
sg = instance_group_obj.InstanceGroup()
sg.project_id = context.project_id
sg.user_id = context.user_id
try:
sg.name = vals.get('name')
sg.policies = vals.get('policies')
sg.create(context)
except ValueError as e:
raise exc.HTTPBadRequest(explanation=e)
return {'server_group': self._format_server_group(context, sg)}
class ServerGroupsTemplateElement(xmlutil.TemplateElement):
def will_render(self, datum):
return "server_groups" in datum
class Server_groups(extensions.ExtensionDescriptor):
"""Server group support."""
name = "ServerGroups"
alias = "os-server-groups"
namespace = ("http://docs.openstack.org/compute/ext/"
"servergroups/api/v2")
updated = "2013-06-20T00:00:00+00:00"
def get_resources(self):
resources = []
res = extensions.ResourceExtension(
'os-server-groups',
controller=ServerGroupController(),
member_actions={"action": "POST", })
resources.append(res)
return resources

View File

@ -0,0 +1,389 @@
# Copyright (c) 2014 Cisco Systems, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from lxml import etree
import webob
from nova.api.openstack.compute.contrib import server_groups
from nova.api.openstack import wsgi
import nova.db
from nova import exception
from nova.openstack.common import uuidutils
from nova import test
from nova.tests.api.openstack import fakes
from nova.tests import utils
FAKE_UUID1 = 'a47ae74e-ab08-447f-8eee-ffd43fc46c16'
FAKE_UUID2 = 'c6e6430a-6563-4efa-9542-5e93c9e97d18'
FAKE_UUID3 = 'b8713410-9ba3-e913-901b-13410ca90121'
class AttrDict(dict):
def __getattr__(self, k):
return self[k]
def server_group_template(**kwargs):
sgroup = kwargs.copy()
sgroup.setdefault('name', 'test')
return sgroup
def server_group_resp_template(**kwargs):
sgroup = kwargs.copy()
sgroup.setdefault('name', 'test')
sgroup.setdefault('policies', [])
sgroup.setdefault('members', [])
sgroup.setdefault('metadata', {})
return sgroup
def server_group_db(sg):
attrs = sg.copy()
if 'id' in attrs:
attrs['uuid'] = attrs.pop('id')
if 'policies' in attrs:
policies = attrs.pop('policies')
attrs['policies'] = policies
else:
attrs['policies'] = []
if 'members' in attrs:
members = attrs.pop('members')
attrs['members'] = members
else:
attrs['members'] = []
if 'metadata' in attrs:
attrs['metadetails'] = attrs.pop('metadata')
else:
attrs['metadetails'] = {}
attrs['deleted'] = 0
attrs['deleted_at'] = None
attrs['created_at'] = None
attrs['updated_at'] = None
if 'user_id' not in attrs:
attrs['user_id'] = 'user_id'
if 'project_id' not in attrs:
attrs['project_id'] = 'project_id'
attrs['id'] = 7
return AttrDict(attrs)
class ServerGroupTest(test.TestCase):
def setUp(self):
super(ServerGroupTest, self).setUp()
self.controller = server_groups.ServerGroupController()
self.app = fakes.wsgi_app(init_only=('os-server-groups',))
def test_create_server_group_with_no_policies(self):
req = fakes.HTTPRequest.blank('/v2/fake/os-server-groups')
sgroup = server_group_template()
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create,
req, {'server_group': sgroup})
def test_create_server_group_normal(self):
req = fakes.HTTPRequest.blank('/v2/fake/os-server-groups')
sgroup = server_group_template()
policies = ['test_policy']
sgroup['policies'] = policies
res_dict = self.controller.create(req, {'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']['policies'], policies)
def test_create_server_group_with_illegal_name(self):
# blank name
sgroup = server_group_template(name='')
req = fakes.HTTPRequest.blank('/v2/fake/os-server-groups')
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create,
req, {'server_group': sgroup})
# name with length 256
sgroup = server_group_template(name='1234567890' * 26)
req = fakes.HTTPRequest.blank('/v2/fake/os-server-groups')
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create,
req, {'server_group': sgroup})
# non-string name
sgroup = server_group_template(name=12)
req = fakes.HTTPRequest.blank('/v2/fake/os-server-groups')
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create,
req, {'server_group': sgroup})
def test_create_server_group_with_no_body(self):
req = fakes.HTTPRequest.blank('/v2/fake/os-server-groups')
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.create, req, None)
def test_create_server_group_with_no_server_group(self):
body = {'no-instanceGroup': None}
req = fakes.HTTPRequest.blank('/v2/fake/os-server-groups')
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller.create, req, body)
def test_list_server_group_by_tenant(self):
groups = []
policies = ['test_policy']
members = ['1', '2']
metadata = {'key1': 'value1'}
names = ['default-x', 'test']
sg1 = server_group_resp_template(id=str(1345),
name=names[0],
policies=policies,
members=members,
metadata=metadata)
sg2 = server_group_resp_template(id=str(891),
name=names[1],
policies=policies,
members=members,
metadata=metadata)
groups = [sg1, sg2]
expected = {'server_groups': groups}
def return_server_groups(context, project_id):
return [server_group_db(sg) for sg in groups]
self.stubs.Set(nova.db, 'instance_group_get_all_by_project_id',
return_server_groups)
req = fakes.HTTPRequest.blank('/v2/fake/os-server-groups')
res_dict = self.controller.index(req)
self.assertEqual(res_dict, expected)
def test_list_server_group_all(self):
all_groups = []
tenant_groups = []
policies = ['test_policy']
members = ['1', '2']
metadata = {'key1': 'value1'}
names = ['default-x', 'test']
sg1 = server_group_resp_template(id=str(1345),
name=names[0],
policies=[],
members=members,
metadata=metadata)
sg2 = server_group_resp_template(id=str(891),
name=names[1],
policies=policies,
members=members,
metadata={})
tenant_groups = [sg2]
all_groups = [sg1, sg2]
all = {'server_groups': all_groups}
tenant_specific = {'server_groups': tenant_groups}
def return_all_server_groups(context):
return [server_group_db(sg) for sg in all_groups]
self.stubs.Set(nova.db, 'instance_group_get_all',
return_all_server_groups)
def return_tenant_server_groups(context, project_id):
return [server_group_db(sg) for sg in tenant_groups]
self.stubs.Set(nova.db, 'instance_group_get_all_by_project_id',
return_tenant_server_groups)
path = '/v2/fake/os-server-groups?all_projects=True'
req = fakes.HTTPRequest.blank(path, use_admin_context=True)
res_dict = self.controller.index(req)
self.assertEqual(res_dict, all)
req = fakes.HTTPRequest.blank(path)
res_dict = self.controller.index(req)
self.assertEqual(res_dict, tenant_specific)
def test_delete_server_group_by_id(self):
sg = server_group_template(id='123')
self.called = False
def server_group_delete(context, id):
self.called = True
def return_server_group(context, group_id):
self.assertEqual(sg['id'], group_id)
return server_group_db(sg)
self.stubs.Set(nova.db, 'instance_group_delete',
server_group_delete)
self.stubs.Set(nova.db, 'instance_group_get',
return_server_group)
req = fakes.HTTPRequest.blank('/v2/fake/os-server-groups/123')
resp = self.controller.delete(req, '123')
self.assertTrue(self.called)
self.assertEqual(resp.status_int, 204)
def test_delete_non_existing_server_group(self):
req = fakes.HTTPRequest.blank('/v2/fake/os-server-groups/invalid')
self.assertRaises(webob.exc.HTTPNotFound, self.controller.delete,
req, 'invalid')
class TestServerGroupXMLDeserializer(test.TestCase):
def setUp(self):
super(TestServerGroupXMLDeserializer, self).setUp()
self.deserializer = server_groups.ServerGroupXMLDeserializer()
def test_create_request(self):
serial_request = """
<server_group name="test">
</server_group>"""
request = self.deserializer.deserialize(serial_request)
expected = {
"server_group": {
"name": "test",
"policies": []
},
}
self.assertEqual(request['body'], expected)
def test_update_request(self):
serial_request = """
<server_group name="test">
<policies>
<policy>policy-1</policy>
<policy>policy-2</policy>
</policies>
</server_group>"""
request = self.deserializer.deserialize(serial_request)
expected = {
"server_group": {
"name": 'test',
"policies": ['policy-1', 'policy-2']
},
}
self.assertEqual(request['body'], expected)
def test_create_request_no_name(self):
serial_request = """
<server_group>
</server_group>"""
request = self.deserializer.deserialize(serial_request)
expected = {
"server_group": {
"policies": []
},
}
self.assertEqual(request['body'], expected)
def test_corrupt_xml(self):
"""Should throw a 400 error on corrupt xml."""
self.assertRaises(
exception.MalformedRequestBody,
self.deserializer.deserialize,
utils.killer_xml_body())
class TestServerGroupXMLSerializer(test.TestCase):
def setUp(self):
super(TestServerGroupXMLSerializer, self).setUp()
self.namespace = wsgi.XMLNS_V11
self.index_serializer = server_groups.ServerGroupsTemplate()
self.default_serializer = server_groups.ServerGroupTemplate()
def _tag(self, elem):
tagname = elem.tag
self.assertEqual(tagname[0], '{')
tmp = tagname.partition('}')
namespace = tmp[0][1:]
self.assertEqual(namespace, self.namespace)
return tmp[2]
def _verify_server_group(self, raw_group, tree):
policies = raw_group['policies']
members = raw_group['members']
metadata = raw_group['metadata']
self.assertEqual('server_group', self._tag(tree))
self.assertEqual(raw_group['id'], tree.get('id'))
self.assertEqual(raw_group['name'], tree.get('name'))
self.assertEqual(3, len(tree))
for child in tree:
child_tag = self._tag(child)
if child_tag == 'policies':
self.assertEqual(len(policies), len(child))
for idx, gr_child in enumerate(child):
self.assertEqual(self._tag(gr_child), 'policy')
self.assertEqual(policies[idx],
gr_child.text)
elif child_tag == 'members':
self.assertEqual(len(members), len(child))
for idx, gr_child in enumerate(child):
self.assertEqual(self._tag(gr_child), 'member')
self.assertEqual(members[idx],
gr_child.text)
elif child_tag == 'metadata':
self.assertEqual(len(metadata), len(child))
metas = {}
for idx, gr_child in enumerate(child):
self.assertEqual(self._tag(gr_child), 'meta')
key = gr_child.get('key')
self.assertTrue(key in ['key1', 'key2'])
metas[key] = gr_child.text
self.assertEqual(len(metas), len(metadata))
for k in metadata:
self.assertEqual(metadata[k], metas[k])
def _verify_server_group_brief(self, raw_group, tree):
self.assertEqual('server_group', self._tag(tree))
self.assertEqual(raw_group['id'], tree.get('id'))
self.assertEqual(raw_group['name'], tree.get('name'))
def test_group_serializer(self):
policies = ["policy-1", "policy-2"]
members = ["1", "2"]
metadata = dict(key1="value1", key2="value2")
raw_group = dict(
id='890',
name='name',
policies=policies,
members=members,
metadata=metadata)
sg_group = dict(server_group=raw_group)
text = self.default_serializer.serialize(sg_group)
tree = etree.fromstring(text)
self._verify_server_group(raw_group, tree)
def test_groups_serializer(self):
policies = ["policy-1", "policy-2",
"policy-3"]
members = ["1", "2", "3"]
metadata = dict(key1="value1", key2="value2")
groups = [dict(
id='890',
name='test',
policies=policies[0:2],
members=members[0:2],
metadata=metadata),
dict(
id='123',
name='default',
policies=policies[2:],
members=members[2:],
metadata=metadata)]
sg_groups = dict(server_groups=groups)
text = self.index_serializer.serialize(sg_groups)
tree = etree.fromstring(text)
self.assertEqual('server_groups', self._tag(tree))
self.assertEqual(len(groups), len(tree))
for idx, child in enumerate(tree):
self._verify_server_group_brief(groups[idx], child)

View File

@ -255,6 +255,7 @@ policy_data = """
"compute_extension:v3:os-security-groups": "",
"compute_extension:server_diagnostics": "",
"compute_extension:v3:os-server-diagnostics": "",
"compute_extension:server_groups": "",
"compute_extension:server_password": "",
"compute_extension:v3:os-server-password": "",
"compute_extension:server_usage": "",

View File

@ -647,6 +647,14 @@
"name": "ExtendedServicesDelete",
"namespace": "http://docs.openstack.org/compute/ext/extended_services_delete/api/v2",
"updated": "%(timestamp)s"
},
{
"alias": "os-server-groups",
"description": "%(text)s",
"links": [],
"name": "ServerGroups",
"namespace": "http://docs.openstack.org/compute/ext/servergroups/api/v2",
"updated": "%(timestamp)s"
}
]
}

View File

@ -242,4 +242,7 @@
<extension alias="os-console-auth-tokens" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/consoles-auth-tokens/api/v2" name="ConsoleAuthTokens">
<description>%(text)s</description>
</extension>
<extension alias="os-server-groups" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/servergroups/api/v2" name="ServerGroups">
<description>%(text)s</description>
</extension>
</extensions>

View File

@ -0,0 +1,9 @@
{
"server_group": {
"id": "%(id)s",
"name": "%(name)s",
"policies": ["test_policy"],
"members": [],
"metadata": {}
}
}

View File

@ -0,0 +1,8 @@
<?xml version='1.0' encoding='UTF-8'?>
<server_group xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1" id="%(id)s" name="test">
<policies>
<policy>test_policy</policy>
</policies>
<members/>
<metadata/>
</server_group>

View File

@ -0,0 +1,11 @@
{
"server_groups": [
{
"id": "%(id)s",
"name": "test",
"policies": ["test_policy"],
"members": [],
"metadata": {}
}
]
}

View File

@ -0,0 +1,10 @@
<?xml version='1.0' encoding='UTF-8'?>
<server_groups xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1">
<server_group id="%(id)s" name="test">
<policies>
<policy>test_policy</policy>
</policies>
<members/>
<metadata/>
</server_group>
</server_groups>

View File

@ -0,0 +1,6 @@
{
"server_group": {
"name": "%(name)s",
"policies": ["test_policy"]
}
}

View File

@ -0,0 +1,5 @@
<server_group name="test">
<policies>
<policy>test_policy</policy>
</policies>
</server_group>

View File

@ -0,0 +1,10 @@
{
"server_group": {
"id": "%(id)s",
"name": "%(name)s",
"policies": ["test_policy"],
"members": [],
"metadata": {}
}
}

View File

@ -0,0 +1,8 @@
<?xml version='1.0' encoding='UTF-8'?>
<server_group xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://docs.openstack.org/compute/api/v1.1" id="%(id)s" name="test">
<policies>
<policy>test_policy</policy>
</policies>
<members/>
<metadata/>
</server_group>

View File

@ -4160,3 +4160,58 @@ class ServerExternalEventsJsonTest(ServersSampleBase):
class ServerExternalEventsXmlTest(ServerExternalEventsJsonTest):
ctype = 'xml'
class ServerGroupsSampleJsonTest(ServersSampleBase):
extension_name = ("nova.api.openstack.compute.contrib"
".server_groups.Server_groups")
def _get_create_subs(self):
return {'name': 'test'}
def _post_server_group(self):
"""Verify the response status and returns the UUID of the
newly created server group.
"""
subs = self._get_create_subs()
response = self._do_post('os-server-groups',
'server-groups-post-req', subs)
subs = self._get_regexes()
subs['name'] = 'test'
return self._verify_response('server-groups-post-resp',
subs, response, 200)
def _create_server_group(self):
subs = self._get_create_subs()
return self._do_post('os-server-groups',
'server-groups-post-req', subs)
def test_server_groups_post(self):
return self._post_server_group()
def test_server_groups_list(self):
subs = self._get_create_subs()
uuid = self._post_server_group()
response = self._do_get('os-server-groups')
subs.update(self._get_regexes())
subs['id'] = uuid
self._verify_response('server-groups-list-resp',
subs, response, 200)
def test_server_groups_get(self):
# Get api sample of server groups get request.
subs = {'name': 'test'}
uuid = self._post_server_group()
subs['id'] = uuid
response = self._do_get('os-server-groups/%s' % uuid)
self._verify_response('server-groups-get-resp', subs, response, 200)
def test_server_groups_delete(self):
uuid = self._post_server_group()
response = self._do_delete('os-server-groups/%s' % uuid)
self.assertEqual(response.status, 204)
class ServerGroupsSampleXmlTest(ServerGroupsSampleJsonTest):
ctype = 'xml'