Merge "Removes os-personalities extension from the V3 API"
This commit is contained in:
commit
646332f225
@ -1,123 +0,0 @@
|
|||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2013 IBM Corp.
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
import base64
|
|
||||||
from webob import exc
|
|
||||||
|
|
||||||
from nova.api.openstack import extensions
|
|
||||||
from nova.api.openstack import wsgi
|
|
||||||
from nova.openstack.common.gettextutils import _
|
|
||||||
import re
|
|
||||||
|
|
||||||
ALIAS = "os-personality"
|
|
||||||
|
|
||||||
|
|
||||||
class Deserializer(wsgi.MetadataXMLDeserializer):
|
|
||||||
|
|
||||||
def extract_personality(self, server_node):
|
|
||||||
"""Marshal the personality attribute of a parsed request."""
|
|
||||||
node = self.find_first_child_named(server_node, "personality")
|
|
||||||
if node is not None:
|
|
||||||
personality = []
|
|
||||||
for file_node in self.find_children_named(node, "file"):
|
|
||||||
item = {}
|
|
||||||
if file_node.hasAttribute("path"):
|
|
||||||
item["path"] = file_node.getAttribute("path")
|
|
||||||
item["contents"] = self.extract_text(file_node)
|
|
||||||
personality.append(item)
|
|
||||||
return personality
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class Personalities(extensions.V3APIExtensionBase):
|
|
||||||
"""Personalities Extension."""
|
|
||||||
|
|
||||||
name = "Personalities"
|
|
||||||
alias = ALIAS
|
|
||||||
namespace = "http://docs.openstack.org/compute/ext/Personalities/api/v3"
|
|
||||||
version = 1
|
|
||||||
|
|
||||||
deserializer = Deserializer()
|
|
||||||
|
|
||||||
def get_controller_extensions(self):
|
|
||||||
return []
|
|
||||||
|
|
||||||
def get_resources(self):
|
|
||||||
return []
|
|
||||||
|
|
||||||
def server_create(self, server_dict, create_kwargs):
|
|
||||||
personality = server_dict.get('personality', None)
|
|
||||||
injected_files = []
|
|
||||||
if personality is not None:
|
|
||||||
injected_files = self._get_injected_files(personality)
|
|
||||||
create_kwargs['injected_files'] = injected_files
|
|
||||||
|
|
||||||
def server_xml_extract_server_deserialize(self, server_node, server_dict):
|
|
||||||
personality = self.deserializer.extract_personality(server_node)
|
|
||||||
if personality is not None:
|
|
||||||
server_dict["personality"] = personality
|
|
||||||
|
|
||||||
def _get_injected_files(self, personality):
|
|
||||||
"""Create a list of injected files from the personality attribute.
|
|
||||||
At this time, injected_files must be formatted as a list of
|
|
||||||
(file_path, file_content) pairs for compatibility with the
|
|
||||||
underlying compute service.
|
|
||||||
"""
|
|
||||||
injected_files = []
|
|
||||||
|
|
||||||
for item in personality:
|
|
||||||
try:
|
|
||||||
path = item['path']
|
|
||||||
contents = item['contents']
|
|
||||||
except KeyError as key:
|
|
||||||
expl = _('Bad personality format: missing %s') % key
|
|
||||||
raise exc.HTTPBadRequest(explanation=expl)
|
|
||||||
except TypeError:
|
|
||||||
expl = _('Bad personality format')
|
|
||||||
raise exc.HTTPBadRequest(explanation=expl)
|
|
||||||
if self._decode_base64(contents) is None:
|
|
||||||
expl = _('Personality content for %s cannot be decoded') % path
|
|
||||||
raise exc.HTTPBadRequest(explanation=expl)
|
|
||||||
injected_files.append((path, contents))
|
|
||||||
return injected_files
|
|
||||||
|
|
||||||
B64_REGEX = re.compile('^(?:[A-Za-z0-9+\/]{4})*'
|
|
||||||
'(?:[A-Za-z0-9+\/]{2}=='
|
|
||||||
'|[A-Za-z0-9+\/]{3}=)?$')
|
|
||||||
|
|
||||||
def _decode_base64(self, data):
|
|
||||||
data = re.sub(r'\s', '', data)
|
|
||||||
if not self.B64_REGEX.match(data):
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
return base64.b64decode(data)
|
|
||||||
except TypeError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def server_rebuild(self, rebuild_dict, rebuild_kwargs):
|
|
||||||
if 'personality' in rebuild_dict:
|
|
||||||
personality = rebuild_dict['personality']
|
|
||||||
rebuild_kwargs['files_to_inject'] =\
|
|
||||||
self._get_injected_files(personality)
|
|
||||||
|
|
||||||
def server_xml_extract_rebuild_deserialize(self,
|
|
||||||
rebuild_node,
|
|
||||||
rebuild_dict):
|
|
||||||
personality = self.deserializer.extract_personality(rebuild_node)
|
|
||||||
if personality is not None:
|
|
||||||
rebuild_dict["personality"] = personality
|
|
@ -449,7 +449,6 @@ class ServersControllerCreateTest(test.TestCase):
|
|||||||
'hello': 'world',
|
'hello': 'world',
|
||||||
'open': 'stack',
|
'open': 'stack',
|
||||||
},
|
},
|
||||||
'personality': {},
|
|
||||||
'availability_zone': "nova",
|
'availability_zone': "nova",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -482,7 +481,6 @@ class ServersControllerCreateTest(test.TestCase):
|
|||||||
'hello': 'world',
|
'hello': 'world',
|
||||||
'open': 'stack',
|
'open': 'stack',
|
||||||
},
|
},
|
||||||
'personality': {},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,12 +69,6 @@ class BlockDeviceMappingTest(test.TestCase):
|
|||||||
'hello': 'world',
|
'hello': 'world',
|
||||||
'open': 'stack',
|
'open': 'stack',
|
||||||
},
|
},
|
||||||
'personality': [
|
|
||||||
{
|
|
||||||
'path': '/etc/banner.txt',
|
|
||||||
'contents': 'MQ==',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,7 +225,6 @@ class ServersControllerCreateTest(test.TestCase):
|
|||||||
'hello': 'world',
|
'hello': 'world',
|
||||||
'open': 'stack',
|
'open': 'stack',
|
||||||
},
|
},
|
||||||
'personality': {},
|
|
||||||
config_drive.ATTRIBUTE_NAME: "true",
|
config_drive.ATTRIBUTE_NAME: "true",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -251,7 +250,6 @@ class ServersControllerCreateTest(test.TestCase):
|
|||||||
'hello': 'world',
|
'hello': 'world',
|
||||||
'open': 'stack',
|
'open': 'stack',
|
||||||
},
|
},
|
||||||
'personality': {},
|
|
||||||
config_drive.ATTRIBUTE_NAME: image_href,
|
config_drive.ATTRIBUTE_NAME: image_href,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -276,7 +274,6 @@ class ServersControllerCreateTest(test.TestCase):
|
|||||||
'hello': 'world',
|
'hello': 'world',
|
||||||
'open': 'stack',
|
'open': 'stack',
|
||||||
},
|
},
|
||||||
'personality': {},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,7 +316,6 @@ class ServersControllerCreateTest(test.TestCase):
|
|||||||
'flavor_ref': flavor_ref,
|
'flavor_ref': flavor_ref,
|
||||||
'metadata': {'hello': 'world',
|
'metadata': {'hello': 'world',
|
||||||
'open': 'stack'},
|
'open': 'stack'},
|
||||||
'personality': []
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -344,7 +343,6 @@ class ServersControllerCreateTest(test.TestCase):
|
|||||||
'flavor_ref': flavor_ref,
|
'flavor_ref': flavor_ref,
|
||||||
'metadata': {'hello': 'world',
|
'metadata': {'hello': 'world',
|
||||||
'open': 'stack'},
|
'open': 'stack'},
|
||||||
'personality': []
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -380,7 +378,6 @@ class ServersControllerCreateTest(test.TestCase):
|
|||||||
'flavor_ref': flavor_ref,
|
'flavor_ref': flavor_ref,
|
||||||
'metadata': {'hello': 'world',
|
'metadata': {'hello': 'world',
|
||||||
'open': 'stack'},
|
'open': 'stack'},
|
||||||
'personality': [],
|
|
||||||
multiple_create.RRID_ATTRIBUTE_NAME: True
|
multiple_create.RRID_ATTRIBUTE_NAME: True
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -453,7 +450,6 @@ class ServersControllerCreateTest(test.TestCase):
|
|||||||
'flavor_ref': flavor_ref,
|
'flavor_ref': flavor_ref,
|
||||||
'metadata': {'hello': 'world',
|
'metadata': {'hello': 'world',
|
||||||
'open': 'stack'},
|
'open': 'stack'},
|
||||||
'personality': []
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -475,7 +471,6 @@ class ServersControllerCreateTest(test.TestCase):
|
|||||||
'flavor_ref': flavor_ref,
|
'flavor_ref': flavor_ref,
|
||||||
'metadata': {'hello': 'world',
|
'metadata': {'hello': 'world',
|
||||||
'open': 'stack'},
|
'open': 'stack'},
|
||||||
'personality': []
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,541 +0,0 @@
|
|||||||
# Copyright 2013 IBM Corp.
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
from oslo.config import cfg
|
|
||||||
import webob
|
|
||||||
|
|
||||||
from nova.api.openstack.compute import plugins
|
|
||||||
from nova.api.openstack.compute.plugins.v3 import servers
|
|
||||||
from nova.compute import api as compute_api
|
|
||||||
from nova.compute import flavors
|
|
||||||
from nova import db
|
|
||||||
from nova.network import manager
|
|
||||||
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
|
|
||||||
from nova.tests.image import fake
|
|
||||||
from nova.tests import matchers
|
|
||||||
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
FAKE_UUID = fakes.FAKE_UUID
|
|
||||||
|
|
||||||
|
|
||||||
def fake_gen_uuid():
|
|
||||||
return FAKE_UUID
|
|
||||||
|
|
||||||
|
|
||||||
def return_security_group(context, instance_id, security_group_id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
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-personality',
|
|
||||||
'osapi_v3')
|
|
||||||
self.no_personality_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),
|
|
||||||
"config_drive": None,
|
|
||||||
"progress": 0,
|
|
||||||
"fixed_ips": [],
|
|
||||||
"task_state": "",
|
|
||||||
"vm_state": "",
|
|
||||||
"security_groups": inst['security_groups'],
|
|
||||||
})
|
|
||||||
|
|
||||||
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)
|
|
||||||
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)
|
|
||||||
|
|
||||||
return_server = fakes.fake_instance_get()
|
|
||||||
return_servers = fakes.fake_instance_get_all_by_filters()
|
|
||||||
self.stubs.Set(db, 'instance_get_all_by_filters',
|
|
||||||
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)
|
|
||||||
|
|
||||||
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_personality_disabled(self):
|
|
||||||
params = {
|
|
||||||
'personality': [
|
|
||||||
{
|
|
||||||
"path": "/etc/banner.txt",
|
|
||||||
"contents": "MQ==",
|
|
||||||
}]}
|
|
||||||
old_create = compute_api.API.create
|
|
||||||
|
|
||||||
def create(*args, **kwargs):
|
|
||||||
self.assertNotIn('injected_files', kwargs)
|
|
||||||
return old_create(*args, **kwargs)
|
|
||||||
|
|
||||||
self.stubs.Set(compute_api.API, 'create', create)
|
|
||||||
self._test_create_extra(params,
|
|
||||||
override_controller=self.no_personality_controller)
|
|
||||||
|
|
||||||
def test_create_instance_with_personality_enabled(self):
|
|
||||||
params = {
|
|
||||||
'personality': [
|
|
||||||
{
|
|
||||||
"path": "/etc/banner.txt",
|
|
||||||
"contents": "MQ==",
|
|
||||||
}]}
|
|
||||||
old_create = compute_api.API.create
|
|
||||||
|
|
||||||
def create(*args, **kwargs):
|
|
||||||
self.assertIn('injected_files', kwargs)
|
|
||||||
return old_create(*args, **kwargs)
|
|
||||||
|
|
||||||
self.stubs.Set(compute_api.API, 'create', create)
|
|
||||||
self._test_create_extra(params)
|
|
||||||
|
|
||||||
def test_create_instance_with_personality(self):
|
|
||||||
image_href = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
|
||||||
flavor_ref = 'http://localhost/flavors/3'
|
|
||||||
value = "A random string"
|
|
||||||
body = {
|
|
||||||
'server': {
|
|
||||||
'name': 'user_data_test',
|
|
||||||
'image_ref': image_href,
|
|
||||||
'flavor_ref': flavor_ref,
|
|
||||||
'metadata': {
|
|
||||||
'hello': 'world',
|
|
||||||
'open': 'stack',
|
|
||||||
},
|
|
||||||
'personality': [
|
|
||||||
{
|
|
||||||
"path": "/etc/banner.txt",
|
|
||||||
"contents": "MQ==",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
old_create = compute_api.API.create
|
|
||||||
|
|
||||||
def create(*args, **kwargs):
|
|
||||||
self.assertIn('injected_files', kwargs)
|
|
||||||
return old_create(*args, **kwargs)
|
|
||||||
|
|
||||||
self.stubs.Set(compute_api.API, 'create', create)
|
|
||||||
|
|
||||||
req = fakes.HTTPRequestV3.blank('/servers')
|
|
||||||
req.method = 'POST'
|
|
||||||
req.body = jsonutils.dumps(body)
|
|
||||||
req.headers["content-type"] = "application/json"
|
|
||||||
res = self.controller.create(req, body).obj
|
|
||||||
|
|
||||||
server = res['server']
|
|
||||||
self.assertEqual(FAKE_UUID, server['id'])
|
|
||||||
|
|
||||||
def test_rebuild_instance_with_personality_disabled(self):
|
|
||||||
image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
|
||||||
image_href = 'http://localhost/v3/images/%s' % image_uuid
|
|
||||||
access_ipv4 = '0.0.0.0'
|
|
||||||
access_ipv6 = 'fead::1234'
|
|
||||||
body = {
|
|
||||||
'rebuild': {
|
|
||||||
'name': 'new_name',
|
|
||||||
'image_ref': image_href,
|
|
||||||
'access_ip_v4': access_ipv4,
|
|
||||||
'access_ip_v6': access_ipv6,
|
|
||||||
'metadata': {
|
|
||||||
'hello': 'world',
|
|
||||||
'open': 'stack',
|
|
||||||
},
|
|
||||||
'personality': [
|
|
||||||
{
|
|
||||||
"path": "/etc/banner.txt",
|
|
||||||
"contents": "MQ==",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
self.rebuild_called = True
|
|
||||||
|
|
||||||
def rebuild(*args, **kwargs):
|
|
||||||
self.assertNotIn('files_to_inject', kwargs)
|
|
||||||
self.rebuild_called = True
|
|
||||||
|
|
||||||
self.stubs.Set(compute_api.API, 'rebuild', rebuild)
|
|
||||||
req = fakes.HTTPRequestV3.blank(
|
|
||||||
'/servers/%s/action' % FAKE_UUID)
|
|
||||||
req.method = 'POST'
|
|
||||||
req.body = jsonutils.dumps(body)
|
|
||||||
req.headers["content-type"] = "application/json"
|
|
||||||
res = self.no_personality_controller._action_rebuild(req,
|
|
||||||
FAKE_UUID,
|
|
||||||
body).obj
|
|
||||||
self.assertTrue(self.rebuild_called)
|
|
||||||
|
|
||||||
def test_rebuild_instance_with_personality(self):
|
|
||||||
image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
|
||||||
image_href = 'http://localhost/v3/images/%s' % image_uuid
|
|
||||||
access_ipv4 = '0.0.0.0'
|
|
||||||
access_ipv6 = 'fead::1234'
|
|
||||||
body = {
|
|
||||||
'rebuild': {
|
|
||||||
'name': 'new_name',
|
|
||||||
'image_ref': image_href,
|
|
||||||
'access_ip_v4': access_ipv4,
|
|
||||||
'access_ip_v6': access_ipv6,
|
|
||||||
'metadata': {
|
|
||||||
'hello': 'world',
|
|
||||||
'open': 'stack',
|
|
||||||
},
|
|
||||||
'personality': [
|
|
||||||
{
|
|
||||||
"path": "/etc/banner.txt",
|
|
||||||
"contents": "MQ==",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
self.rebuild_called = False
|
|
||||||
|
|
||||||
def rebuild(*args, **kwargs):
|
|
||||||
self.assertIn('files_to_inject', kwargs)
|
|
||||||
self.assertIn(('/etc/banner.txt', 'MQ=='),
|
|
||||||
kwargs['files_to_inject'])
|
|
||||||
self.rebuild_called = True
|
|
||||||
|
|
||||||
self.stubs.Set(compute_api.API, 'rebuild', rebuild)
|
|
||||||
req = fakes.HTTPRequestV3.blank(
|
|
||||||
'/servers/%s/action' % FAKE_UUID)
|
|
||||||
req.method = 'POST'
|
|
||||||
req.body = jsonutils.dumps(body)
|
|
||||||
req.headers["content-type"] = "application/json"
|
|
||||||
res = self.controller._action_rebuild(req,
|
|
||||||
FAKE_UUID,
|
|
||||||
body).obj
|
|
||||||
self.assertTrue(self.rebuild_called)
|
|
||||||
|
|
||||||
def test_rebuild_bad_personality(self):
|
|
||||||
image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
|
||||||
image_href = 'http://localhost/v3/images/%s' % image_uuid
|
|
||||||
body = {
|
|
||||||
"rebuild": {
|
|
||||||
"image_ref": image_href,
|
|
||||||
"personality": [{
|
|
||||||
"path": "/path/to/file",
|
|
||||||
"contents": "INVALID b64",
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
req = fakes.HTTPRequestV3.blank(
|
|
||||||
'/servers/%s/action' % FAKE_UUID)
|
|
||||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
||||||
self.controller._action_rebuild,
|
|
||||||
req, FAKE_UUID, body)
|
|
||||||
|
|
||||||
def test_rebuild_instance_without_personality(self):
|
|
||||||
image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
|
||||||
image_href = 'http://localhost/v3/images/%s' % image_uuid
|
|
||||||
access_ipv4 = '0.0.0.0'
|
|
||||||
access_ipv6 = 'fead::1234'
|
|
||||||
body = {
|
|
||||||
'rebuild': {
|
|
||||||
'name': 'new_name',
|
|
||||||
'image_ref': image_href,
|
|
||||||
'access_ip_v4': access_ipv4,
|
|
||||||
'access_ip_v6': access_ipv6,
|
|
||||||
'metadata': {
|
|
||||||
'hello': 'world',
|
|
||||||
'open': 'stack',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
self.rebuild_called = False
|
|
||||||
|
|
||||||
def rebuild(*args, **kwargs):
|
|
||||||
self.assertNotIn('files_to_inject', kwargs)
|
|
||||||
self.rebuild_called = True
|
|
||||||
|
|
||||||
self.stubs.Set(compute_api.API, 'rebuild', rebuild)
|
|
||||||
req = fakes.HTTPRequestV3.blank(
|
|
||||||
'/servers/%s/action' % FAKE_UUID)
|
|
||||||
req.method = 'POST'
|
|
||||||
req.body = jsonutils.dumps(body)
|
|
||||||
req.headers["content-type"] = "application/json"
|
|
||||||
res = self.controller._action_rebuild(req,
|
|
||||||
FAKE_UUID,
|
|
||||||
body).obj
|
|
||||||
self.assertTrue(self.rebuild_called)
|
|
||||||
|
|
||||||
def test_create_instance_invalid_personality(self):
|
|
||||||
image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
|
||||||
flavor_ref = 'http://localhost/flavors/3'
|
|
||||||
|
|
||||||
def fake_create(*args, **kwargs):
|
|
||||||
codec = 'utf8'
|
|
||||||
content = 'b25zLiINCg0KLVJpY2hhcmQgQ$$%QQmFjaA=='
|
|
||||||
start_position = 19
|
|
||||||
end_position = 20
|
|
||||||
msg = 'invalid start byte'
|
|
||||||
raise UnicodeDecodeError(codec, content, start_position,
|
|
||||||
end_position, msg)
|
|
||||||
|
|
||||||
body = {
|
|
||||||
'server': {
|
|
||||||
'min_count': 2,
|
|
||||||
'name': 'server_test',
|
|
||||||
'image_ref': image_uuid,
|
|
||||||
'flavor_ref': flavor_ref,
|
|
||||||
'metadata': {
|
|
||||||
'hello': 'world',
|
|
||||||
'open': 'stack',
|
|
||||||
},
|
|
||||||
'personality': [
|
|
||||||
{
|
|
||||||
"path": "/etc/banner.txt",
|
|
||||||
"contents": "b25zLiINCg0KLVJpY2hhcmQgQ$$%QQmFjaA==",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
self.stubs.Set(compute_api.API,
|
|
||||||
'create',
|
|
||||||
fake_create)
|
|
||||||
|
|
||||||
req = fakes.HTTPRequestV3.blank(
|
|
||||||
'/servers/%s/action' % FAKE_UUID)
|
|
||||||
req.method = 'POST'
|
|
||||||
req.body = jsonutils.dumps(body)
|
|
||||||
req.headers["content-type"] = "application/json"
|
|
||||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
||||||
self.controller.create, req, body)
|
|
||||||
|
|
||||||
|
|
||||||
class TestServerRebuildRequestXMLDeserializer(test.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestServerRebuildRequestXMLDeserializer, self).setUp()
|
|
||||||
ext_info = plugins.LoadedExtensionInfo()
|
|
||||||
controller = servers.ServersController(extension_info=ext_info)
|
|
||||||
self.deserializer = servers.ActionDeserializer(controller)
|
|
||||||
self.create_deserializer = servers.CreateDeserializer(controller)
|
|
||||||
|
|
||||||
def test_rebuild_request_with_personality(self):
|
|
||||||
serial_request = """
|
|
||||||
<rebuild
|
|
||||||
xmlns="http://docs.openstack.org/compute/api/v3"
|
|
||||||
name="foobar"
|
|
||||||
image_ref="1">
|
|
||||||
<metadata>
|
|
||||||
<meta key="My Server Name">Apache1</meta>
|
|
||||||
</metadata>
|
|
||||||
<personality>
|
|
||||||
<file path="/etc/banner.txt">MQ==</file>
|
|
||||||
</personality>
|
|
||||||
</rebuild>
|
|
||||||
"""
|
|
||||||
request = self.deserializer.deserialize(serial_request)
|
|
||||||
expected = {
|
|
||||||
"rebuild": {
|
|
||||||
"image_ref": "1",
|
|
||||||
"name": "foobar",
|
|
||||||
"metadata": {
|
|
||||||
"My Server Name": "Apache1"
|
|
||||||
},
|
|
||||||
"personality": [{
|
|
||||||
"path": "/etc/banner.txt",
|
|
||||||
"contents": "MQ=="}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.assertEqual(request['body'], expected)
|
|
||||||
|
|
||||||
def test_create_request_with_personality(self):
|
|
||||||
serial_request = """
|
|
||||||
<server xmlns="http://docs.openstack.org/compute/api/v3"
|
|
||||||
image_ref="1"
|
|
||||||
flavor_ref="2"
|
|
||||||
name="new-server-test">
|
|
||||||
<metadata>
|
|
||||||
<meta key="My Server Name">Apache1</meta>
|
|
||||||
</metadata>
|
|
||||||
<personality>
|
|
||||||
<file path="/etc/banner.txt">MQ==</file>
|
|
||||||
</personality>
|
|
||||||
</server>
|
|
||||||
"""
|
|
||||||
request = self.create_deserializer.deserialize(serial_request)
|
|
||||||
expected = {
|
|
||||||
"server": {
|
|
||||||
"flavor_ref": "2",
|
|
||||||
"image_ref": "1",
|
|
||||||
"metadata": {
|
|
||||||
"My Server Name": "Apache1"
|
|
||||||
},
|
|
||||||
"name": "new-server-test",
|
|
||||||
"personality": [
|
|
||||||
{
|
|
||||||
"contents": "MQ==",
|
|
||||||
"path": "/etc/banner.txt"
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.assertEqual(request['body'], expected)
|
|
||||||
|
|
||||||
def test_empty_metadata_personality(self):
|
|
||||||
serial_request = """
|
|
||||||
<server xmlns="http://docs.openstack.org/compute/api/v2"
|
|
||||||
name="new-server-test"
|
|
||||||
image_ref="1"
|
|
||||||
flavor_ref="2">
|
|
||||||
<metadata/>
|
|
||||||
<personality/>
|
|
||||||
</server>"""
|
|
||||||
request = self.create_deserializer.deserialize(serial_request)
|
|
||||||
expected = {
|
|
||||||
"server": {
|
|
||||||
"name": "new-server-test",
|
|
||||||
"image_ref": "1",
|
|
||||||
"flavor_ref": "2",
|
|
||||||
"metadata": {},
|
|
||||||
"personality": [],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
self.assertEqual(request['body'], expected)
|
|
||||||
|
|
||||||
def test_multiple_personality_files(self):
|
|
||||||
serial_request = """
|
|
||||||
<server xmlns="http://docs.openstack.org/compute/api/v2"
|
|
||||||
name="new-server-test"
|
|
||||||
image_ref="1"
|
|
||||||
flavor_ref="2">
|
|
||||||
<personality>
|
|
||||||
<file path="/etc/banner.txt">MQ==</file>
|
|
||||||
<file path="/etc/hosts">Mg==</file>
|
|
||||||
</personality>
|
|
||||||
</server>"""
|
|
||||||
request = self.create_deserializer.deserialize(serial_request)
|
|
||||||
expected = {
|
|
||||||
"server": {
|
|
||||||
"name": "new-server-test",
|
|
||||||
"image_ref": "1",
|
|
||||||
"flavor_ref": "2",
|
|
||||||
"personality": [
|
|
||||||
{"path": "/etc/banner.txt", "contents": "MQ=="},
|
|
||||||
{"path": "/etc/hosts", "contents": "Mg=="},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
self.assertThat(request['body'], matchers.DictMatches(expected))
|
|
@ -13,7 +13,6 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import base64
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
import mox
|
import mox
|
||||||
@ -348,22 +347,6 @@ class ServerActionsControllerTest(test.TestCase):
|
|||||||
self.controller._action_rebuild,
|
self.controller._action_rebuild,
|
||||||
req, FAKE_UUID, body)
|
req, FAKE_UUID, body)
|
||||||
|
|
||||||
def test_rebuild_personality(self):
|
|
||||||
body = {
|
|
||||||
"rebuild": {
|
|
||||||
"image_ref": self._image_href,
|
|
||||||
"personality": [{
|
|
||||||
"path": "/path/to/file",
|
|
||||||
"contents": base64.b64encode("Test String"),
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
req = fakes.HTTPRequestV3.blank(self.url)
|
|
||||||
body = self.controller._action_rebuild(req, FAKE_UUID, body).obj
|
|
||||||
|
|
||||||
self.assertNotIn('personality', body['server'])
|
|
||||||
|
|
||||||
def test_rebuild_admin_password(self):
|
def test_rebuild_admin_password(self):
|
||||||
return_server = fakes.fake_instance_get(image_ref='2',
|
return_server = fakes.fake_instance_get(image_ref='2',
|
||||||
vm_state=vm_states.ACTIVE, host='fake_host')
|
vm_state=vm_states.ACTIVE, host='fake_host')
|
||||||
@ -1081,9 +1064,6 @@ class TestServerActionXMLDeserializer(test.TestCase):
|
|||||||
<metadata>
|
<metadata>
|
||||||
<meta key="My Server Name">Apache1</meta>
|
<meta key="My Server Name">Apache1</meta>
|
||||||
</metadata>
|
</metadata>
|
||||||
<personality>
|
|
||||||
<file path="/etc/banner.txt">Mg==</file>
|
|
||||||
</personality>
|
|
||||||
</rebuild>"""
|
</rebuild>"""
|
||||||
request = self.deserializer.deserialize(serial_request, 'action')
|
request = self.deserializer.deserialize(serial_request, 'action')
|
||||||
expected = {
|
expected = {
|
||||||
@ -1093,9 +1073,6 @@ class TestServerActionXMLDeserializer(test.TestCase):
|
|||||||
"metadata": {
|
"metadata": {
|
||||||
"My Server Name": "Apache1",
|
"My Server Name": "Apache1",
|
||||||
},
|
},
|
||||||
"personality": [
|
|
||||||
{"path": "/etc/banner.txt", "contents": "Mg=="},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
self.assertThat(request['body'], matchers.DictMatches(expected))
|
self.assertThat(request['body'], matchers.DictMatches(expected))
|
||||||
@ -1121,9 +1098,6 @@ class TestServerActionXMLDeserializer(test.TestCase):
|
|||||||
<metadata>
|
<metadata>
|
||||||
<meta key="My Server Name">Apache1</meta>
|
<meta key="My Server Name">Apache1</meta>
|
||||||
</metadata>
|
</metadata>
|
||||||
<personality>
|
|
||||||
<file path="/etc/banner.txt">Mg==</file>
|
|
||||||
</personality>
|
|
||||||
</rebuild>"""
|
</rebuild>"""
|
||||||
self.assertRaises(AttributeError,
|
self.assertRaises(AttributeError,
|
||||||
self.deserializer.deserialize,
|
self.deserializer.deserialize,
|
||||||
|
@ -2070,13 +2070,6 @@ class ServersControllerCreateTest(test.TestCase):
|
|||||||
'hello': 'world',
|
'hello': 'world',
|
||||||
'open': 'stack',
|
'open': 'stack',
|
||||||
},
|
},
|
||||||
'personality': [
|
|
||||||
{
|
|
||||||
"path": "/etc/banner.txt",
|
|
||||||
"contents": "MQ==",
|
|
||||||
},
|
|
||||||
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,7 +197,6 @@ class ServersControllerCreateTest(test.TestCase):
|
|||||||
'hello': 'world',
|
'hello': 'world',
|
||||||
'open': 'stack',
|
'open': 'stack',
|
||||||
},
|
},
|
||||||
'personality': {},
|
|
||||||
user_data.ATTRIBUTE_NAME: base64.b64encode(value),
|
user_data.ATTRIBUTE_NAME: base64.b64encode(value),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -224,7 +223,6 @@ class ServersControllerCreateTest(test.TestCase):
|
|||||||
'hello': 'world',
|
'hello': 'world',
|
||||||
'open': 'stack',
|
'open': 'stack',
|
||||||
},
|
},
|
||||||
'personality': {},
|
|
||||||
user_data.ATTRIBUTE_NAME: value,
|
user_data.ATTRIBUTE_NAME: value,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,6 @@ nova.api.v3.extensions.server.create =
|
|||||||
disk_config = nova.api.openstack.compute.plugins.v3.disk_config:DiskConfig
|
disk_config = nova.api.openstack.compute.plugins.v3.disk_config:DiskConfig
|
||||||
keypairs_create = nova.api.openstack.compute.plugins.v3.keypairs:Keypairs
|
keypairs_create = nova.api.openstack.compute.plugins.v3.keypairs:Keypairs
|
||||||
multiple_create = nova.api.openstack.compute.plugins.v3.multiple_create:MultipleCreate
|
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
|
scheduler_hints = nova.api.openstack.compute.plugins.v3.scheduler_hints:SchedulerHints
|
||||||
security_groups = nova.api.openstack.compute.plugins.v3.security_groups:SecurityGroups
|
security_groups = nova.api.openstack.compute.plugins.v3.security_groups:SecurityGroups
|
||||||
user_data = nova.api.openstack.compute.plugins.v3.user_data:UserData
|
user_data = nova.api.openstack.compute.plugins.v3.user_data:UserData
|
||||||
@ -129,7 +128,6 @@ nova.api.v3.extensions.server.create.deserialize =
|
|||||||
config_drive = nova.api.openstack.compute.plugins.v3.config_drive:ConfigDrive
|
config_drive = nova.api.openstack.compute.plugins.v3.config_drive:ConfigDrive
|
||||||
disk_config = nova.api.openstack.compute.plugins.v3.disk_config:DiskConfig
|
disk_config = nova.api.openstack.compute.plugins.v3.disk_config:DiskConfig
|
||||||
multiple_create = nova.api.openstack.compute.plugins.v3.multiple_create:MultipleCreate
|
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
|
scheduler_hints = nova.api.openstack.compute.plugins.v3.scheduler_hints:SchedulerHints
|
||||||
security_groups = nova.api.openstack.compute.plugins.v3.security_groups:SecurityGroups
|
security_groups = nova.api.openstack.compute.plugins.v3.security_groups:SecurityGroups
|
||||||
user_data = nova.api.openstack.compute.plugins.v3.user_data:UserData
|
user_data = nova.api.openstack.compute.plugins.v3.user_data:UserData
|
||||||
@ -137,12 +135,10 @@ nova.api.v3.extensions.server.create.deserialize =
|
|||||||
nova.api.v3.extensions.server.rebuild =
|
nova.api.v3.extensions.server.rebuild =
|
||||||
access_ips = nova.api.openstack.compute.plugins.v3.access_ips:AccessIPs
|
access_ips = nova.api.openstack.compute.plugins.v3.access_ips:AccessIPs
|
||||||
disk_config = nova.api.openstack.compute.plugins.v3.disk_config:DiskConfig
|
disk_config = nova.api.openstack.compute.plugins.v3.disk_config:DiskConfig
|
||||||
personalities = nova.api.openstack.compute.plugins.v3.personalities:Personalities
|
|
||||||
|
|
||||||
nova.api.v3.extensions.server.rebuild.deserialize =
|
nova.api.v3.extensions.server.rebuild.deserialize =
|
||||||
access_ips = nova.api.openstack.compute.plugins.v3.access_ips:AccessIPs
|
access_ips = nova.api.openstack.compute.plugins.v3.access_ips:AccessIPs
|
||||||
disk_config = nova.api.openstack.compute.plugins.v3.disk_config:DiskConfig
|
disk_config = nova.api.openstack.compute.plugins.v3.disk_config:DiskConfig
|
||||||
personalities = nova.api.openstack.compute.plugins.v3.personalities:Personalities
|
|
||||||
|
|
||||||
nova.api.v3.extensions.server.resize =
|
nova.api.v3.extensions.server.resize =
|
||||||
disk_config = nova.api.openstack.compute.plugins.v3.disk_config:DiskConfig
|
disk_config = nova.api.openstack.compute.plugins.v3.disk_config:DiskConfig
|
||||||
|
Loading…
Reference in New Issue
Block a user