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:
parent
c08cc38b81
commit
aeda1f6e64
|
@ -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.",
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"server_group": {
|
||||
"id": "5bbcc3c4-1da2-4437-a48a-66f15b1b13f9",
|
||||
"name": "test",
|
||||
"policies": ["test_policy"],
|
||||
"members": [],
|
||||
"metadata": {}
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"server_groups": [
|
||||
{
|
||||
"id": "616fb98f-46ca-475e-917e-2563e5a8cd19",
|
||||
"name": "test",
|
||||
"policies": ["test_policy"],
|
||||
"members": [],
|
||||
"metadata": {}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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>
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"server_group": {
|
||||
"name": "test",
|
||||
"policies": ["test_policy"]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<server_group name="test">
|
||||
<policies>
|
||||
<policy>test_policy</policy>
|
||||
</policies>
|
||||
</server_group>
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"server_group": {
|
||||
"id": "5bbcc3c4-1da2-4437-a48a-66f15b1b13f9",
|
||||
"name": "test",
|
||||
"policies": ["test_policy"],
|
||||
"members": [],
|
||||
"metadata": {}
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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": "",
|
||||
|
|
|
@ -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
|
|
@ -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)
|
|
@ -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": "",
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"server_group": {
|
||||
"id": "%(id)s",
|
||||
"name": "%(name)s",
|
||||
"policies": ["test_policy"],
|
||||
"members": [],
|
||||
"metadata": {}
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"server_groups": [
|
||||
{
|
||||
"id": "%(id)s",
|
||||
"name": "test",
|
||||
"policies": ["test_policy"],
|
||||
"members": [],
|
||||
"metadata": {}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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>
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"server_group": {
|
||||
"name": "%(name)s",
|
||||
"policies": ["test_policy"]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<server_group name="test">
|
||||
<policies>
|
||||
<policy>test_policy</policy>
|
||||
</policies>
|
||||
</server_group>
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"server_group": {
|
||||
"id": "%(id)s",
|
||||
"name": "%(name)s",
|
||||
"policies": ["test_policy"],
|
||||
"members": [],
|
||||
"metadata": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
|
@ -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'
|
||||
|
|
Loading…
Reference in New Issue