Merge "Adds support for security_groups for V3 API server create"

This commit is contained in:
Jenkins 2013-08-29 15:34:40 +00:00 committed by Gerrit Code Review
commit e138115aef
5 changed files with 282 additions and 122 deletions

View File

@ -34,6 +34,7 @@ from nova.openstack.common import xmlutils
ALIAS = 'os-security-groups'
ATTRIBUTE_NAME = '%s:security_groups' % ALIAS
authorize = extensions.extension_authorizer('compute', 'v3:' + ALIAS)
softauth = extensions.soft_extension_authorizer('compute', 'v3:' + ALIAS)
@ -239,6 +240,10 @@ class SecurityGroups(extensions.V3APIExtensionBase):
namespace = "http://docs.openstack.org/compute/ext/securitygroups/api/v3"
version = 1
def __init__(self, extension_info):
super(SecurityGroups, self).__init__(extension_info)
self.xml_deserializer = wsgi.XMLDeserializer()
def get_controller_extensions(self):
controller = SecurityGroupActionController()
actions = extensions.ControllerExtension(self, 'servers', controller)
@ -249,6 +254,37 @@ class SecurityGroups(extensions.V3APIExtensionBase):
def get_resources(self):
return []
def server_create(self, server_dict, create_kwargs):
security_groups = server_dict.get(ATTRIBUTE_NAME)
if security_groups is not None:
create_kwargs['security_group'] = [
sg['name'] for sg in security_groups if sg.get('name')]
create_kwargs['security_group'] = list(
set(create_kwargs['security_group']))
def _extract_security_groups(self, server_node):
"""Marshal the security_groups attribute of a parsed request."""
node = self.xml_deserializer.find_first_child_named_in_namespace(
server_node, self.namespace, 'security_groups')
if node is not None:
security_groups = []
for sg_node in self.xml_deserializer.find_children_named(
node, "security_group"):
item = {}
name = self.xml_deserializer.find_attribute_or_element(
sg_node, 'name')
if name:
item["name"] = name
security_groups.append(item)
return security_groups
else:
return None
def server_xml_extract_server_deserialize(self, server_node, server_dict):
security_groups = self._extract_security_groups(server_node)
if security_groups is not None:
server_dict[ATTRIBUTE_NAME] = security_groups
class NativeSecurityGroupExceptions(object):
@staticmethod

View File

@ -173,10 +173,6 @@ class CommonDeserializer(wsgi.MetadataXMLDeserializer):
if networks is not None:
server["networks"] = networks
security_groups = self._extract_security_groups(server_node)
if security_groups is not None:
server["security_groups"] = security_groups
# NOTE(vish): this is not namespaced in json, so leave it without a
# namespace for now
block_device_mapping = self._extract_block_device_mapping(server_node)
@ -232,21 +228,6 @@ class CommonDeserializer(wsgi.MetadataXMLDeserializer):
else:
return None
def _extract_security_groups(self, server_node):
"""Marshal the security_groups attribute of a parsed request."""
node = self.find_first_child_named(server_node, "security_groups")
if node is not None:
security_groups = []
for sg_node in self.find_children_named(node, "security_group"):
item = {}
name = self.find_attribute_or_element(sg_node, 'name')
if name:
item["name"] = name
security_groups.append(item)
return security_groups
else:
return None
class ActionDeserializer(CommonDeserializer):
"""Deserializer to handle xml-formatted server action requests.
@ -794,20 +775,6 @@ class ServersController(wsgi.Controller):
return_reservation_id = create_kwargs.pop('return_reservation_id',
False)
# TODO(cyeoh): bp v3-api-core-as-extensions
# Replace with an extension point when the security groups
# extension is ported
sg_names = []
#if self.ext_mgr.is_loaded('os-security-groups'):
# security_groups = server_dict.get('security_groups')
# if security_groups is not None:
# sg_names = [sg['name'] for sg in security_groups
# if sg.get('name')]
if not sg_names:
sg_names.append('default')
sg_names = list(set(sg_names))
requested_networks = None
# TODO(cyeoh): bp v3-api-core-as-extensions
# Replace with an extension point when the os-networks
@ -863,7 +830,6 @@ class ServersController(wsgi.Controller):
access_ip_v6=access_ip_v6,
admin_password=password,
requested_networks=requested_networks,
security_group=sg_names,
block_device_mapping=block_device_mapping,
**create_kwargs)
except exception.QuotaError as error:

View File

@ -15,28 +15,46 @@
# License for the specific language governing permissions and limitations
# under the License.
import datetime
from lxml import etree
import mox
from oslo.config import cfg
import uuid
import webob
from nova.api.openstack.compute import plugins
from nova.api.openstack.compute.plugins.v3 import security_groups
from nova.api.openstack.compute.plugins.v3 import servers
from nova.api.openstack import xmlutil
from nova import compute
from nova.compute import api as compute_api
from nova.compute import flavors
from nova.compute import power_state
import nova.db
from nova import db
from nova import exception
from nova.network import manager
from nova.objects import instance as instance_obj
from nova.openstack.common import jsonutils
from nova.openstack.common import rpc
from nova import test
from nova.tests.api.openstack import fakes
from nova.tests import fake_instance
import nova.tests.image.fake
CONF = cfg.CONF
FAKE_UUID = fakes.FAKE_UUID
FAKE_UUID1 = 'a47ae74e-ab08-447f-8eee-ffd43fc46c16'
def fake_gen_uuid():
return FAKE_UUID
def return_security_group(context, instance_id, security_group_id):
pass
def return_server(context, server_id, columns_to_join=None):
return fake_instance.fake_db_instance(
**{'id': int(server_id),
@ -80,9 +98,9 @@ class TestSecurityGroups(test.TestCase):
self.manager = security_groups.SecurityGroupActionController()
def test_associate_by_non_existing_security_group_name(self):
self.stubs.Set(nova.db, 'instance_get', return_server)
self.stubs.Set(db, 'instance_get', return_server)
self.assertEquals(return_server(None, '1'),
nova.db.instance_get(None, '1'))
db.instance_get(None, '1'))
body = dict(add_security_group=dict(name='non-existing'))
req = fakes.HTTPRequestV3.blank('/servers/1/action')
@ -98,7 +116,7 @@ class TestSecurityGroups(test.TestCase):
req, 'invalid', body)
def test_associate_without_body(self):
self.stubs.Set(nova.db, 'instance_get', return_server)
self.stubs.Set(db, 'instance_get', return_server)
body = dict(add_security_group=None)
req = fakes.HTTPRequestV3.blank('/servers/1/action')
@ -106,7 +124,7 @@ class TestSecurityGroups(test.TestCase):
self.manager._add_security_group, req, '1', body)
def test_associate_no_security_group_name(self):
self.stubs.Set(nova.db, 'instance_get', return_server)
self.stubs.Set(db, 'instance_get', return_server)
body = dict(add_security_group=dict())
req = fakes.HTTPRequestV3.blank('/servers/1/action')
@ -114,7 +132,7 @@ class TestSecurityGroups(test.TestCase):
self.manager._add_security_group, req, '1', body)
def test_associate_security_group_name_with_whitespaces(self):
self.stubs.Set(nova.db, 'instance_get', return_server)
self.stubs.Set(db, 'instance_get', return_server)
body = dict(add_security_group=dict(name=" "))
req = fakes.HTTPRequestV3.blank('/servers/1/action')
@ -122,8 +140,8 @@ class TestSecurityGroups(test.TestCase):
self.manager._add_security_group, req, '1', body)
def test_associate_non_existing_instance(self):
self.stubs.Set(nova.db, 'instance_get', return_server_nonexistent)
self.stubs.Set(nova.db, 'instance_get_by_uuid',
self.stubs.Set(db, 'instance_get', return_server_nonexistent)
self.stubs.Set(db, 'instance_get_by_uuid',
return_server_nonexistent)
body = dict(add_security_group=dict(name="test"))
@ -132,10 +150,10 @@ class TestSecurityGroups(test.TestCase):
self.manager._add_security_group, req, '1', body)
def test_associate_non_running_instance(self):
self.stubs.Set(nova.db, 'instance_get', return_non_running_server)
self.stubs.Set(nova.db, 'instance_get_by_uuid',
self.stubs.Set(db, 'instance_get', return_non_running_server)
self.stubs.Set(db, 'instance_get_by_uuid',
return_non_running_server)
self.stubs.Set(nova.db, 'security_group_get_by_name',
self.stubs.Set(db, 'security_group_get_by_name',
return_security_group_without_instances)
body = dict(add_security_group=dict(name="test"))
@ -143,10 +161,10 @@ class TestSecurityGroups(test.TestCase):
self.manager._add_security_group(req, '1', body)
def test_associate_already_associated_security_group_to_instance(self):
self.stubs.Set(nova.db, 'instance_get', return_server)
self.stubs.Set(nova.db, 'instance_get_by_uuid',
self.stubs.Set(db, 'instance_get', return_server)
self.stubs.Set(db, 'instance_get_by_uuid',
return_server_by_uuid)
self.stubs.Set(nova.db, 'security_group_get_by_name',
self.stubs.Set(db, 'security_group_get_by_name',
return_security_group_by_name)
body = dict(add_security_group=dict(name="test"))
@ -155,14 +173,14 @@ class TestSecurityGroups(test.TestCase):
self.manager._add_security_group, req, '1', body)
def test_associate(self):
self.stubs.Set(nova.db, 'instance_get', return_server)
self.stubs.Set(nova.db, 'instance_get_by_uuid',
self.stubs.Set(db, 'instance_get', return_server)
self.stubs.Set(db, 'instance_get_by_uuid',
return_server_by_uuid)
self.mox.StubOutWithMock(nova.db, 'instance_add_security_group')
nova.db.instance_add_security_group(mox.IgnoreArg(),
self.mox.StubOutWithMock(db, 'instance_add_security_group')
db.instance_add_security_group(mox.IgnoreArg(),
mox.IgnoreArg(),
mox.IgnoreArg())
self.stubs.Set(nova.db, 'security_group_get_by_name',
self.stubs.Set(db, 'security_group_get_by_name',
return_security_group_without_instances)
self.mox.ReplayAll()
@ -172,9 +190,9 @@ class TestSecurityGroups(test.TestCase):
self.manager._add_security_group(req, '1', body)
def test_disassociate_by_non_existing_security_group_name(self):
self.stubs.Set(nova.db, 'instance_get', return_server)
self.stubs.Set(db, 'instance_get', return_server)
self.assertEquals(return_server(None, '1'),
nova.db.instance_get(None, '1'))
db.instance_get(None, '1'))
body = dict(remove_security_group=dict(name='non-existing'))
req = fakes.HTTPRequestV3.blank('/servers/1/action')
@ -182,7 +200,7 @@ class TestSecurityGroups(test.TestCase):
self.manager._remove_security_group, req, '1', body)
def test_disassociate_by_invalid_server_id(self):
self.stubs.Set(nova.db, 'security_group_get_by_name',
self.stubs.Set(db, 'security_group_get_by_name',
return_security_group_by_name)
body = dict(remove_security_group=dict(name='test'))
@ -192,7 +210,7 @@ class TestSecurityGroups(test.TestCase):
body)
def test_disassociate_without_body(self):
self.stubs.Set(nova.db, 'instance_get', return_server)
self.stubs.Set(db, 'instance_get', return_server)
body = dict(remove_security_group=None)
req = fakes.HTTPRequestV3.blank('/servers/1/action')
@ -200,7 +218,7 @@ class TestSecurityGroups(test.TestCase):
self.manager._remove_security_group, req, '1', body)
def test_disassociate_no_security_group_name(self):
self.stubs.Set(nova.db, 'instance_get', return_server)
self.stubs.Set(db, 'instance_get', return_server)
body = dict(remove_security_group=dict())
req = fakes.HTTPRequestV3.blank('/servers/1/action')
@ -208,7 +226,7 @@ class TestSecurityGroups(test.TestCase):
self.manager._remove_security_group, req, '1', body)
def test_disassociate_security_group_name_with_whitespaces(self):
self.stubs.Set(nova.db, 'instance_get', return_server)
self.stubs.Set(db, 'instance_get', return_server)
body = dict(remove_security_group=dict(name=" "))
req = fakes.HTTPRequestV3.blank('/servers/1/action')
@ -216,8 +234,8 @@ class TestSecurityGroups(test.TestCase):
self.manager._remove_security_group, req, '1', body)
def test_disassociate_non_existing_instance(self):
self.stubs.Set(nova.db, 'instance_get', return_server_nonexistent)
self.stubs.Set(nova.db, 'security_group_get_by_name',
self.stubs.Set(db, 'instance_get', return_server_nonexistent)
self.stubs.Set(db, 'security_group_get_by_name',
return_security_group_by_name)
body = dict(remove_security_group=dict(name="test"))
@ -226,10 +244,10 @@ class TestSecurityGroups(test.TestCase):
self.manager._remove_security_group, req, '1', body)
def test_disassociate_non_running_instance(self):
self.stubs.Set(nova.db, 'instance_get', return_non_running_server)
self.stubs.Set(nova.db, 'instance_get_by_uuid',
self.stubs.Set(db, 'instance_get', return_non_running_server)
self.stubs.Set(db, 'instance_get_by_uuid',
return_non_running_server)
self.stubs.Set(nova.db, 'security_group_get_by_name',
self.stubs.Set(db, 'security_group_get_by_name',
return_security_group_by_name)
body = dict(remove_security_group=dict(name="test"))
@ -237,10 +255,10 @@ class TestSecurityGroups(test.TestCase):
self.manager._remove_security_group(req, '1', body)
def test_disassociate_already_associated_security_group_to_instance(self):
self.stubs.Set(nova.db, 'instance_get', return_server)
self.stubs.Set(nova.db, 'instance_get_by_uuid',
self.stubs.Set(db, 'instance_get', return_server)
self.stubs.Set(db, 'instance_get_by_uuid',
return_server_by_uuid)
self.stubs.Set(nova.db, 'security_group_get_by_name',
self.stubs.Set(db, 'security_group_get_by_name',
return_security_group_without_instances)
body = dict(remove_security_group=dict(name="test"))
@ -249,14 +267,14 @@ class TestSecurityGroups(test.TestCase):
self.manager._remove_security_group, req, '1', body)
def test_disassociate(self):
self.stubs.Set(nova.db, 'instance_get', return_server)
self.stubs.Set(nova.db, 'instance_get_by_uuid',
self.stubs.Set(db, 'instance_get', return_server)
self.stubs.Set(db, 'instance_get_by_uuid',
return_server_by_uuid)
self.mox.StubOutWithMock(nova.db, 'instance_remove_security_group')
nova.db.instance_remove_security_group(mox.IgnoreArg(),
self.mox.StubOutWithMock(db, 'instance_remove_security_group')
db.instance_remove_security_group(mox.IgnoreArg(),
mox.IgnoreArg(),
mox.IgnoreArg())
self.stubs.Set(nova.db, 'security_group_get_by_name',
self.stubs.Set(db, 'security_group_get_by_name',
return_security_group_by_name)
self.mox.ReplayAll()
@ -415,3 +433,191 @@ class SecurityGroupsOutputXmlTest(SecurityGroupsOutputTest):
# the existing server namespace.
namespace = server.nsmap[None]
return server.find('{%s}security_groups' % namespace).getchildren()
class ServersControllerCreateTest(test.TestCase):
def setUp(self):
"""Shared implementation for tests below that create instance."""
super(ServersControllerCreateTest, self).setUp()
self.flags(verbose=True,
enable_instance_password=True)
self.instance_cache_num = 0
self.instance_cache_by_id = {}
self.instance_cache_by_uuid = {}
ext_info = plugins.LoadedExtensionInfo()
self.controller = servers.ServersController(extension_info=ext_info)
CONF.set_override('extensions_blacklist', 'os-security-groups',
'osapi_v3')
self.no_security_groups_controller = servers.ServersController(
extension_info=ext_info)
def instance_create(context, inst):
inst_type = flavors.get_flavor_by_flavor_id(3)
image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
def_image_ref = 'http://localhost/images/%s' % image_uuid
self.instance_cache_num += 1
instance = fake_instance.fake_db_instance(**{
'id': self.instance_cache_num,
'display_name': inst['display_name'] or 'test',
'uuid': FAKE_UUID,
'instance_type': dict(inst_type),
'access_ip_v4': '1.2.3.4',
'access_ip_v6': 'fead::1234',
'image_ref': inst.get('image_ref', def_image_ref),
'user_id': 'fake',
'project_id': 'fake',
'reservation_id': inst['reservation_id'],
"created_at": datetime.datetime(2010, 10, 10, 12, 0, 0),
"updated_at": datetime.datetime(2010, 11, 11, 11, 0, 0),
"user_data": None,
"progress": 0,
"fixed_ips": [],
"task_state": "",
"vm_state": "",
"root_device_name": inst.get('root_device_name', 'vda'),
})
self.instance_cache_by_id[instance['id']] = instance
self.instance_cache_by_uuid[instance['uuid']] = instance
return instance
def instance_get(context, instance_id):
"""Stub for compute/api create() pulling in instance after
scheduling
"""
return self.instance_cache_by_id[instance_id]
def instance_update(context, uuid, values):
instance = self.instance_cache_by_uuid[uuid]
instance.update(values)
return instance
def server_update(context, instance_uuid, params):
inst = self.instance_cache_by_uuid[instance_uuid]
inst.update(params)
return (inst, inst)
def fake_method(*args, **kwargs):
pass
def project_get_networks(context, user_id):
return dict(id='1', host='localhost')
def queue_get_for(context, *args):
return 'network_topic'
fakes.stub_out_rate_limiting(self.stubs)
fakes.stub_out_key_pair_funcs(self.stubs)
nova.tests.image.fake.stub_out_image_service(self.stubs)
fakes.stub_out_nw_api(self.stubs)
self.stubs.Set(uuid, 'uuid4', fake_gen_uuid)
self.stubs.Set(db, 'instance_add_security_group',
return_security_group)
self.stubs.Set(db, 'project_get_networks',
project_get_networks)
self.stubs.Set(db, 'instance_create', instance_create)
self.stubs.Set(db, 'instance_system_metadata_update',
fake_method)
self.stubs.Set(db, 'instance_get', instance_get)
self.stubs.Set(db, 'instance_update', instance_update)
self.stubs.Set(rpc, 'cast', fake_method)
self.stubs.Set(db, 'instance_update_and_get_original',
server_update)
self.stubs.Set(rpc, 'queue_get_for', queue_get_for)
self.stubs.Set(manager.VlanManager, 'allocate_fixed_ip',
fake_method)
def _test_create_extra(self, params, no_image=False,
override_controller=None):
image_uuid = 'c905cedb-7281-47e4-8a62-f26bc5fc4c77'
server = dict(name='server_test', image_ref=image_uuid, flavor_ref=2)
if no_image:
server.pop('image_ref', None)
server.update(params)
body = dict(server=server)
req = fakes.HTTPRequestV3.blank('/servers')
req.method = 'POST'
req.body = jsonutils.dumps(body)
req.headers["content-type"] = "application/json"
if override_controller:
server = override_controller.create(req, body).obj['server']
else:
server = self.controller.create(req, body).obj['server']
def test_create_instance_with_security_group_enabled(self):
group = 'foo'
old_create = compute_api.API.create
def sec_group_get(ctx, proj, name):
if name == group:
return True
else:
raise exception.SecurityGroupNotFoundForProject(
project_id=proj, security_group_id=name)
def create(*args, **kwargs):
self.assertEqual(kwargs['security_group'], [group])
return old_create(*args, **kwargs)
self.stubs.Set(db, 'security_group_get_by_name', sec_group_get)
# negative test
self.assertRaises(webob.exc.HTTPBadRequest,
self._test_create_extra,
{security_groups.ATTRIBUTE_NAME:
[{'name': 'bogus'}]})
# positive test - extra assert in create path
self.stubs.Set(compute_api.API, 'create', create)
self._test_create_extra({security_groups.ATTRIBUTE_NAME:
[{'name': group}]})
def test_create_instance_with_security_group_disabled(self):
group = 'foo'
params = {'security_groups': [{'name': group}]}
old_create = compute_api.API.create
def create(*args, **kwargs):
# NOTE(vish): if the security groups extension is not
# enabled, then security groups passed in
# are ignored.
self.assertNotIn('security_group', kwargs)
return old_create(*args, **kwargs)
self.stubs.Set(compute_api.API, 'create', create)
self._test_create_extra(params,
override_controller=self.no_security_groups_controller)
class TestServerCreateRequestXMLDeserializer(test.TestCase):
def setUp(self):
super(TestServerCreateRequestXMLDeserializer, self).setUp()
ext_info = plugins.LoadedExtensionInfo()
controller = servers.ServersController(extension_info=ext_info)
self.deserializer = servers.CreateDeserializer(controller)
def test_request_with_security_groups(self):
serial_request = """
<server xmlns="http://docs.openstack.org/compute/api/v3"
name="security_groups_test"
image_ref="1"
flavor_ref="1">
<os:security_groups xmlns:os="%(namespace)s">
<security_group name="sg1"/>
<security_group name="sg2"/>
</os:security_groups>
</server>""" % {
'namespace': security_groups.SecurityGroups.namespace}
request = self.deserializer.deserialize(serial_request)
expected = {
"server": {
"name": "security_groups_test",
"image_ref": "1",
"flavor_ref": "1",
security_groups.ATTRIBUTE_NAME: [{"name": "sg1"},
{"name": "sg2"}]
},
}
self.assertEquals(request['body'], expected)

View File

@ -84,10 +84,6 @@ def return_servers_empty(context, *args, **kwargs):
return []
def return_security_group(context, instance_id, security_group_id):
pass
def instance_update(context, instance_uuid, values, update_cells=True):
inst = fakes.stub_instance(INSTANCE_IDS.get(instance_uuid),
name=values.get('display_name'))
@ -172,8 +168,6 @@ class ControllerTest(test.TestCase):
return_servers)
self.stubs.Set(db, 'instance_get_by_uuid',
return_server)
self.stubs.Set(db, 'instance_add_security_group',
return_security_group)
self.stubs.Set(db, 'instance_update_and_get_original',
instance_update)
@ -1688,8 +1682,6 @@ class ServersControllerCreateTest(test.TestCase):
fakes.stub_out_key_pair_funcs(self.stubs)
fake.stub_out_image_service(self.stubs)
self.stubs.Set(uuid, 'uuid4', fake_gen_uuid)
self.stubs.Set(db, 'instance_add_security_group',
return_security_group)
self.stubs.Set(db, 'project_get_networks',
project_get_networks)
self.stubs.Set(db, 'instance_create', instance_create)
@ -1857,48 +1849,6 @@ class ServersControllerCreateTest(test.TestCase):
self.req.headers["content-type"] = "application/json"
server = self.controller.create(self.req, self.body).obj['server']
# TODO(cyeoh): bp-v3-api-unittests
# This needs to be ported to the os-security-groups extension tests
# def test_create_instance_with_security_group_enabled(self):
# self.ext_mgr.extensions = {'os-security-groups': 'fake'}
# group = 'foo'
# old_create = compute_api.API.create
# def sec_group_get(ctx, proj, name):
# if name == group:
# return True
# else:
# raise exception.SecurityGroupNotFoundForProject(
# project_id=proj, security_group_id=name)
# def create(*args, **kwargs):
# self.assertEqual(kwargs['security_group'], [group])
# return old_create(*args, **kwargs)
# self.stubs.Set(db, 'security_group_get_by_name', sec_group_get)
# # negative test
# self.assertRaises(webob.exc.HTTPBadRequest,
# self._test_create_extra,
# {'security_groups': [{'name': 'bogus'}]})
# # positive test - extra assert in create path
# self.stubs.Set(compute_api.API, 'create', create)
# self._test_create_extra({'security_groups': [{'name': group}]})
def test_create_instance_with_security_group_disabled(self):
group = 'foo'
params = {'security_groups': [{'name': group}]}
old_create = compute_api.API.create
def create(*args, **kwargs):
# NOTE(vish): if the security groups extension is not
# enabled, then security groups passed in
# are ignored.
self.assertEqual(kwargs['security_group'], ['default'])
return old_create(*args, **kwargs)
self.stubs.Set(compute_api.API, 'create', create)
self._test_create_extra(params)
# TODO(cyeoh): bp-v3-api-unittests
# This needs to be ported to the os-volumes extension tests
# def test_create_instance_with_volumes_enabled(self):

View File

@ -115,6 +115,7 @@ nova.api.v3.extensions.server.create =
multiple_create = nova.api.openstack.compute.plugins.v3.multiple_create:MultipleCreate
personalities = nova.api.openstack.compute.plugins.v3.personalities:Personalities
scheduler_hints = nova.api.openstack.compute.plugins.v3.scheduler_hints:SchedulerHints
security_groups = nova.api.openstack.compute.plugins.v3.security_groups:SecurityGroups
user_data = nova.api.openstack.compute.plugins.v3.user_data:UserData
nova.api.v3.extensions.server.create.deserialize =
@ -124,6 +125,7 @@ nova.api.v3.extensions.server.create.deserialize =
multiple_create = nova.api.openstack.compute.plugins.v3.multiple_create:MultipleCreate
personalities = nova.api.openstack.compute.plugins.v3.personalities:Personalities
scheduler_hints = nova.api.openstack.compute.plugins.v3.scheduler_hints:SchedulerHints
security_groups = nova.api.openstack.compute.plugins.v3.security_groups:SecurityGroups
user_data = nova.api.openstack.compute.plugins.v3.user_data:UserData
nova.api.v3.extensions.server.rebuild =