7447 lines
319 KiB
Python
7447 lines
319 KiB
Python
# Copyright 2010-2011 OpenStack Foundation
|
|
# Copyright 2011 Piston Cloud Computing, 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.
|
|
|
|
import collections
|
|
import datetime
|
|
import ddt
|
|
|
|
import fixtures
|
|
import iso8601
|
|
import mock
|
|
from oslo_policy import policy as oslo_policy
|
|
from oslo_serialization import base64
|
|
from oslo_serialization import jsonutils
|
|
from oslo_utils import encodeutils
|
|
from oslo_utils import fixture as utils_fixture
|
|
from oslo_utils.fixture import uuidsentinel as uuids
|
|
from oslo_utils import timeutils
|
|
from oslo_utils import uuidutils
|
|
import six
|
|
from six.moves import range
|
|
import six.moves.urllib.parse as urlparse
|
|
import testtools
|
|
import webob
|
|
|
|
from nova.api.openstack import api_version_request
|
|
from nova.api.openstack import common
|
|
from nova.api.openstack import compute
|
|
from nova.api.openstack.compute import ips
|
|
from nova.api.openstack.compute import servers
|
|
from nova.api.openstack.compute import views
|
|
from nova.api.openstack import wsgi as os_wsgi
|
|
from nova import availability_zones
|
|
from nova import block_device
|
|
from nova.compute import api as compute_api
|
|
from nova.compute import flavors
|
|
from nova.compute import task_states
|
|
from nova.compute import vm_states
|
|
import nova.conf
|
|
from nova import context
|
|
from nova.db import api as db
|
|
from nova.db.sqlalchemy import api as db_api
|
|
from nova.db.sqlalchemy import models
|
|
from nova import exception
|
|
from nova.image import glance
|
|
from nova import objects
|
|
from nova.objects import instance as instance_obj
|
|
from nova.objects import tag
|
|
from nova.policies import servers as server_policies
|
|
from nova import policy
|
|
from nova import test
|
|
from nova.tests import fixtures as nova_fixtures
|
|
from nova.tests.unit.api.openstack import fakes
|
|
from nova.tests.unit import fake_flavor
|
|
from nova.tests.unit import fake_instance
|
|
from nova.tests.unit import fake_network
|
|
from nova.tests.unit.image import fake
|
|
from nova.tests.unit import matchers
|
|
from nova import utils as nova_utils
|
|
|
|
|
|
CONF = nova.conf.CONF
|
|
|
|
FAKE_UUID = fakes.FAKE_UUID
|
|
|
|
UUID1 = '00000000-0000-0000-0000-000000000001'
|
|
UUID2 = '00000000-0000-0000-0000-000000000002'
|
|
|
|
INSTANCE_IDS = {FAKE_UUID: 1}
|
|
FIELDS = instance_obj.INSTANCE_DEFAULT_FIELDS
|
|
|
|
|
|
def instance_update_and_get_original(context, instance_uuid, values,
|
|
columns_to_join=None,
|
|
):
|
|
inst = fakes.stub_instance(INSTANCE_IDS.get(instance_uuid),
|
|
name=values.get('display_name'))
|
|
inst = dict(inst, **values)
|
|
return (inst, inst)
|
|
|
|
|
|
def instance_update(context, instance_uuid, values):
|
|
inst = fakes.stub_instance(INSTANCE_IDS.get(instance_uuid),
|
|
name=values.get('display_name'))
|
|
inst = dict(inst, **values)
|
|
return inst
|
|
|
|
|
|
def fake_compute_api(cls, req, id):
|
|
return True
|
|
|
|
|
|
def fake_start_stop_not_ready(self, context, instance):
|
|
raise exception.InstanceNotReady(instance_id=instance["uuid"])
|
|
|
|
|
|
def fake_start_stop_invalid_state(self, context, instance):
|
|
raise exception.InstanceInvalidState(
|
|
instance_uuid=instance['uuid'], attr='fake_attr',
|
|
method='fake_method', state='fake_state')
|
|
|
|
|
|
def fake_instance_get_by_uuid_not_found(context, uuid,
|
|
columns_to_join, use_slave=False):
|
|
raise exception.InstanceNotFound(instance_id=uuid)
|
|
|
|
|
|
def fake_instance_get_all_with_locked(context, list_locked, **kwargs):
|
|
obj_list = []
|
|
s_id = 0
|
|
for locked in list_locked:
|
|
uuid = fakes.get_fake_uuid(locked)
|
|
s_id = s_id + 1
|
|
kwargs['locked_by'] = None if locked == 'not_locked' else locked
|
|
server = fakes.stub_instance_obj(context, id=s_id, uuid=uuid, **kwargs)
|
|
obj_list.append(server)
|
|
return objects.InstanceList(objects=obj_list)
|
|
|
|
|
|
def fake_instance_get_all_with_description(context, list_desc, **kwargs):
|
|
obj_list = []
|
|
s_id = 0
|
|
for desc in list_desc:
|
|
uuid = fakes.get_fake_uuid(desc)
|
|
s_id = s_id + 1
|
|
kwargs['display_description'] = desc
|
|
server = fakes.stub_instance_obj(context, id=s_id, uuid=uuid, **kwargs)
|
|
obj_list.append(server)
|
|
return objects.InstanceList(objects=obj_list)
|
|
|
|
|
|
def fake_compute_get_empty_az(*args, **kwargs):
|
|
inst = fakes.stub_instance(vm_state=vm_states.ACTIVE,
|
|
availability_zone='')
|
|
return fake_instance.fake_instance_obj(args[1], **inst)
|
|
|
|
|
|
class MockSetAdminPassword(object):
|
|
def __init__(self):
|
|
self.instance_id = None
|
|
self.password = None
|
|
|
|
def __call__(self, context, instance_id, password):
|
|
self.instance_id = instance_id
|
|
self.password = password
|
|
|
|
|
|
class ControllerTest(test.TestCase):
|
|
|
|
def setUp(self):
|
|
super(ControllerTest, self).setUp()
|
|
self.flags(use_ipv6=False)
|
|
fakes.stub_out_key_pair_funcs(self)
|
|
fake.stub_out_image_service(self)
|
|
return_server = fakes.fake_compute_get(id=2, availability_zone='nova',
|
|
launched_at=None,
|
|
terminated_at=None)
|
|
return_servers = fakes.fake_compute_get_all()
|
|
# Server sort keys extension is enabled in v21 so sort data is passed
|
|
# to the instance API and the sorted DB API is invoked
|
|
self.mock_get_all = self.useFixture(fixtures.MockPatchObject(
|
|
compute_api.API, 'get_all', side_effect=return_servers)).mock
|
|
self.mock_get = self.useFixture(fixtures.MockPatchObject(
|
|
compute_api.API, 'get', side_effect=return_server)).mock
|
|
self.stub_out('nova.db.api.instance_update_and_get_original',
|
|
instance_update_and_get_original)
|
|
self.flags(group='glance', api_servers=['http://localhost:9292'])
|
|
|
|
self.controller = servers.ServersController()
|
|
self.ips_controller = ips.IPsController()
|
|
policy.reset()
|
|
policy.init()
|
|
fake_network.stub_out_nw_api_get_instance_nw_info(self)
|
|
|
|
|
|
class ServersControllerTest(ControllerTest):
|
|
wsgi_api_version = os_wsgi.DEFAULT_API_VERSION
|
|
|
|
def req(self, url, use_admin_context=False):
|
|
return fakes.HTTPRequest.blank(url,
|
|
use_admin_context=use_admin_context,
|
|
version=self.wsgi_api_version)
|
|
|
|
@mock.patch('nova.objects.Instance.get_by_uuid')
|
|
@mock.patch('nova.objects.InstanceMapping.get_by_instance_uuid')
|
|
def test_cellsv1_instance_lookup_no_target(self, mock_get_im,
|
|
mock_get_inst):
|
|
self.flags(enable=True, group='cells')
|
|
ctxt = context.RequestContext('fake', 'fake')
|
|
self.controller._get_instance(ctxt, 'foo')
|
|
self.assertFalse(mock_get_im.called)
|
|
self.assertIsNone(ctxt.db_connection)
|
|
|
|
@mock.patch('nova.objects.Instance.get_by_uuid')
|
|
@mock.patch('nova.objects.InstanceMapping.get_by_instance_uuid')
|
|
def test_instance_lookup_targets(self, mock_get_im, mock_get_inst):
|
|
ctxt = context.RequestContext('fake', 'fake')
|
|
mock_get_im.return_value.cell_mapping.database_connection = uuids.cell1
|
|
self.controller._get_instance(ctxt, 'foo')
|
|
mock_get_im.assert_called_once_with(ctxt, 'foo')
|
|
self.assertIsNotNone(ctxt.db_connection)
|
|
|
|
def test_requested_networks_prefix(self):
|
|
self.flags(use_neutron=True)
|
|
uuid = 'br-00000000-0000-0000-0000-000000000000'
|
|
requested_networks = [{'uuid': uuid}]
|
|
res = self.controller._get_requested_networks(requested_networks)
|
|
self.assertIn((uuid, None, None, None), res.as_tuples())
|
|
|
|
def test_requested_networks_neutronv2_enabled_with_port(self):
|
|
self.flags(use_neutron=True)
|
|
port = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee'
|
|
requested_networks = [{'port': port}]
|
|
res = self.controller._get_requested_networks(requested_networks)
|
|
self.assertEqual([(None, None, port, None)], res.as_tuples())
|
|
|
|
def test_requested_networks_neutronv2_enabled_with_network(self):
|
|
self.flags(use_neutron=True)
|
|
network = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
|
requested_networks = [{'uuid': network}]
|
|
res = self.controller._get_requested_networks(requested_networks)
|
|
self.assertEqual([(network, None, None, None)], res.as_tuples())
|
|
|
|
def test_requested_networks_neutronv2_enabled_with_network_and_port(self):
|
|
self.flags(use_neutron=True)
|
|
network = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
|
port = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee'
|
|
requested_networks = [{'uuid': network, 'port': port}]
|
|
res = self.controller._get_requested_networks(requested_networks)
|
|
self.assertEqual([(None, None, port, None)], res.as_tuples())
|
|
|
|
def test_requested_networks_with_duplicate_networks_nova_net(self):
|
|
# duplicate networks are allowed only for nova neutron v2.0
|
|
self.flags(use_neutron=False)
|
|
network = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
|
requested_networks = [{'uuid': network}, {'uuid': network}]
|
|
self.assertRaises(
|
|
webob.exc.HTTPBadRequest,
|
|
self.controller._get_requested_networks,
|
|
requested_networks)
|
|
|
|
def test_requested_networks_with_neutronv2_and_duplicate_networks(self):
|
|
# duplicate networks are allowed only for nova neutron v2.0
|
|
self.flags(use_neutron=True)
|
|
network = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
|
requested_networks = [{'uuid': network}, {'uuid': network}]
|
|
res = self.controller._get_requested_networks(requested_networks)
|
|
self.assertEqual([(network, None, None, None),
|
|
(network, None, None, None)], res.as_tuples())
|
|
|
|
def test_requested_networks_neutronv2_enabled_conflict_on_fixed_ip(self):
|
|
self.flags(use_neutron=True)
|
|
network = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
|
port = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee'
|
|
addr = '10.0.0.1'
|
|
requested_networks = [{'uuid': network,
|
|
'fixed_ip': addr,
|
|
'port': port}]
|
|
self.assertRaises(
|
|
webob.exc.HTTPBadRequest,
|
|
self.controller._get_requested_networks,
|
|
requested_networks)
|
|
|
|
def test_requested_networks_neutronv2_disabled_with_port(self):
|
|
self.flags(use_neutron=False)
|
|
port = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee'
|
|
requested_networks = [{'port': port}]
|
|
self.assertRaises(
|
|
webob.exc.HTTPBadRequest,
|
|
self.controller._get_requested_networks,
|
|
requested_networks)
|
|
|
|
def test_requested_networks_api_enabled_with_v2_subclass(self):
|
|
self.flags(use_neutron=True)
|
|
network = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
|
port = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee'
|
|
requested_networks = [{'uuid': network, 'port': port}]
|
|
res = self.controller._get_requested_networks(requested_networks)
|
|
self.assertEqual([(None, None, port, None)], res.as_tuples())
|
|
|
|
def test_get_server_by_uuid(self):
|
|
req = self.req('/fake/servers/%s' % FAKE_UUID)
|
|
res_dict = self.controller.show(req, FAKE_UUID)
|
|
self.assertEqual(res_dict['server']['id'], FAKE_UUID)
|
|
|
|
def test_get_server_joins(self):
|
|
def fake_get(*args, **kwargs):
|
|
expected_attrs = kwargs['expected_attrs']
|
|
self.assertEqual(['flavor', 'info_cache', 'metadata',
|
|
'numa_topology'], expected_attrs)
|
|
ctxt = context.RequestContext('fake', 'fake')
|
|
return fake_instance.fake_instance_obj(
|
|
ctxt, expected_attrs=expected_attrs)
|
|
|
|
self.mock_get.side_effect = fake_get
|
|
|
|
req = self.req('/fake/servers/%s' % FAKE_UUID)
|
|
self.controller.show(req, FAKE_UUID)
|
|
|
|
def test_unique_host_id(self):
|
|
"""Create two servers with the same host and different
|
|
project_ids and check that the host_id's are unique.
|
|
"""
|
|
def return_instance_with_host(context, *args, **kwargs):
|
|
project_id = uuidutils.generate_uuid()
|
|
return fakes.stub_instance_obj(context, id=1, uuid=FAKE_UUID,
|
|
project_id=project_id,
|
|
host='fake_host')
|
|
|
|
req = self.req('/fake/servers/%s' % FAKE_UUID)
|
|
self.mock_get.side_effect = return_instance_with_host
|
|
server1 = self.controller.show(req, FAKE_UUID)
|
|
server2 = self.controller.show(req, FAKE_UUID)
|
|
|
|
self.assertNotEqual(server1['server']['hostId'],
|
|
server2['server']['hostId'])
|
|
|
|
def _get_server_data_dict(self, uuid, image_bookmark, flavor_bookmark,
|
|
status="ACTIVE", progress=100):
|
|
return {
|
|
"server": {
|
|
"id": uuid,
|
|
"user_id": "fake_user",
|
|
"tenant_id": "fake_project",
|
|
"updated": "2010-11-11T11:00:00Z",
|
|
"created": "2010-10-10T12:00:00Z",
|
|
"progress": progress,
|
|
"name": "server2",
|
|
"status": status,
|
|
"hostId": '',
|
|
"image": {
|
|
"id": "10",
|
|
"links": [
|
|
{
|
|
"rel": "bookmark",
|
|
"href": image_bookmark,
|
|
},
|
|
],
|
|
},
|
|
"flavor": {
|
|
"id": "2",
|
|
"links": [
|
|
{
|
|
"rel": "bookmark",
|
|
"href": flavor_bookmark,
|
|
},
|
|
],
|
|
},
|
|
"addresses": {
|
|
'test1': [
|
|
{'version': 4, 'addr': '192.168.1.100',
|
|
'OS-EXT-IPS:type': 'fixed',
|
|
'OS-EXT-IPS-MAC:mac_addr': 'aa:aa:aa:aa:aa:aa'},
|
|
{'version': 6, 'addr': '2001:db8:0:1::1',
|
|
'OS-EXT-IPS:type': 'fixed',
|
|
'OS-EXT-IPS-MAC:mac_addr': 'aa:aa:aa:aa:aa:aa'}
|
|
]
|
|
},
|
|
"metadata": {
|
|
"seq": "2",
|
|
},
|
|
"links": [
|
|
{
|
|
"rel": "self",
|
|
"href": "http://localhost/v2/fake/servers/%s" % uuid,
|
|
},
|
|
{
|
|
"rel": "bookmark",
|
|
"href": "http://localhost/fake/servers/%s" % uuid,
|
|
},
|
|
],
|
|
"OS-DCF:diskConfig": "MANUAL",
|
|
"accessIPv4": '',
|
|
"accessIPv6": '',
|
|
"OS-EXT-AZ:availability_zone": "nova",
|
|
"config_drive": None,
|
|
"OS-EXT-SRV-ATTR:host": None,
|
|
"OS-EXT-SRV-ATTR:hypervisor_hostname": None,
|
|
"OS-EXT-SRV-ATTR:instance_name": "instance-00000002",
|
|
"key_name": '',
|
|
"OS-SRV-USG:launched_at": None,
|
|
"OS-SRV-USG:terminated_at": None
|
|
}
|
|
}
|
|
|
|
def test_get_server_by_id(self):
|
|
self.flags(use_ipv6=True)
|
|
image_bookmark = "http://localhost/fake/images/10"
|
|
flavor_bookmark = "http://localhost/fake/flavors/2"
|
|
|
|
uuid = FAKE_UUID
|
|
req = self.req('/v2/fake/servers/%s' % uuid)
|
|
res_dict = self.controller.show(req, uuid)
|
|
|
|
expected_server = self._get_server_data_dict(uuid,
|
|
image_bookmark,
|
|
flavor_bookmark,
|
|
progress=0)
|
|
self.assertThat(res_dict, matchers.DictMatches(expected_server))
|
|
|
|
def test_get_server_empty_az(self):
|
|
self.mock_get.side_effect = fakes.fake_compute_get(
|
|
availability_zone='')
|
|
uuid = FAKE_UUID
|
|
req = self.req('/v2/fake/servers/%s' % uuid)
|
|
res_dict = self.controller.show(req, uuid)
|
|
self.assertEqual(res_dict['server']['OS-EXT-AZ:availability_zone'], '')
|
|
|
|
def test_get_server_with_active_status_by_id(self):
|
|
image_bookmark = "http://localhost/fake/images/10"
|
|
flavor_bookmark = "http://localhost/fake/flavors/2"
|
|
|
|
req = self.req('/fake/servers/%s' % FAKE_UUID)
|
|
res_dict = self.controller.show(req, FAKE_UUID)
|
|
expected_server = self._get_server_data_dict(FAKE_UUID,
|
|
image_bookmark,
|
|
flavor_bookmark,
|
|
progress=0)
|
|
self.assertThat(res_dict, matchers.DictMatches(expected_server))
|
|
self.mock_get.assert_called_once_with(
|
|
req.environ['nova.context'], FAKE_UUID,
|
|
expected_attrs=['flavor', 'info_cache', 'metadata',
|
|
'numa_topology'])
|
|
|
|
def test_get_server_with_id_image_ref_by_id(self):
|
|
image_bookmark = "http://localhost/fake/images/10"
|
|
flavor_bookmark = "http://localhost/fake/flavors/2"
|
|
|
|
req = self.req('/fake/servers/%s' % FAKE_UUID)
|
|
res_dict = self.controller.show(req, FAKE_UUID)
|
|
expected_server = self._get_server_data_dict(FAKE_UUID,
|
|
image_bookmark,
|
|
flavor_bookmark,
|
|
progress=0)
|
|
|
|
self.assertThat(res_dict, matchers.DictMatches(expected_server))
|
|
self.mock_get.assert_called_once_with(
|
|
req.environ['nova.context'], FAKE_UUID,
|
|
expected_attrs=['flavor', 'info_cache', 'metadata',
|
|
'numa_topology'])
|
|
|
|
def test_get_server_addresses_from_cache(self):
|
|
pub0 = ('172.19.0.1', '172.19.0.2',)
|
|
pub1 = ('1.2.3.4',)
|
|
pub2 = ('b33f::fdee:ddff:fecc:bbaa',)
|
|
priv0 = ('192.168.0.3', '192.168.0.4',)
|
|
|
|
def _ip(ip):
|
|
return {'address': ip, 'type': 'fixed'}
|
|
|
|
nw_cache = [
|
|
{'address': 'aa:aa:aa:aa:aa:aa',
|
|
'id': 1,
|
|
'network': {'bridge': 'br0',
|
|
'id': 1,
|
|
'label': 'public',
|
|
'subnets': [{'cidr': '172.19.0.0/24',
|
|
'ips': [_ip(ip) for ip in pub0]},
|
|
{'cidr': '1.2.3.0/16',
|
|
'ips': [_ip(ip) for ip in pub1]},
|
|
{'cidr': 'b33f::/64',
|
|
'ips': [_ip(ip) for ip in pub2]}]}},
|
|
{'address': 'bb:bb:bb:bb:bb:bb',
|
|
'id': 2,
|
|
'network': {'bridge': 'br1',
|
|
'id': 2,
|
|
'label': 'private',
|
|
'subnets': [{'cidr': '192.168.0.0/24',
|
|
'ips': [_ip(ip) for ip in priv0]}]}}]
|
|
|
|
self.mock_get.side_effect = fakes.fake_compute_get(nw_cache=nw_cache,
|
|
availability_zone='nova')
|
|
|
|
req = self.req('/fake/servers/%s/ips' % FAKE_UUID)
|
|
res_dict = self.ips_controller.index(req, FAKE_UUID)
|
|
|
|
expected = {
|
|
'addresses': {
|
|
'private': [
|
|
{'version': 4, 'addr': '192.168.0.3'},
|
|
{'version': 4, 'addr': '192.168.0.4'},
|
|
],
|
|
'public': [
|
|
{'version': 4, 'addr': '172.19.0.1'},
|
|
{'version': 4, 'addr': '172.19.0.2'},
|
|
{'version': 4, 'addr': '1.2.3.4'},
|
|
{'version': 6, 'addr': 'b33f::fdee:ddff:fecc:bbaa'},
|
|
],
|
|
},
|
|
}
|
|
self.assertThat(res_dict, matchers.DictMatches(expected))
|
|
self.mock_get.assert_called_once_with(
|
|
req.environ['nova.context'], FAKE_UUID,
|
|
expected_attrs=None)
|
|
# Make sure we kept the addresses in order
|
|
self.assertIsInstance(res_dict['addresses'], collections.OrderedDict)
|
|
labels = [vif['network']['label'] for vif in nw_cache]
|
|
for index, label in enumerate(res_dict['addresses'].keys()):
|
|
self.assertEqual(label, labels[index])
|
|
|
|
def test_get_server_addresses_nonexistent_network(self):
|
|
url = '/v2/fake/servers/%s/ips/network_0' % FAKE_UUID
|
|
req = self.req(url)
|
|
self.assertRaises(webob.exc.HTTPNotFound, self.ips_controller.show,
|
|
req, FAKE_UUID, 'network_0')
|
|
|
|
def test_get_server_addresses_nonexistent_server(self):
|
|
self.mock_get.side_effect = exception.InstanceNotFound(
|
|
instance_id='fake')
|
|
req = self.req('/fake/servers/%s/ips' % uuids.fake)
|
|
self.assertRaises(webob.exc.HTTPNotFound,
|
|
self.ips_controller.index, req, uuids.fake)
|
|
self.mock_get.assert_called_once_with(
|
|
req.environ['nova.context'], uuids.fake, expected_attrs=None)
|
|
|
|
def test_get_server_list_empty(self):
|
|
self.mock_get_all.side_effect = None
|
|
self.mock_get_all.return_value = objects.InstanceList(objects=[])
|
|
req = self.req('/fake/servers')
|
|
res_dict = self.controller.index(req)
|
|
|
|
self.assertEqual(0, len(res_dict['servers']))
|
|
self.mock_get_all.assert_called_once_with(
|
|
req.environ['nova.context'], expected_attrs=[], limit=1000,
|
|
marker=None, search_opts={'deleted': False, 'project_id': 'fake'},
|
|
sort_dirs=['desc'], sort_keys=['created_at'])
|
|
|
|
def test_get_server_list_with_reservation_id(self):
|
|
req = self.req('/fake/servers?reservation_id=foo')
|
|
res_dict = self.controller.index(req)
|
|
|
|
i = 0
|
|
for s in res_dict['servers']:
|
|
self.assertEqual(s.get('name'), 'server%d' % (i + 1))
|
|
i += 1
|
|
|
|
def test_get_server_list_with_reservation_id_empty(self):
|
|
req = self.req('/fake/servers/detail?'
|
|
'reservation_id=foo')
|
|
res_dict = self.controller.detail(req)
|
|
|
|
i = 0
|
|
for s in res_dict['servers']:
|
|
self.assertEqual(s.get('name'), 'server%d' % (i + 1))
|
|
i += 1
|
|
|
|
def test_get_server_list_with_reservation_id_details(self):
|
|
req = self.req('/fake/servers/detail?'
|
|
'reservation_id=foo')
|
|
res_dict = self.controller.detail(req)
|
|
|
|
i = 0
|
|
for s in res_dict['servers']:
|
|
self.assertEqual(s.get('name'), 'server%d' % (i + 1))
|
|
i += 1
|
|
|
|
def test_get_server_list(self):
|
|
req = self.req('/fake/servers')
|
|
res_dict = self.controller.index(req)
|
|
|
|
self.assertEqual(len(res_dict['servers']), 5)
|
|
for i, s in enumerate(res_dict['servers']):
|
|
self.assertEqual(s['id'], fakes.get_fake_uuid(i))
|
|
self.assertEqual(s['name'], 'server%d' % (i + 1))
|
|
self.assertIsNone(s.get('image', None))
|
|
|
|
expected_links = [
|
|
{
|
|
"rel": "self",
|
|
"href": "http://localhost/v2/fake/servers/%s" % s['id'],
|
|
},
|
|
{
|
|
"rel": "bookmark",
|
|
"href": "http://localhost/fake/servers/%s" % s['id'],
|
|
},
|
|
]
|
|
|
|
self.assertEqual(s['links'], expected_links)
|
|
|
|
def test_get_servers_with_limit(self):
|
|
req = self.req('/fake/servers?limit=3')
|
|
res_dict = self.controller.index(req)
|
|
|
|
servers = res_dict['servers']
|
|
self.assertEqual([s['id'] for s in servers],
|
|
[fakes.get_fake_uuid(i) for i in range(len(servers))])
|
|
|
|
servers_links = res_dict['servers_links']
|
|
self.assertEqual(servers_links[0]['rel'], 'next')
|
|
href_parts = urlparse.urlparse(servers_links[0]['href'])
|
|
self.assertEqual('/v2/fake/servers', href_parts.path)
|
|
params = urlparse.parse_qs(href_parts.query)
|
|
expected_params = {'limit': ['3'],
|
|
'marker': [fakes.get_fake_uuid(2)]}
|
|
self.assertThat(params, matchers.DictMatches(expected_params))
|
|
|
|
def test_get_servers_with_limit_bad_value(self):
|
|
req = self.req('/fake/servers?limit=aaa')
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.index, req)
|
|
|
|
def test_get_server_details_empty(self):
|
|
self.mock_get_all.side_effect = None
|
|
self.mock_get_all.return_value = objects.InstanceList(objects=[])
|
|
req = self.req('/fake/servers/detail')
|
|
expected_attrs = ['flavor', 'info_cache', 'metadata']
|
|
if api_version_request.is_supported(req, '2.16'):
|
|
expected_attrs.append('services')
|
|
|
|
res_dict = self.controller.detail(req)
|
|
|
|
self.assertEqual(0, len(res_dict['servers']))
|
|
self.mock_get_all.assert_called_once_with(
|
|
req.environ['nova.context'],
|
|
expected_attrs=expected_attrs,
|
|
limit=1000, marker=None,
|
|
search_opts={'deleted': False, 'project_id': 'fake'},
|
|
sort_dirs=['desc'], sort_keys=['created_at'])
|
|
|
|
def test_get_server_details_with_bad_name(self):
|
|
req = self.req('/fake/servers/detail?name=%2Binstance')
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.index, req)
|
|
|
|
def test_get_server_details_with_limit(self):
|
|
req = self.req('/fake/servers/detail?limit=3')
|
|
res = self.controller.detail(req)
|
|
|
|
servers = res['servers']
|
|
self.assertEqual([s['id'] for s in servers],
|
|
[fakes.get_fake_uuid(i) for i in range(len(servers))])
|
|
|
|
servers_links = res['servers_links']
|
|
self.assertEqual(servers_links[0]['rel'], 'next')
|
|
|
|
href_parts = urlparse.urlparse(servers_links[0]['href'])
|
|
self.assertEqual('/v2/fake/servers/detail', href_parts.path)
|
|
params = urlparse.parse_qs(href_parts.query)
|
|
expected = {'limit': ['3'], 'marker': [fakes.get_fake_uuid(2)]}
|
|
self.assertThat(params, matchers.DictMatches(expected))
|
|
|
|
def test_get_server_details_with_limit_bad_value(self):
|
|
req = self.req('/fake/servers/detail?limit=aaa')
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.detail, req)
|
|
|
|
def test_get_server_details_with_limit_and_other_params(self):
|
|
req = self.req('/fake/servers/detail'
|
|
'?limit=3&blah=2:t'
|
|
'&sort_key=uuid&sort_dir=asc')
|
|
res = self.controller.detail(req)
|
|
|
|
servers = res['servers']
|
|
self.assertEqual([s['id'] for s in servers],
|
|
[fakes.get_fake_uuid(i) for i in range(len(servers))])
|
|
|
|
servers_links = res['servers_links']
|
|
self.assertEqual(servers_links[0]['rel'], 'next')
|
|
|
|
href_parts = urlparse.urlparse(servers_links[0]['href'])
|
|
self.assertEqual('/v2/fake/servers/detail', href_parts.path)
|
|
params = urlparse.parse_qs(href_parts.query)
|
|
expected = {'limit': ['3'],
|
|
'sort_key': ['uuid'], 'sort_dir': ['asc'],
|
|
'marker': [fakes.get_fake_uuid(2)]}
|
|
self.assertThat(params, matchers.DictMatches(expected))
|
|
|
|
def test_get_servers_with_too_big_limit(self):
|
|
req = self.req('/fake/servers?limit=30')
|
|
res_dict = self.controller.index(req)
|
|
self.assertNotIn('servers_links', res_dict)
|
|
|
|
def test_get_servers_with_bad_limit(self):
|
|
req = self.req('/fake/servers?limit=asdf')
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.index, req)
|
|
|
|
def test_get_servers_with_marker(self):
|
|
url = '/v2/fake/servers?marker=%s' % fakes.get_fake_uuid(2)
|
|
req = self.req(url)
|
|
servers = self.controller.index(req)['servers']
|
|
self.assertEqual([s['name'] for s in servers], ["server4", "server5"])
|
|
|
|
def test_get_servers_with_limit_and_marker(self):
|
|
url = ('/v2/fake/servers?limit=2&marker=%s' %
|
|
fakes.get_fake_uuid(1))
|
|
req = self.req(url)
|
|
servers = self.controller.index(req)['servers']
|
|
self.assertEqual([s['name'] for s in servers], ['server3', 'server4'])
|
|
|
|
def test_get_servers_with_bad_marker(self):
|
|
req = self.req('/fake/servers?limit=2&marker=asdf')
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.index, req)
|
|
|
|
def test_get_servers_with_invalid_filter_param(self):
|
|
req = self.req('/fake/servers?info_cache=asdf',
|
|
use_admin_context=True)
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.index, req)
|
|
req = self.req('/fake/servers?__foo__=asdf',
|
|
use_admin_context=True)
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.index, req)
|
|
|
|
def test_get_servers_with_invalid_regex_filter_param(self):
|
|
req = self.req('/fake/servers?flavor=[[[',
|
|
use_admin_context=True)
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.index, req)
|
|
|
|
def test_get_servers_with_empty_regex_filter_param(self):
|
|
empty_string = ''
|
|
req = self.req('/fake/servers?flavor=%s' % empty_string,
|
|
use_admin_context=True)
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.index, req)
|
|
|
|
def test_get_servers_detail_with_empty_regex_filter_param(self):
|
|
empty_string = ''
|
|
req = self.req('/fake/servers/detail?flavor=%s' % empty_string,
|
|
use_admin_context=True)
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.detail, req)
|
|
|
|
def test_get_servers_invalid_sort_key(self):
|
|
req = self.req('/fake/servers?sort_key=foo&sort_dir=desc')
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.index, req)
|
|
|
|
def test_get_servers_ignore_sort_key(self):
|
|
req = self.req('/fake/servers?sort_key=vcpus&sort_dir=asc')
|
|
self.controller.index(req)
|
|
self.mock_get_all.assert_called_once_with(
|
|
mock.ANY, search_opts=mock.ANY, limit=mock.ANY, marker=mock.ANY,
|
|
expected_attrs=mock.ANY, sort_keys=[], sort_dirs=[])
|
|
|
|
def test_get_servers_ignore_sort_key_only_one_dir(self):
|
|
req = self.req(
|
|
'/fake/servers?sort_key=user_id&sort_key=vcpus&sort_dir=asc')
|
|
self.controller.index(req)
|
|
self.mock_get_all.assert_called_once_with(
|
|
mock.ANY, search_opts=mock.ANY, limit=mock.ANY, marker=mock.ANY,
|
|
expected_attrs=mock.ANY, sort_keys=['user_id'],
|
|
sort_dirs=['asc'])
|
|
|
|
def test_get_servers_ignore_sort_key_with_no_sort_dir(self):
|
|
req = self.req('/fake/servers?sort_key=vcpus&sort_key=user_id')
|
|
self.controller.index(req)
|
|
self.mock_get_all.assert_called_once_with(
|
|
mock.ANY, search_opts=mock.ANY, limit=mock.ANY, marker=mock.ANY,
|
|
expected_attrs=mock.ANY, sort_keys=['user_id'], sort_dirs=[])
|
|
|
|
def test_get_servers_ignore_sort_key_with_bad_sort_dir(self):
|
|
req = self.req('/fake/servers?sort_key=vcpus&sort_dir=bad_dir')
|
|
self.controller.index(req)
|
|
self.mock_get_all.assert_called_once_with(
|
|
mock.ANY, search_opts=mock.ANY, limit=mock.ANY, marker=mock.ANY,
|
|
expected_attrs=mock.ANY, sort_keys=[], sort_dirs=[])
|
|
|
|
def test_get_servers_non_admin_with_admin_only_sort_key(self):
|
|
req = self.req('/fake/servers?sort_key=host&sort_dir=desc')
|
|
self.assertRaises(webob.exc.HTTPForbidden,
|
|
self.controller.index, req)
|
|
|
|
def test_get_servers_admin_with_admin_only_sort_key(self):
|
|
req = self.req('/fake/servers?sort_key=node&sort_dir=desc',
|
|
use_admin_context=True)
|
|
self.controller.detail(req)
|
|
self.mock_get_all.assert_called_once_with(
|
|
mock.ANY, search_opts=mock.ANY, limit=mock.ANY, marker=mock.ANY,
|
|
expected_attrs=mock.ANY, sort_keys=['node'], sort_dirs=['desc'])
|
|
|
|
def test_get_servers_with_bad_option(self):
|
|
def fake_get_all(context, search_opts=None,
|
|
limit=None, marker=None,
|
|
expected_attrs=None, sort_keys=None, sort_dirs=None):
|
|
db_list = [fakes.stub_instance(100, uuid=uuids.fake)]
|
|
return instance_obj._make_instance_list(
|
|
context, objects.InstanceList(), db_list, FIELDS)
|
|
|
|
self.mock_get_all.side_effect = fake_get_all
|
|
|
|
req = self.req('/fake/servers?unknownoption=whee')
|
|
servers = self.controller.index(req)['servers']
|
|
|
|
self.assertEqual(1, len(servers))
|
|
self.assertEqual(uuids.fake, servers[0]['id'])
|
|
self.mock_get_all.assert_called_once_with(
|
|
req.environ['nova.context'], expected_attrs=[],
|
|
limit=1000, marker=None,
|
|
search_opts={'deleted': False, 'project_id': 'fake'},
|
|
sort_dirs=['desc'], sort_keys=['created_at'])
|
|
|
|
def test_get_servers_allows_image(self):
|
|
def fake_get_all(context, search_opts=None,
|
|
limit=None, marker=None,
|
|
expected_attrs=None, sort_keys=None, sort_dirs=None):
|
|
self.assertIsNotNone(search_opts)
|
|
self.assertIn('image', search_opts)
|
|
self.assertEqual(search_opts['image'], '12345')
|
|
db_list = [fakes.stub_instance(100, uuid=uuids.fake)]
|
|
return instance_obj._make_instance_list(
|
|
context, objects.InstanceList(), db_list, FIELDS)
|
|
|
|
self.mock_get_all.side_effect = fake_get_all
|
|
|
|
req = self.req('/fake/servers?image=12345')
|
|
servers = self.controller.index(req)['servers']
|
|
|
|
self.assertEqual(1, len(servers))
|
|
self.assertEqual(uuids.fake, servers[0]['id'])
|
|
|
|
def test_tenant_id_filter_no_admin_context(self):
|
|
def fake_get_all(context, search_opts=None, **kwargs):
|
|
self.assertIsNotNone(search_opts)
|
|
self.assertNotIn('tenant_id', search_opts)
|
|
self.assertEqual(search_opts['project_id'], 'fake')
|
|
return [fakes.stub_instance_obj(100)]
|
|
|
|
req = self.req('/fake/servers?tenant_id=newfake')
|
|
self.mock_get_all.side_effect = fake_get_all
|
|
servers = self.controller.index(req)['servers']
|
|
self.assertEqual(len(servers), 1)
|
|
|
|
def test_tenant_id_filter_admin_context(self):
|
|
""""Test tenant_id search opt is dropped if all_tenants is not set."""
|
|
def fake_get_all(context, search_opts=None, **kwargs):
|
|
self.assertIsNotNone(search_opts)
|
|
self.assertNotIn('tenant_id', search_opts)
|
|
self.assertEqual('fake', search_opts['project_id'])
|
|
return [fakes.stub_instance_obj(100)]
|
|
|
|
req = self.req('/fake/servers?tenant_id=newfake',
|
|
use_admin_context=True)
|
|
self.mock_get_all.side_effect = fake_get_all
|
|
servers = self.controller.index(req)['servers']
|
|
self.assertEqual(len(servers), 1)
|
|
|
|
def test_all_tenants_param_normal(self):
|
|
def fake_get_all(context, search_opts=None, **kwargs):
|
|
self.assertNotIn('project_id', search_opts)
|
|
return [fakes.stub_instance_obj(100)]
|
|
|
|
req = self.req('/fake/servers?all_tenants',
|
|
use_admin_context=True)
|
|
self.mock_get_all.side_effect = fake_get_all
|
|
servers = self.controller.index(req)['servers']
|
|
self.assertEqual(len(servers), 1)
|
|
|
|
def test_all_tenants_param_one(self):
|
|
def fake_get_all(context, search_opts=None, **kwargs):
|
|
self.assertNotIn('project_id', search_opts)
|
|
return [fakes.stub_instance_obj(100)]
|
|
|
|
self.mock_get_all.side_effect = fake_get_all
|
|
|
|
req = self.req('/fake/servers?all_tenants=1',
|
|
use_admin_context=True)
|
|
servers = self.controller.index(req)['servers']
|
|
self.assertEqual(1, len(servers))
|
|
|
|
def test_all_tenants_param_zero(self):
|
|
def fake_get_all(context, search_opts=None, **kwargs):
|
|
self.assertNotIn('all_tenants', search_opts)
|
|
return [fakes.stub_instance_obj(100)]
|
|
|
|
self.mock_get_all.side_effect = fake_get_all
|
|
|
|
req = self.req('/fake/servers?all_tenants=0',
|
|
use_admin_context=True)
|
|
servers = self.controller.index(req)['servers']
|
|
self.assertEqual(1, len(servers))
|
|
|
|
def test_all_tenants_param_false(self):
|
|
def fake_get_all(context, search_opts=None, **kwargs):
|
|
self.assertNotIn('all_tenants', search_opts)
|
|
return [fakes.stub_instance_obj(100)]
|
|
|
|
self.mock_get_all.side_effect = fake_get_all
|
|
|
|
req = self.req('/fake/servers?all_tenants=false',
|
|
use_admin_context=True)
|
|
servers = self.controller.index(req)['servers']
|
|
self.assertEqual(1, len(servers))
|
|
|
|
def test_all_tenants_param_invalid(self):
|
|
def fake_get_all(context, search_opts=None, **kwargs):
|
|
self.assertNotIn('all_tenants', search_opts)
|
|
return [fakes.stub_instance_obj(100)]
|
|
|
|
self.mock_get_all.side_effect = fake_get_all
|
|
|
|
req = self.req('/fake/servers?all_tenants=xxx',
|
|
use_admin_context=True)
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.index, req)
|
|
|
|
def test_admin_restricted_tenant(self):
|
|
def fake_get_all(context, search_opts=None, **kwargs):
|
|
self.assertIsNotNone(search_opts)
|
|
self.assertEqual(search_opts['project_id'], 'fake')
|
|
return [fakes.stub_instance_obj(100)]
|
|
|
|
self.mock_get_all.side_effect = fake_get_all
|
|
req = self.req('/fake/servers',
|
|
use_admin_context=True)
|
|
servers = self.controller.index(req)['servers']
|
|
self.assertEqual(1, len(servers))
|
|
|
|
def test_all_tenants_pass_policy(self):
|
|
def fake_get_all(context, search_opts=None, **kwargs):
|
|
self.assertIsNotNone(search_opts)
|
|
self.assertNotIn('project_id', search_opts)
|
|
self.assertTrue(context.is_admin)
|
|
return [fakes.stub_instance_obj(100)]
|
|
|
|
self.mock_get_all.side_effect = fake_get_all
|
|
|
|
rules = {
|
|
"os_compute_api:servers:index": "project_id:fake",
|
|
"os_compute_api:servers:index:get_all_tenants": "project_id:fake"
|
|
}
|
|
policy.set_rules(oslo_policy.Rules.from_dict(rules))
|
|
|
|
req = self.req('/fake/servers?all_tenants=1')
|
|
servers = self.controller.index(req)['servers']
|
|
self.assertEqual(1, len(servers))
|
|
|
|
def test_all_tenants_fail_policy(self):
|
|
def fake_get_all(context, search_opts=None, **kwargs):
|
|
self.assertIsNotNone(search_opts)
|
|
return [fakes.stub_instance_obj(100)]
|
|
|
|
rules = {
|
|
"os_compute_api:servers:index:get_all_tenants":
|
|
"project_id:non_fake",
|
|
"os_compute_api:servers:get_all": "project_id:fake",
|
|
}
|
|
|
|
policy.set_rules(oslo_policy.Rules.from_dict(rules))
|
|
self.mock_get_all.side_effect = fake_get_all
|
|
|
|
req = self.req('/fake/servers?all_tenants=1')
|
|
self.assertRaises(exception.PolicyNotAuthorized,
|
|
self.controller.index, req)
|
|
|
|
def test_get_servers_allows_flavor(self):
|
|
def fake_get_all(context, search_opts=None,
|
|
limit=None, marker=None,
|
|
expected_attrs=None, sort_keys=None, sort_dirs=None):
|
|
self.assertIsNotNone(search_opts)
|
|
self.assertIn('flavor', search_opts)
|
|
# flavor is an integer ID
|
|
self.assertEqual(search_opts['flavor'], '12345')
|
|
return objects.InstanceList(
|
|
objects=[fakes.stub_instance_obj(100, uuid=uuids.fake)])
|
|
|
|
self.mock_get_all.side_effect = fake_get_all
|
|
|
|
req = self.req('/fake/servers?flavor=12345')
|
|
servers = self.controller.index(req)['servers']
|
|
|
|
self.assertEqual(1, len(servers))
|
|
self.assertEqual(uuids.fake, servers[0]['id'])
|
|
|
|
def test_get_servers_with_bad_flavor(self):
|
|
req = self.req('/fake/servers?flavor=abcde')
|
|
self.mock_get_all.side_effect = None
|
|
self.mock_get_all.return_value = objects.InstanceList(objects=[])
|
|
servers = self.controller.index(req)['servers']
|
|
|
|
self.assertEqual(len(servers), 0)
|
|
|
|
def test_get_server_details_with_bad_flavor(self):
|
|
req = self.req('/fake/servers?flavor=abcde')
|
|
self.mock_get_all.side_effect = None
|
|
self.mock_get_all.return_value = objects.InstanceList(objects=[])
|
|
servers = self.controller.detail(req)['servers']
|
|
|
|
self.assertThat(servers, testtools.matchers.HasLength(0))
|
|
|
|
def test_get_servers_allows_status(self):
|
|
def fake_get_all(context, search_opts=None,
|
|
limit=None, marker=None,
|
|
expected_attrs=None, sort_keys=None, sort_dirs=None):
|
|
self.assertIsNotNone(search_opts)
|
|
self.assertIn('vm_state', search_opts)
|
|
self.assertEqual(search_opts['vm_state'], [vm_states.ACTIVE])
|
|
return objects.InstanceList(
|
|
objects=[fakes.stub_instance_obj(100, uuid=uuids.fake)])
|
|
|
|
self.mock_get_all.side_effect = fake_get_all
|
|
|
|
req = self.req('/fake/servers?status=active')
|
|
servers = self.controller.index(req)['servers']
|
|
|
|
self.assertEqual(1, len(servers))
|
|
self.assertEqual(uuids.fake, servers[0]['id'])
|
|
|
|
def test_get_servers_allows_task_status(self):
|
|
def fake_get_all(context, search_opts=None,
|
|
limit=None, marker=None,
|
|
expected_attrs=None, sort_keys=None, sort_dirs=None):
|
|
self.assertIsNotNone(search_opts)
|
|
self.assertIn('task_state', search_opts)
|
|
self.assertEqual([task_states.REBOOT_PENDING,
|
|
task_states.REBOOT_STARTED,
|
|
task_states.REBOOTING],
|
|
search_opts['task_state'])
|
|
return objects.InstanceList(
|
|
objects=[fakes.stub_instance_obj(
|
|
100, uuid=uuids.fake, task_state=task_states.REBOOTING)])
|
|
|
|
self.mock_get_all.side_effect = fake_get_all
|
|
|
|
req = self.req('/fake/servers?status=reboot')
|
|
servers = self.controller.index(req)['servers']
|
|
|
|
self.assertEqual(1, len(servers))
|
|
self.assertEqual(uuids.fake, servers[0]['id'])
|
|
|
|
def test_get_servers_resize_status(self):
|
|
# Test when resize status, it maps list of vm states.
|
|
def fake_get_all(context, search_opts=None,
|
|
limit=None, marker=None,
|
|
expected_attrs=None, sort_keys=None, sort_dirs=None):
|
|
self.assertIn('vm_state', search_opts)
|
|
self.assertEqual(search_opts['vm_state'],
|
|
[vm_states.ACTIVE, vm_states.STOPPED])
|
|
|
|
return objects.InstanceList(
|
|
objects=[fakes.stub_instance_obj(100, uuid=uuids.fake)])
|
|
|
|
self.mock_get_all.side_effect = fake_get_all
|
|
|
|
req = self.req('/fake/servers?status=resize')
|
|
|
|
servers = self.controller.detail(req)['servers']
|
|
self.assertEqual(1, len(servers), 1)
|
|
self.assertEqual(servers[0]['id'], uuids.fake)
|
|
|
|
def test_get_servers_invalid_status(self):
|
|
# Test getting servers by invalid status.
|
|
req = self.req('/fake/servers?status=baloney',
|
|
use_admin_context=False)
|
|
servers = self.controller.index(req)['servers']
|
|
self.assertEqual(len(servers), 0)
|
|
|
|
def test_get_servers_deleted_status_as_user(self):
|
|
req = self.req('/fake/servers?status=deleted',
|
|
use_admin_context=False)
|
|
self.assertRaises(webob.exc.HTTPForbidden,
|
|
self.controller.detail, req)
|
|
|
|
def test_get_servers_deleted_status_as_admin(self):
|
|
def fake_get_all(context, search_opts=None,
|
|
limit=None, marker=None,
|
|
expected_attrs=None, sort_keys=None, sort_dirs=None):
|
|
self.assertIn('vm_state', search_opts)
|
|
self.assertEqual(search_opts['vm_state'], ['deleted'])
|
|
|
|
return objects.InstanceList(
|
|
objects=[fakes.stub_instance_obj(100, uuid=uuids.fake)])
|
|
|
|
self.mock_get_all.side_effect = fake_get_all
|
|
|
|
req = self.req('/fake/servers?status=deleted',
|
|
use_admin_context=True)
|
|
|
|
servers = self.controller.detail(req)['servers']
|
|
self.assertEqual(1, len(servers))
|
|
self.assertEqual(uuids.fake, servers[0]['id'])
|
|
|
|
def test_get_servers_deleted_filter_str_to_bool(self):
|
|
db_list = objects.InstanceList(
|
|
objects=[fakes.stub_instance_obj(100, uuid=uuids.fake,
|
|
vm_state='deleted')])
|
|
self.mock_get_all.side_effect = None
|
|
self.mock_get_all.return_value = db_list
|
|
|
|
req = self.req('/fake/servers?deleted=true',
|
|
use_admin_context=True)
|
|
|
|
servers = self.controller.detail(req)['servers']
|
|
self.assertEqual(1, len(servers))
|
|
self.assertEqual(uuids.fake, servers[0]['id'])
|
|
|
|
# Assert that 'deleted' filter value is converted to boolean
|
|
# while calling get_all() method.
|
|
expected_search_opts = {'deleted': True, 'project_id': 'fake'}
|
|
self.assertEqual(expected_search_opts,
|
|
self.mock_get_all.call_args[1]['search_opts'])
|
|
|
|
def test_get_servers_deleted_filter_invalid_str(self):
|
|
db_list = objects.InstanceList(
|
|
objects=[fakes.stub_instance_obj(100, uuid=uuids.fake)])
|
|
self.mock_get_all.side_effect = None
|
|
self.mock_get_all.return_value = db_list
|
|
|
|
req = fakes.HTTPRequest.blank('/fake/servers?deleted=abc',
|
|
use_admin_context=True)
|
|
|
|
servers = self.controller.detail(req)['servers']
|
|
self.assertEqual(1, len(servers))
|
|
self.assertEqual(uuids.fake, servers[0]['id'])
|
|
|
|
# Assert that invalid 'deleted' filter value is converted to boolean
|
|
# False while calling get_all() method.
|
|
expected_search_opts = {'deleted': False, 'project_id': 'fake'}
|
|
self.assertEqual(expected_search_opts,
|
|
self.mock_get_all.call_args[1]['search_opts'])
|
|
|
|
def test_get_servers_allows_name(self):
|
|
def fake_get_all(context, search_opts=None,
|
|
limit=None, marker=None,
|
|
expected_attrs=None, sort_keys=None, sort_dirs=None):
|
|
self.assertIsNotNone(search_opts)
|
|
self.assertIn('name', search_opts)
|
|
self.assertEqual(search_opts['name'], 'whee.*')
|
|
self.assertEqual([], expected_attrs)
|
|
return objects.InstanceList(
|
|
objects=[fakes.stub_instance_obj(100, uuid=uuids.fake)])
|
|
|
|
self.mock_get_all.side_effect = fake_get_all
|
|
|
|
req = self.req('/fake/servers?name=whee.*')
|
|
servers = self.controller.index(req)['servers']
|
|
|
|
self.assertEqual(1, len(servers))
|
|
self.assertEqual(uuids.fake, servers[0]['id'])
|
|
|
|
def test_get_servers_flavor_not_found(self):
|
|
self.mock_get_all.side_effect = exception.FlavorNotFound(flavor_id=1)
|
|
|
|
req = fakes.HTTPRequest.blank(
|
|
'/fake/servers?status=active&flavor=abc')
|
|
servers = self.controller.index(req)['servers']
|
|
self.assertEqual(0, len(servers))
|
|
|
|
def test_get_servers_allows_changes_since(self):
|
|
def fake_get_all(context, search_opts=None,
|
|
limit=None, marker=None,
|
|
expected_attrs=None, sort_keys=None, sort_dirs=None):
|
|
self.assertIsNotNone(search_opts)
|
|
self.assertIn('changes-since', search_opts)
|
|
changes_since = datetime.datetime(2011, 1, 24, 17, 8, 1,
|
|
tzinfo=iso8601.iso8601.UTC)
|
|
self.assertEqual(search_opts['changes-since'], changes_since)
|
|
self.assertNotIn('deleted', search_opts)
|
|
return objects.InstanceList(
|
|
objects=[fakes.stub_instance_obj(100, uuid=uuids.fake)])
|
|
|
|
self.mock_get_all.side_effect = fake_get_all
|
|
|
|
params = 'changes-since=2011-01-24T17:08:01Z'
|
|
req = self.req('/fake/servers?%s' % params)
|
|
servers = self.controller.index(req)['servers']
|
|
|
|
self.assertEqual(1, len(servers))
|
|
self.assertEqual(uuids.fake, servers[0]['id'])
|
|
|
|
def test_get_servers_allows_changes_since_bad_value(self):
|
|
params = 'changes-since=asdf'
|
|
req = self.req('/fake/servers?%s' % params)
|
|
self.assertRaises(exception.ValidationError, self.controller.index,
|
|
req)
|
|
|
|
def test_get_servers_allows_changes_since_bad_value_on_compat_mode(self):
|
|
params = 'changes-since=asdf'
|
|
req = self.req('/fake/servers?%s' % params)
|
|
req.set_legacy_v2()
|
|
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.index,
|
|
req)
|
|
|
|
def test_get_servers_admin_filters_as_user(self):
|
|
"""Test getting servers by admin-only or unknown options when
|
|
context is not admin. Make sure the admin and unknown options
|
|
are stripped before they get to compute_api.get_all()
|
|
"""
|
|
def fake_get_all(context, search_opts=None,
|
|
limit=None, marker=None,
|
|
expected_attrs=None, sort_keys=None, sort_dirs=None):
|
|
self.assertIsNotNone(search_opts)
|
|
# Allowed by user
|
|
self.assertIn('name', search_opts)
|
|
self.assertIn('ip', search_opts)
|
|
# OSAPI converts status to vm_state
|
|
self.assertIn('vm_state', search_opts)
|
|
# Allowed only by admins with admin API on
|
|
self.assertNotIn('unknown_option', search_opts)
|
|
return objects.InstanceList(
|
|
objects=[fakes.stub_instance_obj(100, uuid=uuids.fake)])
|
|
|
|
self.mock_get_all.side_effect = fake_get_all
|
|
|
|
query_str = "name=foo&ip=10.*&status=active&unknown_option=meow"
|
|
req = fakes.HTTPRequest.blank('/fake/servers?%s' % query_str)
|
|
res = self.controller.index(req)
|
|
|
|
servers = res['servers']
|
|
self.assertEqual(1, len(servers))
|
|
self.assertEqual(uuids.fake, servers[0]['id'])
|
|
|
|
def test_get_servers_admin_options_as_admin(self):
|
|
"""Test getting servers by admin-only or unknown options when
|
|
context is admin. All options should be passed
|
|
"""
|
|
def fake_get_all(context, search_opts=None,
|
|
limit=None, marker=None,
|
|
expected_attrs=None, sort_keys=None, sort_dirs=None):
|
|
self.assertIsNotNone(search_opts)
|
|
# Allowed by user
|
|
self.assertIn('name', search_opts)
|
|
self.assertIn('terminated_at', search_opts)
|
|
# OSAPI converts status to vm_state
|
|
self.assertIn('vm_state', search_opts)
|
|
# Allowed only by admins with admin API on
|
|
self.assertIn('ip', search_opts)
|
|
self.assertNotIn('unknown_option', search_opts)
|
|
return objects.InstanceList(
|
|
objects=[fakes.stub_instance_obj(100, uuid=uuids.fake)])
|
|
|
|
self.mock_get_all.side_effect = fake_get_all
|
|
|
|
query_str = ("name=foo&ip=10.*&status=active&unknown_option=meow&"
|
|
"terminated_at=^2016-02-01.*")
|
|
req = self.req('/fake/servers?%s' % query_str,
|
|
use_admin_context=True)
|
|
servers = self.controller.index(req)['servers']
|
|
|
|
self.assertEqual(1, len(servers))
|
|
self.assertEqual(uuids.fake, servers[0]['id'])
|
|
|
|
def test_get_servers_allows_ip(self):
|
|
"""Test getting servers by ip."""
|
|
def fake_get_all(context, search_opts=None,
|
|
limit=None, marker=None,
|
|
expected_attrs=None, sort_keys=None, sort_dirs=None):
|
|
self.assertIsNotNone(search_opts)
|
|
self.assertIn('ip', search_opts)
|
|
self.assertEqual(search_opts['ip'], '10\..*')
|
|
return objects.InstanceList(
|
|
objects=[fakes.stub_instance_obj(100, uuid=uuids.fake)])
|
|
|
|
self.mock_get_all.side_effect = fake_get_all
|
|
|
|
req = self.req('/fake/servers?ip=10\..*')
|
|
servers = self.controller.index(req)['servers']
|
|
|
|
self.assertEqual(1, len(servers))
|
|
self.assertEqual(uuids.fake, servers[0]['id'])
|
|
|
|
def test_get_servers_admin_allows_ip6(self):
|
|
"""Test getting servers by ip6 with admin_api enabled and
|
|
admin context
|
|
"""
|
|
def fake_get_all(context, search_opts=None,
|
|
limit=None, marker=None,
|
|
expected_attrs=None, sort_keys=None, sort_dirs=None):
|
|
self.assertIsNotNone(search_opts)
|
|
self.assertIn('ip6', search_opts)
|
|
self.assertEqual(search_opts['ip6'], 'ffff.*')
|
|
return objects.InstanceList(
|
|
objects=[fakes.stub_instance_obj(100, uuid=uuids.fake)])
|
|
|
|
self.mock_get_all.side_effect = fake_get_all
|
|
|
|
req = self.req('/fake/servers?ip6=ffff.*',
|
|
use_admin_context=True)
|
|
servers = self.controller.index(req)['servers']
|
|
|
|
self.assertEqual(1, len(servers))
|
|
self.assertEqual(uuids.fake, servers[0]['id'])
|
|
|
|
def test_get_servers_allows_ip6_with_new_version(self):
|
|
"""Test getting servers by ip6 with new version requested
|
|
and no admin context
|
|
"""
|
|
def fake_get_all(context, search_opts=None,
|
|
limit=None, marker=None,
|
|
expected_attrs=None, sort_keys=None, sort_dirs=None):
|
|
self.assertIsNotNone(search_opts)
|
|
self.assertIn('ip6', search_opts)
|
|
self.assertEqual(search_opts['ip6'], 'ffff.*')
|
|
return objects.InstanceList(
|
|
objects=[fakes.stub_instance_obj(100, uuid=uuids.fake)])
|
|
|
|
self.mock_get_all.side_effect = fake_get_all
|
|
|
|
req = self.req('/fake/servers?ip6=ffff.*')
|
|
req.api_version_request = api_version_request.APIVersionRequest('2.5')
|
|
servers = self.controller.index(req)['servers']
|
|
|
|
self.assertEqual(1, len(servers))
|
|
self.assertEqual(uuids.fake, servers[0]['id'])
|
|
|
|
def test_get_servers_admin_allows_access_ip_v4(self):
|
|
"""Test getting servers by access_ip_v4 with admin_api enabled and
|
|
admin context
|
|
"""
|
|
def fake_get_all(context, search_opts=None,
|
|
limit=None, marker=None,
|
|
expected_attrs=None, sort_keys=None, sort_dirs=None):
|
|
self.assertIsNotNone(search_opts)
|
|
self.assertIn('access_ip_v4', search_opts)
|
|
self.assertEqual(search_opts['access_ip_v4'], 'ffff.*')
|
|
return objects.InstanceList(
|
|
objects=[fakes.stub_instance_obj(100, uuid=uuids.fake)])
|
|
|
|
self.mock_get_all.side_effect = fake_get_all
|
|
|
|
req = self.req('/fake/servers?access_ip_v4=ffff.*',
|
|
use_admin_context=True)
|
|
servers = self.controller.index(req)['servers']
|
|
|
|
self.assertEqual(1, len(servers))
|
|
self.assertEqual(uuids.fake, servers[0]['id'])
|
|
|
|
def test_get_servers_admin_allows_access_ip_v6(self):
|
|
"""Test getting servers by access_ip_v6 with admin_api enabled and
|
|
admin context
|
|
"""
|
|
def fake_get_all(context, search_opts=None,
|
|
limit=None, marker=None,
|
|
expected_attrs=None, sort_keys=None, sort_dirs=None):
|
|
self.assertIsNotNone(search_opts)
|
|
self.assertIn('access_ip_v6', search_opts)
|
|
self.assertEqual(search_opts['access_ip_v6'], 'ffff.*')
|
|
return objects.InstanceList(
|
|
objects=[fakes.stub_instance_obj(100, uuid=uuids.fake)])
|
|
|
|
self.mock_get_all.side_effect = fake_get_all
|
|
|
|
req = self.req('/fake/servers?access_ip_v6=ffff.*',
|
|
use_admin_context=True)
|
|
servers = self.controller.index(req)['servers']
|
|
|
|
self.assertEqual(1, len(servers))
|
|
self.assertEqual(uuids.fake, servers[0]['id'])
|
|
|
|
def _assertServerUsage(self, server, launched_at, terminated_at):
|
|
resp_launched_at = timeutils.parse_isotime(
|
|
server.get('OS-SRV-USG:launched_at'))
|
|
self.assertEqual(timeutils.normalize_time(resp_launched_at),
|
|
launched_at)
|
|
resp_terminated_at = timeutils.parse_isotime(
|
|
server.get('OS-SRV-USG:terminated_at'))
|
|
self.assertEqual(timeutils.normalize_time(resp_terminated_at),
|
|
terminated_at)
|
|
|
|
def test_show_server_usage(self):
|
|
DATE1 = datetime.datetime(year=2013, month=4, day=5, hour=12)
|
|
DATE2 = datetime.datetime(year=2013, month=4, day=5, hour=13)
|
|
self.mock_get.side_effect = fakes.fake_compute_get(
|
|
id=1, uuid=FAKE_UUID, launched_at=DATE1, terminated_at=DATE2)
|
|
fakes.stub_out_secgroup_api(self)
|
|
req = self.req('/fake/servers/%s' % FAKE_UUID)
|
|
req.accept = 'application/json'
|
|
req.method = 'GET'
|
|
res = req.get_response(compute.APIRouterV21())
|
|
self.assertEqual(res.status_int, 200)
|
|
self.useFixture(utils_fixture.TimeFixture())
|
|
self._assertServerUsage(jsonutils.loads(res.body).get('server'),
|
|
launched_at=DATE1,
|
|
terminated_at=DATE2)
|
|
|
|
def test_detail_server_usage(self):
|
|
DATE1 = datetime.datetime(year=2013, month=4, day=5, hour=12)
|
|
DATE2 = datetime.datetime(year=2013, month=4, day=5, hour=13)
|
|
DATE3 = datetime.datetime(year=2013, month=4, day=5, hour=14)
|
|
|
|
def fake_compute_get_all(*args, **kwargs):
|
|
db_list = [
|
|
fakes.stub_instance_obj(context, id=2, uuid=FAKE_UUID,
|
|
launched_at=DATE2,
|
|
terminated_at=DATE3),
|
|
fakes.stub_instance_obj(context, id=3, uuid=FAKE_UUID,
|
|
launched_at=DATE1,
|
|
terminated_at=DATE3),
|
|
]
|
|
return objects.InstanceList(objects=db_list)
|
|
self.mock_get_all.side_effect = fake_compute_get_all
|
|
fakes.stub_out_secgroup_api(self)
|
|
req = self.req('/fake/servers/detail')
|
|
req.accept = 'application/json'
|
|
servers = req.get_response(compute.APIRouterV21())
|
|
self.assertEqual(servers.status_int, 200)
|
|
self._assertServerUsage(jsonutils.loads(
|
|
servers.body).get('servers')[0],
|
|
launched_at=DATE2,
|
|
terminated_at=DATE3)
|
|
self._assertServerUsage(jsonutils.loads(
|
|
servers.body).get('servers')[1],
|
|
launched_at=DATE1,
|
|
terminated_at=DATE3)
|
|
|
|
def test_get_all_server_details(self):
|
|
expected_flavor = {
|
|
"id": "2",
|
|
"links": [
|
|
{
|
|
"rel": "bookmark",
|
|
"href": 'http://localhost/fake/flavors/2',
|
|
},
|
|
],
|
|
}
|
|
expected_image = {
|
|
"id": "10",
|
|
"links": [
|
|
{
|
|
"rel": "bookmark",
|
|
"href": 'http://localhost/fake/images/10',
|
|
},
|
|
],
|
|
}
|
|
req = self.req('/fake/servers/detail')
|
|
res_dict = self.controller.detail(req)
|
|
|
|
for i, s in enumerate(res_dict['servers']):
|
|
self.assertEqual(s['id'], fakes.get_fake_uuid(i))
|
|
self.assertEqual(s['hostId'], '')
|
|
self.assertEqual(s['name'], 'server%d' % (i + 1))
|
|
self.assertEqual(s['image'], expected_image)
|
|
self.assertEqual(s['flavor'], expected_flavor)
|
|
self.assertEqual(s['status'], 'ACTIVE')
|
|
self.assertEqual(s['metadata']['seq'], str(i + 1))
|
|
|
|
def test_get_all_server_details_with_host(self):
|
|
"""We want to make sure that if two instances are on the same host,
|
|
then they return the same hostId. If two instances are on different
|
|
hosts, they should return different hostIds. In this test,
|
|
there are 5 instances - 2 on one host and 3 on another.
|
|
"""
|
|
|
|
def return_servers_with_host(*args, **kwargs):
|
|
return objects.InstanceList(
|
|
objects=[fakes.stub_instance_obj(None,
|
|
id=i + 1,
|
|
user_id='fake',
|
|
project_id='fake',
|
|
host=i % 2,
|
|
uuid=fakes.get_fake_uuid(i))
|
|
for i in range(5)])
|
|
|
|
self.mock_get_all.side_effect = return_servers_with_host
|
|
|
|
req = self.req('/fake/servers/detail')
|
|
res_dict = self.controller.detail(req)
|
|
|
|
server_list = res_dict['servers']
|
|
host_ids = [server_list[0]['hostId'], server_list[1]['hostId']]
|
|
self.assertTrue(host_ids[0] and host_ids[1])
|
|
self.assertNotEqual(host_ids[0], host_ids[1])
|
|
|
|
for i, s in enumerate(server_list):
|
|
self.assertEqual(s['id'], fakes.get_fake_uuid(i))
|
|
self.assertEqual(s['hostId'], host_ids[i % 2])
|
|
self.assertEqual(s['name'], 'server%d' % (i + 1))
|
|
|
|
def test_get_servers_joins_services(self):
|
|
def fake_get_all(context, search_opts=None,
|
|
limit=None, marker=None,
|
|
expected_attrs=None, sort_keys=None, sort_dirs=None):
|
|
cur = api_version_request.APIVersionRequest(self.wsgi_api_version)
|
|
v216 = api_version_request.APIVersionRequest('2.16')
|
|
if cur >= v216:
|
|
self.assertIn('services', expected_attrs)
|
|
else:
|
|
self.assertNotIn('services', expected_attrs)
|
|
return objects.InstanceList()
|
|
|
|
self.mock_get_all.side_effect = fake_get_all
|
|
|
|
req = self.req('/fake/servers/detail', use_admin_context=True)
|
|
self.assertIn('servers', self.controller.detail(req))
|
|
|
|
req = fakes.HTTPRequest.blank('/fake/servers/detail',
|
|
use_admin_context=True,
|
|
version=self.wsgi_api_version)
|
|
self.assertIn('servers', self.controller.detail(req))
|
|
|
|
|
|
class ServersControllerTestV23(ServersControllerTest):
|
|
wsgi_api_version = '2.3'
|
|
|
|
def setUp(self):
|
|
super(ServersControllerTestV23, self).setUp()
|
|
self.mock_get.side_effect = fakes.fake_compute_get(
|
|
id=2, uuid=FAKE_UUID,
|
|
node="node-fake",
|
|
reservation_id="r-1", launch_index=0,
|
|
kernel_id=UUID1, ramdisk_id=UUID2,
|
|
display_name="server2",
|
|
root_device_name="/dev/vda",
|
|
user_data="userdata",
|
|
metadata={"seq": "2"},
|
|
availability_zone='nova',
|
|
launched_at=None,
|
|
terminated_at=None)
|
|
|
|
def _get_server_data_dict(self, uuid, image_bookmark, flavor_bookmark,
|
|
status="ACTIVE", progress=100):
|
|
server_dict = super(ServersControllerTestV23,
|
|
self)._get_server_data_dict(uuid,
|
|
image_bookmark,
|
|
flavor_bookmark,
|
|
status,
|
|
progress)
|
|
server_dict['server']["OS-EXT-SRV-ATTR:hostname"] = "server2"
|
|
server_dict['server'][
|
|
"OS-EXT-SRV-ATTR:hypervisor_hostname"] = "node-fake"
|
|
server_dict['server']["OS-EXT-SRV-ATTR:kernel_id"] = UUID1
|
|
server_dict['server']["OS-EXT-SRV-ATTR:launch_index"] = 0
|
|
server_dict['server']["OS-EXT-SRV-ATTR:ramdisk_id"] = UUID2
|
|
server_dict['server']["OS-EXT-SRV-ATTR:reservation_id"] = "r-1"
|
|
server_dict['server']["OS-EXT-SRV-ATTR:root_device_name"] = "/dev/vda"
|
|
server_dict['server']["OS-EXT-SRV-ATTR:user_data"] = "userdata"
|
|
return server_dict
|
|
|
|
def test_show(self):
|
|
image_bookmark = "http://localhost/fake/images/10"
|
|
flavor_bookmark = "http://localhost/fake/flavors/2"
|
|
|
|
req = self.req('/fake/servers/%s' % FAKE_UUID)
|
|
res_dict = self.controller.show(req, FAKE_UUID)
|
|
|
|
expected_server = self._get_server_data_dict(FAKE_UUID,
|
|
image_bookmark,
|
|
flavor_bookmark,
|
|
progress=0)
|
|
self.assertThat(res_dict, matchers.DictMatches(expected_server))
|
|
|
|
def test_detail(self):
|
|
def fake_get_all(context, search_opts=None,
|
|
limit=None, marker=None,
|
|
expected_attrs=None, sort_keys=None, sort_dirs=None):
|
|
obj_list = []
|
|
for i in range(2):
|
|
server = fakes.stub_instance_obj(context,
|
|
id=2, uuid=FAKE_UUID,
|
|
node="node-fake",
|
|
reservation_id="r-1", launch_index=0,
|
|
kernel_id=UUID1, ramdisk_id=UUID2,
|
|
display_name="server2",
|
|
root_device_name="/dev/vda",
|
|
user_data="userdata",
|
|
metadata={"seq": "2"},
|
|
availability_zone='nova',
|
|
launched_at=None,
|
|
terminated_at=None)
|
|
obj_list.append(server)
|
|
return objects.InstanceList(objects=obj_list)
|
|
|
|
self.mock_get_all.side_effect = None
|
|
self.mock_get_all.return_value = fake_get_all(context)
|
|
|
|
req = self.req('/fake/servers/detail')
|
|
servers_list = self.controller.detail(req)
|
|
image_bookmark = "http://localhost/fake/images/10"
|
|
flavor_bookmark = "http://localhost/fake/flavors/2"
|
|
expected_server = self._get_server_data_dict(FAKE_UUID,
|
|
image_bookmark,
|
|
flavor_bookmark,
|
|
progress=0)
|
|
|
|
self.assertIn(expected_server['server'], servers_list['servers'])
|
|
|
|
|
|
class ServersControllerTestV29(ServersControllerTest):
|
|
wsgi_api_version = '2.9'
|
|
|
|
def setUp(self):
|
|
super(ServersControllerTestV29, self).setUp()
|
|
self.mock_get.side_effect = fakes.fake_compute_get(
|
|
id=2, uuid=FAKE_UUID,
|
|
node="node-fake",
|
|
reservation_id="r-1", launch_index=0,
|
|
kernel_id=UUID1, ramdisk_id=UUID2,
|
|
display_name="server2",
|
|
root_device_name="/dev/vda",
|
|
user_data="userdata",
|
|
metadata={"seq": "2"},
|
|
availability_zone='nova',
|
|
launched_at=None,
|
|
terminated_at=None)
|
|
|
|
def _get_server_data_dict(self, uuid, image_bookmark, flavor_bookmark,
|
|
status="ACTIVE", progress=100):
|
|
server_dict = super(ServersControllerTestV29,
|
|
self)._get_server_data_dict(uuid,
|
|
image_bookmark,
|
|
flavor_bookmark,
|
|
status,
|
|
progress)
|
|
server_dict['server']['locked'] = False
|
|
server_dict['server']["OS-EXT-SRV-ATTR:hostname"] = "server2"
|
|
server_dict['server'][
|
|
"OS-EXT-SRV-ATTR:hypervisor_hostname"] = "node-fake"
|
|
server_dict['server']["OS-EXT-SRV-ATTR:kernel_id"] = UUID1
|
|
server_dict['server']["OS-EXT-SRV-ATTR:launch_index"] = 0
|
|
server_dict['server']["OS-EXT-SRV-ATTR:ramdisk_id"] = UUID2
|
|
server_dict['server']["OS-EXT-SRV-ATTR:reservation_id"] = "r-1"
|
|
server_dict['server']["OS-EXT-SRV-ATTR:root_device_name"] = "/dev/vda"
|
|
server_dict['server']["OS-EXT-SRV-ATTR:user_data"] = "userdata"
|
|
return server_dict
|
|
|
|
def _test_get_server_with_lock(self, locked_by):
|
|
image_bookmark = "http://localhost/fake/images/10"
|
|
flavor_bookmark = "http://localhost/fake/flavors/2"
|
|
self.mock_get.side_effect = fakes.fake_compute_get(
|
|
id=2, locked_by=locked_by, uuid=FAKE_UUID,
|
|
node="node-fake",
|
|
reservation_id="r-1", launch_index=0,
|
|
kernel_id=UUID1, ramdisk_id=UUID2,
|
|
display_name="server2",
|
|
root_device_name="/dev/vda",
|
|
user_data="userdata",
|
|
metadata={"seq": "2"},
|
|
availability_zone='nova',
|
|
launched_at=None,
|
|
terminated_at=None)
|
|
|
|
req = self.req('/fake/servers/%s' % FAKE_UUID)
|
|
res_dict = self.controller.show(req, FAKE_UUID)
|
|
|
|
expected_server = self._get_server_data_dict(FAKE_UUID,
|
|
image_bookmark,
|
|
flavor_bookmark,
|
|
progress=0)
|
|
expected_server['server']['locked'] = True if locked_by else False
|
|
self.assertThat(res_dict, matchers.DictMatches(expected_server))
|
|
return res_dict
|
|
|
|
def test_get_server_with_locked_by_admin(self):
|
|
res_dict = self._test_get_server_with_lock('admin')
|
|
self.assertTrue(res_dict['server']['locked'])
|
|
|
|
def test_get_server_with_locked_by_owner(self):
|
|
res_dict = self._test_get_server_with_lock('owner')
|
|
self.assertTrue(res_dict['server']['locked'])
|
|
|
|
def test_get_server_not_locked(self):
|
|
res_dict = self._test_get_server_with_lock(None)
|
|
self.assertFalse(res_dict['server']['locked'])
|
|
|
|
def _test_list_server_detail_with_lock(self,
|
|
s1_locked,
|
|
s2_locked):
|
|
self.mock_get_all.side_effect = None
|
|
self.mock_get_all.return_value = fake_instance_get_all_with_locked(
|
|
context, [s1_locked, s2_locked],
|
|
node="node-fake",
|
|
reservation_id="r-1", launch_index=0,
|
|
kernel_id=UUID1, ramdisk_id=UUID2,
|
|
display_name="server2",
|
|
root_device_name="/dev/vda",
|
|
user_data="userdata",
|
|
metadata={"seq": "2"},
|
|
availability_zone='nova',
|
|
launched_at=None,
|
|
terminated_at=None)
|
|
|
|
req = self.req('/fake/servers/detail')
|
|
servers_list = self.controller.detail(req)
|
|
# Check that each returned server has the same 'locked' value
|
|
# and 'id' as they were created.
|
|
for locked in [s1_locked, s2_locked]:
|
|
server = next(server for server in servers_list['servers']
|
|
if (server['id'] == fakes.get_fake_uuid(locked)))
|
|
expected = False if locked == 'not_locked' else True
|
|
self.assertEqual(expected, server['locked'])
|
|
|
|
def test_list_server_detail_with_locked_s1_admin_s2_owner(self):
|
|
self._test_list_server_detail_with_lock('admin', 'owner')
|
|
|
|
def test_list_server_detail_with_locked_s1_owner_s2_admin(self):
|
|
self._test_list_server_detail_with_lock('owner', 'admin')
|
|
|
|
def test_list_server_detail_with_locked_s1_admin_s2_admin(self):
|
|
self._test_list_server_detail_with_lock('admin', 'admin')
|
|
|
|
def test_list_server_detail_with_locked_s1_admin_s2_not_locked(self):
|
|
self._test_list_server_detail_with_lock('admin', 'not_locked')
|
|
|
|
def test_list_server_detail_with_locked_s1_s2_not_locked(self):
|
|
self._test_list_server_detail_with_lock('not_locked',
|
|
'not_locked')
|
|
|
|
def test_get_servers_remove_non_search_options(self):
|
|
self.mock_get_all.side_effect = None
|
|
req = fakes.HTTPRequestV21.blank('/servers'
|
|
'?sort_key=uuid&sort_dir=asc'
|
|
'&sort_key=user_id&sort_dir=desc'
|
|
'&limit=1&marker=123',
|
|
use_admin_context=True)
|
|
self.controller.index(req)
|
|
kwargs = self.mock_get_all.call_args[1]
|
|
search_opts = kwargs['search_opts']
|
|
for key in ('sort_key', 'sort_dir', 'limit', 'marker'):
|
|
self.assertNotIn(key, search_opts)
|
|
|
|
|
|
class ServersControllerTestV216(ServersControllerTest):
|
|
wsgi_api_version = '2.16'
|
|
|
|
def setUp(self):
|
|
super(ServersControllerTestV216, self).setUp()
|
|
self.mock_get.side_effect = fakes.fake_compute_get(
|
|
id=2, uuid=FAKE_UUID,
|
|
node="node-fake",
|
|
reservation_id="r-1", launch_index=0,
|
|
kernel_id=UUID1, ramdisk_id=UUID2,
|
|
display_name="server2",
|
|
root_device_name="/dev/vda",
|
|
user_data="userdata",
|
|
metadata={"seq": "2"},
|
|
availability_zone='nova',
|
|
launched_at=None,
|
|
terminated_at=None)
|
|
self.useFixture(fixtures.MockPatchObject(
|
|
compute_api.API, 'get_instance_host_status',
|
|
return_value='UP')).mock
|
|
|
|
def _get_server_data_dict(self, uuid, image_bookmark, flavor_bookmark,
|
|
status="ACTIVE", progress=100):
|
|
server_dict = super(ServersControllerTestV216,
|
|
self)._get_server_data_dict(uuid,
|
|
image_bookmark,
|
|
flavor_bookmark,
|
|
status,
|
|
progress)
|
|
server_dict['server']['locked'] = False
|
|
server_dict['server']["host_status"] = "UP"
|
|
server_dict['server']["OS-EXT-SRV-ATTR:hostname"] = "server2"
|
|
server_dict['server'][
|
|
"OS-EXT-SRV-ATTR:hypervisor_hostname"] = "node-fake"
|
|
server_dict['server']["OS-EXT-SRV-ATTR:kernel_id"] = UUID1
|
|
server_dict['server']["OS-EXT-SRV-ATTR:launch_index"] = 0
|
|
server_dict['server']["OS-EXT-SRV-ATTR:ramdisk_id"] = UUID2
|
|
server_dict['server']["OS-EXT-SRV-ATTR:reservation_id"] = "r-1"
|
|
server_dict['server']["OS-EXT-SRV-ATTR:root_device_name"] = "/dev/vda"
|
|
server_dict['server']["OS-EXT-SRV-ATTR:user_data"] = "userdata"
|
|
|
|
return server_dict
|
|
|
|
def test_show(self):
|
|
image_bookmark = "http://localhost/fake/images/10"
|
|
flavor_bookmark = "http://localhost/fake/flavors/2"
|
|
req = self.req('/fake/servers/%s' % FAKE_UUID)
|
|
res_dict = self.controller.show(req, FAKE_UUID)
|
|
expected_server = self._get_server_data_dict(FAKE_UUID,
|
|
image_bookmark,
|
|
flavor_bookmark,
|
|
progress=0)
|
|
self.assertThat(res_dict, matchers.DictMatches(expected_server))
|
|
|
|
def test_detail(self):
|
|
def fake_get_all(context, search_opts=None,
|
|
limit=None, marker=None,
|
|
expected_attrs=None, sort_keys=None, sort_dirs=None):
|
|
obj_list = []
|
|
for i in range(2):
|
|
server = fakes.stub_instance_obj(context,
|
|
id=2, uuid=FAKE_UUID,
|
|
node="node-fake",
|
|
reservation_id="r-1", launch_index=0,
|
|
kernel_id=UUID1, ramdisk_id=UUID2,
|
|
display_name="server2",
|
|
root_device_name="/dev/vda",
|
|
user_data="userdata",
|
|
metadata={"seq": "2"},
|
|
availability_zone='nova',
|
|
launched_at=None,
|
|
terminated_at=None)
|
|
obj_list.append(server)
|
|
return objects.InstanceList(objects=obj_list)
|
|
|
|
self.mock_get_all.side_effect = None
|
|
self.mock_get_all.return_value = fake_get_all(context)
|
|
|
|
req = self.req('/fake/servers/detail')
|
|
servers_list = self.controller.detail(req)
|
|
image_bookmark = "http://localhost/fake/images/10"
|
|
flavor_bookmark = "http://localhost/fake/flavors/2"
|
|
expected_server = self._get_server_data_dict(FAKE_UUID,
|
|
image_bookmark,
|
|
flavor_bookmark,
|
|
progress=0)
|
|
|
|
self.assertIn(expected_server['server'], servers_list['servers'])
|
|
|
|
|
|
class ServersControllerTestV219(ServersControllerTest):
|
|
wsgi_api_version = '2.19'
|
|
|
|
def setUp(self):
|
|
super(ServersControllerTestV219, self).setUp()
|
|
self.mock_get.side_effect = fakes.fake_compute_get(
|
|
id=2, uuid=FAKE_UUID,
|
|
node="node-fake",
|
|
reservation_id="r-1", launch_index=0,
|
|
kernel_id=UUID1, ramdisk_id=UUID2,
|
|
display_name="server2",
|
|
root_device_name="/dev/vda",
|
|
user_data="userdata",
|
|
metadata={"seq": "2"},
|
|
availability_zone='nova',
|
|
launched_at=None,
|
|
terminated_at=None)
|
|
self.useFixture(fixtures.MockPatchObject(
|
|
compute_api.API, 'get_instance_host_status',
|
|
return_value='UP')).mock
|
|
|
|
def _get_server_data_dict(self, uuid, image_bookmark, flavor_bookmark,
|
|
status="ACTIVE", progress=100, description=None):
|
|
server_dict = super(ServersControllerTestV219,
|
|
self)._get_server_data_dict(uuid,
|
|
image_bookmark,
|
|
flavor_bookmark,
|
|
status,
|
|
progress)
|
|
server_dict['server']['locked'] = False
|
|
server_dict['server']['description'] = description
|
|
server_dict['server']["host_status"] = "UP"
|
|
server_dict['server']["OS-EXT-SRV-ATTR:hostname"] = "server2"
|
|
server_dict['server'][
|
|
"OS-EXT-SRV-ATTR:hypervisor_hostname"] = "node-fake"
|
|
server_dict['server']["OS-EXT-SRV-ATTR:kernel_id"] = UUID1
|
|
server_dict['server']["OS-EXT-SRV-ATTR:launch_index"] = 0
|
|
server_dict['server']["OS-EXT-SRV-ATTR:ramdisk_id"] = UUID2
|
|
server_dict['server']["OS-EXT-SRV-ATTR:reservation_id"] = "r-1"
|
|
server_dict['server']["OS-EXT-SRV-ATTR:root_device_name"] = "/dev/vda"
|
|
server_dict['server']["OS-EXT-SRV-ATTR:user_data"] = "userdata"
|
|
|
|
return server_dict
|
|
|
|
def _test_get_server_with_description(self, description):
|
|
image_bookmark = "http://localhost/fake/images/10"
|
|
flavor_bookmark = "http://localhost/fake/flavors/2"
|
|
self.mock_get.side_effect = fakes.fake_compute_get(
|
|
id=2, display_description=description, uuid=FAKE_UUID,
|
|
node="node-fake",
|
|
reservation_id="r-1", launch_index=0,
|
|
kernel_id=UUID1, ramdisk_id=UUID2,
|
|
display_name="server2",
|
|
root_device_name="/dev/vda",
|
|
user_data="userdata",
|
|
metadata={"seq": "2"},
|
|
availability_zone='nova',
|
|
launched_at=None,
|
|
terminated_at=None)
|
|
|
|
req = self.req('/fake/servers/%s' % FAKE_UUID)
|
|
res_dict = self.controller.show(req, FAKE_UUID)
|
|
|
|
expected_server = self._get_server_data_dict(FAKE_UUID,
|
|
image_bookmark,
|
|
flavor_bookmark,
|
|
progress=0,
|
|
description=description)
|
|
self.assertThat(res_dict, matchers.DictMatches(expected_server))
|
|
return res_dict
|
|
|
|
def _test_list_server_detail_with_descriptions(self,
|
|
s1_desc,
|
|
s2_desc):
|
|
self.mock_get_all.side_effect = None
|
|
self.mock_get_all.return_value = (
|
|
fake_instance_get_all_with_description(context,
|
|
[s1_desc, s2_desc],
|
|
launched_at=None,
|
|
terminated_at=None))
|
|
req = self.req('/fake/servers/detail')
|
|
servers_list = self.controller.detail(req)
|
|
# Check that each returned server has the same 'description' value
|
|
# and 'id' as they were created.
|
|
for desc in [s1_desc, s2_desc]:
|
|
server = next(server for server in servers_list['servers']
|
|
if (server['id'] == fakes.get_fake_uuid(desc)))
|
|
expected = desc
|
|
self.assertEqual(expected, server['description'])
|
|
|
|
def test_get_server_with_description(self):
|
|
self._test_get_server_with_description('test desc')
|
|
|
|
def test_list_server_detail_with_descriptions(self):
|
|
self._test_list_server_detail_with_descriptions('desc1', 'desc2')
|
|
|
|
|
|
class ServersControllerTestV226(ControllerTest):
|
|
wsgi_api_version = '2.26'
|
|
|
|
def test_get_server_with_tags_by_id(self):
|
|
req = fakes.HTTPRequest.blank('/fake/servers/%s' % FAKE_UUID,
|
|
version=self.wsgi_api_version)
|
|
ctxt = req.environ['nova.context']
|
|
tags = ['tag1', 'tag2']
|
|
|
|
def fake_get(*args, **kwargs):
|
|
self.assertIn('tags', kwargs['expected_attrs'])
|
|
fake_server = fakes.stub_instance_obj(
|
|
ctxt, id=2, vm_state=vm_states.ACTIVE, progress=100)
|
|
|
|
tag_list = objects.TagList(objects=[
|
|
objects.Tag(resource_id=FAKE_UUID, tag=tag)
|
|
for tag in tags])
|
|
|
|
fake_server.tags = tag_list
|
|
return fake_server
|
|
|
|
self.mock_get.side_effect = fake_get
|
|
|
|
res_dict = self.controller.show(req, FAKE_UUID)
|
|
|
|
self.assertIn('tags', res_dict['server'])
|
|
self.assertEqual(tags, res_dict['server']['tags'])
|
|
|
|
def _test_get_servers_allows_tag_filters(self, filter_name):
|
|
req = fakes.HTTPRequest.blank('/fake/servers?%s=t1,t2' % filter_name,
|
|
version=self.wsgi_api_version)
|
|
|
|
def fake_get_all(*a, **kw):
|
|
self.assertIsNotNone(kw['search_opts'])
|
|
self.assertIn(filter_name, kw['search_opts'])
|
|
self.assertEqual(kw['search_opts'][filter_name], ['t1', 't2'])
|
|
return objects.InstanceList(
|
|
objects=[fakes.stub_instance_obj(req.environ['nova.context'],
|
|
uuid=uuids.fake)])
|
|
|
|
self.mock_get_all.side_effect = fake_get_all
|
|
|
|
servers = self.controller.index(req)['servers']
|
|
|
|
self.assertEqual(1, len(servers))
|
|
self.assertEqual(uuids.fake, servers[0]['id'])
|
|
|
|
def test_get_servers_allows_tags_filter(self):
|
|
self._test_get_servers_allows_tag_filters('tags')
|
|
|
|
def test_get_servers_allows_tags_any_filter(self):
|
|
self._test_get_servers_allows_tag_filters('tags-any')
|
|
|
|
def test_get_servers_allows_not_tags_filter(self):
|
|
self._test_get_servers_allows_tag_filters('not-tags')
|
|
|
|
def test_get_servers_allows_not_tags_any_filter(self):
|
|
self._test_get_servers_allows_tag_filters('not-tags-any')
|
|
|
|
|
|
class ServerControllerTestV238(ControllerTest):
|
|
wsgi_api_version = '2.38'
|
|
|
|
def _test_invalid_status(self, is_admin):
|
|
req = fakes.HTTPRequest.blank('/fake/servers/detail?status=invalid',
|
|
version=self.wsgi_api_version,
|
|
use_admin_context=is_admin)
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.detail, req)
|
|
|
|
def test_list_servers_detail_invalid_status_for_admin(self):
|
|
self._test_invalid_status(True)
|
|
|
|
def test_list_servers_detail_invalid_status_for_non_admin(self):
|
|
self._test_invalid_status(False)
|
|
|
|
|
|
class ServerControllerTestV247(ControllerTest):
|
|
"""Server controller test for microversion 2.47
|
|
|
|
The intent here is simply to verify that when showing server details
|
|
after microversion 2.47 that the flavor is shown as a dict of flavor
|
|
information rather than as dict of id/links. The existence of the
|
|
'extra_specs' key is controlled by policy.
|
|
"""
|
|
wsgi_api_version = '2.47'
|
|
|
|
@mock.patch.object(objects.TagList, 'get_by_resource_id')
|
|
def test_get_all_server_details(self, mock_get_by_resource_id):
|
|
# Fake out tags on the instances
|
|
mock_get_by_resource_id.return_value = objects.TagList()
|
|
|
|
expected_flavor = {
|
|
'disk': 20,
|
|
'ephemeral': 0,
|
|
'extra_specs': {},
|
|
'original_name': u'm1.small',
|
|
'ram': 2048,
|
|
'swap': 0,
|
|
'vcpus': 1}
|
|
|
|
req = fakes.HTTPRequest.blank('/fake/servers/detail',
|
|
version=self.wsgi_api_version)
|
|
|
|
hits = []
|
|
real_auth = policy.authorize
|
|
|
|
# Wrapper for authorize to count the number of times
|
|
# we authorize for extra-specs
|
|
def fake_auth(context, action, target):
|
|
if 'extra-specs' in action:
|
|
hits.append(1)
|
|
return real_auth(context, action, target)
|
|
|
|
with mock.patch('nova.policy.authorize') as mock_auth:
|
|
mock_auth.side_effect = fake_auth
|
|
res_dict = self.controller.detail(req)
|
|
|
|
# We should have found more than one servers, but only hit the
|
|
# policy check once
|
|
self.assertGreater(len(res_dict['servers']), 1)
|
|
self.assertEqual(1, len(hits))
|
|
|
|
for i, s in enumerate(res_dict['servers']):
|
|
self.assertEqual(s['flavor'], expected_flavor)
|
|
|
|
@mock.patch.object(objects.TagList, 'get_by_resource_id')
|
|
def test_get_all_server_details_no_extra_spec(self,
|
|
mock_get_by_resource_id):
|
|
# Fake out tags on the instances
|
|
mock_get_by_resource_id.return_value = objects.TagList()
|
|
# Set the policy so we don't have permission to index
|
|
# flavor extra-specs but are able to get server details.
|
|
servers_rule = 'os_compute_api:servers:detail'
|
|
extraspec_rule = 'os_compute_api:os-flavor-extra-specs:index'
|
|
self.policy.set_rules({
|
|
extraspec_rule: 'rule:admin_api',
|
|
servers_rule: '@'})
|
|
|
|
expected_flavor = {
|
|
'disk': 20,
|
|
'ephemeral': 0,
|
|
'original_name': u'm1.small',
|
|
'ram': 2048,
|
|
'swap': 0,
|
|
'vcpus': 1}
|
|
|
|
req = fakes.HTTPRequest.blank('/fake/servers/detail',
|
|
version=self.wsgi_api_version)
|
|
res_dict = self.controller.detail(req)
|
|
for i, s in enumerate(res_dict['servers']):
|
|
self.assertEqual(s['flavor'], expected_flavor)
|
|
|
|
|
|
class ServerControllerTestV266(ControllerTest):
|
|
"""Server controller test for microversion 2.66
|
|
|
|
Add changes-before parameter to get servers or servers details of
|
|
2.66 microversion.
|
|
|
|
Filters the response by a date and time stamp when the server last
|
|
changed. Those changed before the specified date and time stamp are
|
|
returned.
|
|
"""
|
|
wsgi_api_version = '2.66'
|
|
|
|
def req(self, url, use_admin_context=False):
|
|
return fakes.HTTPRequest.blank(url,
|
|
use_admin_context=use_admin_context,
|
|
version=self.wsgi_api_version)
|
|
|
|
def test_get_servers_allows_changes_before(self):
|
|
def fake_get_all(context, search_opts=None,
|
|
limit=None, marker=None,
|
|
expected_attrs=None, sort_keys=None, sort_dirs=None):
|
|
self.assertIsNotNone(search_opts)
|
|
self.assertIn('changes-before', search_opts)
|
|
changes_before = datetime.datetime(2011, 1, 24, 17, 8, 1,
|
|
tzinfo=iso8601.iso8601.UTC)
|
|
self.assertEqual(search_opts['changes-before'], changes_before)
|
|
self.assertNotIn('deleted', search_opts)
|
|
return objects.InstanceList(
|
|
objects=[fakes.stub_instance_obj(100, uuid=uuids.fake)])
|
|
|
|
self.mock_get_all.side_effect = fake_get_all
|
|
|
|
params = 'changes-before=2011-01-24T17:08:01Z'
|
|
req = self.req('/fake/servers?%s' % params)
|
|
req.api_version_request = api_version_request.APIVersionRequest('2.66')
|
|
servers = self.controller.index(req)['servers']
|
|
|
|
self.assertEqual(1, len(servers))
|
|
self.assertEqual(uuids.fake, servers[0]['id'])
|
|
|
|
def test_get_servers_allows_changes_before_bad_value(self):
|
|
params = 'changes-before=asdf'
|
|
req = self.req('/fake/servers?%s' % params)
|
|
req.api_version_request = api_version_request.APIVersionRequest('2.66')
|
|
self.assertRaises(exception.ValidationError, self.controller.index,
|
|
req)
|
|
|
|
def test_get_servers_allows_changes_before_bad_value_on_compat_mode(self):
|
|
params = 'changes-before=asdf'
|
|
req = self.req('/fake/servers?%s' % params)
|
|
req.api_version_request = api_version_request.APIVersionRequest('2.66')
|
|
req.set_legacy_v2()
|
|
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.index, req)
|
|
|
|
def test_get_servers_allows_changes_since_and_changes_before(self):
|
|
def fake_get_all(context, search_opts=None,
|
|
limit=None, marker=None,
|
|
expected_attrs=None, sort_keys=None, sort_dirs=None):
|
|
self.assertIsNotNone(search_opts)
|
|
self.assertIn('changes-since', search_opts)
|
|
changes_since = datetime.datetime(2011, 1, 23, 17, 8, 1,
|
|
tzinfo=iso8601.iso8601.UTC)
|
|
self.assertIn('changes-before', search_opts)
|
|
changes_before = datetime.datetime(2011, 1, 24, 17, 8, 1,
|
|
tzinfo=iso8601.iso8601.UTC)
|
|
self.assertEqual(search_opts['changes-since'], changes_since)
|
|
self.assertEqual(search_opts['changes-before'], changes_before)
|
|
self.assertNotIn('deleted', search_opts)
|
|
return objects.InstanceList(
|
|
objects=[fakes.stub_instance_obj(100, uuid=uuids.fake)])
|
|
|
|
self.mock_get_all.side_effect = fake_get_all
|
|
|
|
params = 'changes-since=2011-01-23T17:08:01Z&' \
|
|
'changes-before=2011-01-24T17:08:01Z'
|
|
req = self.req('/fake/servers?%s' % params)
|
|
req.api_version_request = api_version_request.APIVersionRequest('2.66')
|
|
servers = self.controller.index(req)['servers']
|
|
|
|
self.assertEqual(1, len(servers))
|
|
self.assertEqual(uuids.fake, servers[0]['id'])
|
|
|
|
def test_get_servers_filters_with_distinct_changes_time_bad_request(self):
|
|
changes_since = '2018-09-04T05:45:27Z'
|
|
changes_before = '2018-09-03T05:45:27Z'
|
|
req = self.req('/fake/servers?changes-since=%s&changes-before=%s' %
|
|
(changes_since, changes_before))
|
|
req.api_version_request = api_version_request.APIVersionRequest('2.66')
|
|
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.index, req)
|
|
|
|
|
|
class ServersControllerDeleteTest(ControllerTest):
|
|
|
|
def setUp(self):
|
|
super(ServersControllerDeleteTest, self).setUp()
|
|
self.server_delete_called = False
|
|
|
|
def fake_delete(api, context, instance):
|
|
if instance.uuid == uuids.non_existent_uuid:
|
|
raise exception.InstanceNotFound(instance_id=instance.uuid)
|
|
self.server_delete_called = True
|
|
|
|
self.stub_out('nova.compute.api.API.delete', fake_delete)
|
|
|
|
def _create_delete_request(self, uuid):
|
|
fakes.stub_out_instance_quota(self, 0, 10)
|
|
req = fakes.HTTPRequestV21.blank('/fake/servers/%s' % uuid)
|
|
req.method = 'DELETE'
|
|
fake_get = fakes.fake_compute_get(
|
|
uuid=uuid,
|
|
vm_state=vm_states.ACTIVE,
|
|
project_id=req.environ['nova.context'].project_id,
|
|
user_id=req.environ['nova.context'].user_id)
|
|
self.mock_get.side_effect = fake_get
|
|
return req
|
|
|
|
def _delete_server_instance(self, uuid=FAKE_UUID):
|
|
req = self._create_delete_request(uuid)
|
|
self.controller.delete(req, uuid)
|
|
|
|
def test_delete_server_instance(self):
|
|
self._delete_server_instance()
|
|
self.assertTrue(self.server_delete_called)
|
|
|
|
def test_delete_server_instance_not_found(self):
|
|
self.assertRaises(webob.exc.HTTPNotFound,
|
|
self._delete_server_instance,
|
|
uuid=uuids.non_existent_uuid)
|
|
|
|
def test_delete_server_instance_while_building(self):
|
|
req = self._create_delete_request(FAKE_UUID)
|
|
self.controller.delete(req, FAKE_UUID)
|
|
|
|
self.assertTrue(self.server_delete_called)
|
|
|
|
@mock.patch.object(compute_api.API, 'delete',
|
|
side_effect=exception.InstanceIsLocked(
|
|
instance_uuid=FAKE_UUID))
|
|
def test_delete_locked_server(self, mock_delete):
|
|
req = self._create_delete_request(FAKE_UUID)
|
|
|
|
self.assertRaises(webob.exc.HTTPConflict, self.controller.delete,
|
|
req, FAKE_UUID)
|
|
mock_delete.assert_called_once_with(
|
|
req.environ['nova.context'], test.MatchType(objects.Instance))
|
|
|
|
def test_delete_server_instance_while_resize(self):
|
|
req = self._create_delete_request(FAKE_UUID)
|
|
fake_get = fakes.fake_compute_get(
|
|
vm_state=vm_states.ACTIVE,
|
|
task_state=task_states.RESIZE_PREP,
|
|
project_id=req.environ['nova.context'].project_id,
|
|
user_id=req.environ['nova.context'].user_id)
|
|
self.mock_get.side_effect = fake_get
|
|
|
|
self.controller.delete(req, FAKE_UUID)
|
|
|
|
def test_delete_server_instance_if_not_launched(self):
|
|
self.flags(reclaim_instance_interval=3600)
|
|
req = fakes.HTTPRequestV21.blank('/fake/servers/%s' % FAKE_UUID)
|
|
req.method = 'DELETE'
|
|
|
|
self.server_delete_called = False
|
|
|
|
fake_get = fakes.fake_compute_get(
|
|
launched_at=None,
|
|
project_id=req.environ['nova.context'].project_id,
|
|
user_id=req.environ['nova.context'].user_id)
|
|
self.mock_get.side_effect = fake_get
|
|
|
|
def instance_destroy_mock(*args, **kwargs):
|
|
self.server_delete_called = True
|
|
deleted_at = timeutils.utcnow()
|
|
return fake_instance.fake_db_instance(deleted_at=deleted_at)
|
|
self.stub_out('nova.db.api.instance_destroy', instance_destroy_mock)
|
|
|
|
self.controller.delete(req, FAKE_UUID)
|
|
# delete() should be called for instance which has never been active,
|
|
# even if reclaim_instance_interval has been set.
|
|
self.assertTrue(self.server_delete_called)
|
|
|
|
|
|
class ServersControllerRebuildInstanceTest(ControllerTest):
|
|
|
|
image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
|
expected_key_name = False
|
|
|
|
def setUp(self):
|
|
super(ServersControllerRebuildInstanceTest, self).setUp()
|
|
self.req = fakes.HTTPRequest.blank('/fake/servers/a/action')
|
|
self.req.method = 'POST'
|
|
self.req.headers["content-type"] = "application/json"
|
|
self.req_user_id = self.req.environ['nova.context'].user_id
|
|
self.req_project_id = self.req.environ['nova.context'].project_id
|
|
self.useFixture(nova_fixtures.SingleCellSimple())
|
|
|
|
def fake_get(ctrl, ctxt, uuid):
|
|
if uuid == 'test_inst':
|
|
raise webob.exc.HTTPNotFound(explanation='fakeout')
|
|
return fakes.stub_instance_obj(None,
|
|
vm_state=vm_states.ACTIVE,
|
|
project_id=self.req_project_id,
|
|
user_id=self.req_user_id)
|
|
|
|
self.useFixture(
|
|
fixtures.MonkeyPatch('nova.api.openstack.compute.servers.'
|
|
'ServersController._get_instance',
|
|
fake_get))
|
|
fake_get = fakes.fake_compute_get(vm_state=vm_states.ACTIVE,
|
|
project_id=self.req_project_id,
|
|
user_id=self.req_user_id)
|
|
self.mock_get.side_effect = fake_get
|
|
|
|
self.body = {
|
|
'rebuild': {
|
|
'name': 'new_name',
|
|
'imageRef': self.image_uuid,
|
|
'metadata': {
|
|
'open': 'stack',
|
|
},
|
|
},
|
|
}
|
|
|
|
def test_rebuild_server_with_image_not_uuid(self):
|
|
self.body['rebuild']['imageRef'] = 'not-uuid'
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID,
|
|
body=self.body)
|
|
|
|
def test_rebuild_server_with_image_as_full_url(self):
|
|
image_href = ('http://localhost/v2/fake/images/'
|
|
'76fa36fc-c930-4bf3-8c8a-ea2a2420deb6')
|
|
self.body['rebuild']['imageRef'] = image_href
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID,
|
|
body=self.body)
|
|
|
|
def test_rebuild_server_with_image_as_empty_string(self):
|
|
self.body['rebuild']['imageRef'] = ''
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID,
|
|
body=self.body)
|
|
|
|
def test_rebuild_instance_name_with_spaces_in_the_middle(self):
|
|
self.body['rebuild']['name'] = 'abc def'
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.controller._action_rebuild(self.req, FAKE_UUID, body=self.body)
|
|
|
|
def test_rebuild_instance_name_with_leading_trailing_spaces(self):
|
|
self.body['rebuild']['name'] = ' abc def '
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID, body=self.body)
|
|
|
|
def test_rebuild_instance_name_with_leading_trailing_spaces_compat_mode(
|
|
self):
|
|
self.body['rebuild']['name'] = ' abc def '
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.req.set_legacy_v2()
|
|
|
|
def fake_rebuild(*args, **kwargs):
|
|
self.assertEqual('abc def', kwargs['display_name'])
|
|
|
|
with mock.patch.object(compute_api.API, 'rebuild') as mock_rebuild:
|
|
mock_rebuild.side_effect = fake_rebuild
|
|
self.controller._action_rebuild(self.req, FAKE_UUID,
|
|
body=self.body)
|
|
|
|
def test_rebuild_instance_with_blank_metadata_key(self):
|
|
self.body['rebuild']['metadata'][''] = 'world'
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID, body=self.body)
|
|
|
|
def test_rebuild_instance_with_metadata_key_too_long(self):
|
|
self.body['rebuild']['metadata'][('a' * 260)] = 'world'
|
|
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID, body=self.body)
|
|
|
|
def test_rebuild_instance_with_metadata_value_too_long(self):
|
|
self.body['rebuild']['metadata']['key1'] = ('a' * 260)
|
|
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller._action_rebuild, self.req,
|
|
FAKE_UUID, body=self.body)
|
|
|
|
def test_rebuild_instance_with_metadata_value_not_string(self):
|
|
self.body['rebuild']['metadata']['key1'] = 1
|
|
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller._action_rebuild, self.req,
|
|
FAKE_UUID, body=self.body)
|
|
|
|
@mock.patch.object(fake._FakeImageService, 'show',
|
|
return_value=dict(
|
|
id='76fa36fc-c930-4bf3-8c8a-ea2a2420deb6',
|
|
name='public image', is_public=True,
|
|
status='active', properties={'key1': 'value1'},
|
|
min_ram="4096", min_disk="10"))
|
|
def test_rebuild_instance_fails_when_min_ram_too_small(self, mock_show):
|
|
# make min_ram larger than our instance ram size
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID, body=self.body)
|
|
mock_show.assert_called_once_with(
|
|
self.req.environ['nova.context'], self.image_uuid,
|
|
include_locations=False, show_deleted=True)
|
|
|
|
@mock.patch.object(fake._FakeImageService, 'show',
|
|
return_value=dict(
|
|
id='76fa36fc-c930-4bf3-8c8a-ea2a2420deb6',
|
|
name='public image', is_public=True,
|
|
status='active', properties={'key1': 'value1'},
|
|
min_ram="128", min_disk="100000"))
|
|
def test_rebuild_instance_fails_when_min_disk_too_small(self, mock_show):
|
|
# make min_disk larger than our instance disk size
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller._action_rebuild, self.req,
|
|
FAKE_UUID, body=self.body)
|
|
mock_show.assert_called_once_with(
|
|
self.req.environ['nova.context'], self.image_uuid,
|
|
include_locations=False, show_deleted=True)
|
|
|
|
@mock.patch.object(fake._FakeImageService, 'show',
|
|
return_value=dict(
|
|
id='76fa36fc-c930-4bf3-8c8a-ea2a2420deb6',
|
|
name='public image', is_public=True,
|
|
status='active', size=str(1000 * (1024 ** 3))))
|
|
def test_rebuild_instance_image_too_large(self, mock_show):
|
|
# make image size larger than our instance disk size
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID, body=self.body)
|
|
mock_show.assert_called_once_with(
|
|
self.req.environ['nova.context'], self.image_uuid,
|
|
include_locations=False, show_deleted=True)
|
|
|
|
def test_rebuild_instance_name_all_blank(self):
|
|
self.body['rebuild']['name'] = ' '
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID, body=self.body)
|
|
|
|
@mock.patch.object(fake._FakeImageService, 'show',
|
|
return_value=dict(
|
|
id='76fa36fc-c930-4bf3-8c8a-ea2a2420deb6',
|
|
name='public image', is_public=True,
|
|
status='DELETED'))
|
|
def test_rebuild_instance_with_deleted_image(self, mock_show):
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID, body=self.body)
|
|
mock_show.assert_called_once_with(
|
|
self.req.environ['nova.context'], self.image_uuid,
|
|
include_locations=False, show_deleted=True)
|
|
|
|
def test_rebuild_instance_onset_file_limit_over_quota(self):
|
|
def fake_get_image(self, context, image_href, **kwargs):
|
|
return dict(id='76fa36fc-c930-4bf3-8c8a-ea2a2420deb6',
|
|
name='public image', is_public=True, status='active')
|
|
|
|
with test.nested(
|
|
mock.patch.object(fake._FakeImageService, 'show',
|
|
side_effect=fake_get_image),
|
|
mock.patch.object(self.controller.compute_api, 'rebuild',
|
|
side_effect=exception.OnsetFileLimitExceeded)
|
|
) as (
|
|
show_mock, rebuild_mock
|
|
):
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(webob.exc.HTTPForbidden,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID, body=self.body)
|
|
|
|
def test_rebuild_bad_personality(self):
|
|
# Personality files have been deprecated as of v2.57
|
|
self.req.api_version_request = \
|
|
api_version_request.APIVersionRequest('2.56')
|
|
|
|
body = {
|
|
"rebuild": {
|
|
"imageRef": self.image_uuid,
|
|
"personality": [{
|
|
"path": "/path/to/file",
|
|
"contents": "INVALID b64",
|
|
}]
|
|
},
|
|
}
|
|
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID, body=body)
|
|
|
|
def test_rebuild_personality(self):
|
|
# Personality files have been deprecated as of v2.57
|
|
self.req.api_version_request = \
|
|
api_version_request.APIVersionRequest('2.56')
|
|
|
|
body = {
|
|
"rebuild": {
|
|
"imageRef": self.image_uuid,
|
|
"personality": [{
|
|
"path": "/path/to/file",
|
|
"contents": base64.encode_as_text("Test String"),
|
|
}]
|
|
},
|
|
}
|
|
|
|
body = self.controller._action_rebuild(self.req, FAKE_UUID,
|
|
body=body).obj
|
|
|
|
self.assertNotIn('personality', body['server'])
|
|
|
|
def test_rebuild_response_has_no_show_server_only_attributes(self):
|
|
# There are some old server attributes which were added only for
|
|
# GET server APIs not for rebuild. GET server and Rebuild share the
|
|
# same view builder method SHOW() to build the response, So make sure
|
|
# attributes which are not supposed to be included for Rebuild
|
|
# response are not present.
|
|
|
|
body = {
|
|
"rebuild": {
|
|
"imageRef": self.image_uuid,
|
|
},
|
|
}
|
|
|
|
body = self.controller._action_rebuild(self.req, FAKE_UUID,
|
|
body=body).obj
|
|
|
|
get_only_fields = ['OS-EXT-AZ:availability_zone', 'config_drive',
|
|
'OS-EXT-SRV-ATTR:host',
|
|
'OS-EXT-SRV-ATTR:hypervisor_hostname',
|
|
'OS-EXT-SRV-ATTR:instance_name',
|
|
'OS-EXT-SRV-ATTR:hostname'
|
|
'OS-EXT-SRV-ATTR:kernel_id',
|
|
'OS-EXT-SRV-ATTR:launch_index',
|
|
'OS-EXT-SRV-ATTR:ramdisk_id',
|
|
'OS-EXT-SRV-ATTR:reservation_id',
|
|
'OS-EXT-SRV-ATTR:root_device_name',
|
|
'OS-EXT-SRV-ATTR:user_data', 'host_status',
|
|
'OS-SRV-USG:launched_at',
|
|
'OS-SRV-USG:terminated_at']
|
|
if not self.expected_key_name:
|
|
get_only_fields.append('key_name')
|
|
for field in get_only_fields:
|
|
self.assertNotIn(field, body['server'])
|
|
|
|
@mock.patch.object(compute_api.API, 'start')
|
|
def test_start(self, mock_start):
|
|
req = fakes.HTTPRequestV21.blank('/fake/servers/%s/action' % FAKE_UUID)
|
|
body = dict(start="")
|
|
self.controller._start_server(req, FAKE_UUID, body)
|
|
mock_start.assert_called_once_with(mock.ANY, mock.ANY)
|
|
|
|
@mock.patch.object(compute_api.API, 'start', fake_start_stop_not_ready)
|
|
def test_start_not_ready(self):
|
|
req = fakes.HTTPRequestV21.blank('/fake/servers/%s/action' % FAKE_UUID)
|
|
body = dict(start="")
|
|
self.assertRaises(webob.exc.HTTPConflict,
|
|
self.controller._start_server, req, FAKE_UUID, body)
|
|
|
|
@mock.patch.object(
|
|
compute_api.API, 'start', fakes.fake_actions_to_locked_server)
|
|
def test_start_locked_server(self):
|
|
req = fakes.HTTPRequestV21.blank('/fake/servers/%s/action' % FAKE_UUID)
|
|
body = dict(start="")
|
|
self.assertRaises(webob.exc.HTTPConflict,
|
|
self.controller._start_server, req, FAKE_UUID, body)
|
|
|
|
@mock.patch.object(compute_api.API, 'start', fake_start_stop_invalid_state)
|
|
def test_start_invalid(self):
|
|
req = fakes.HTTPRequestV21.blank('/fake/servers/%s/action' % FAKE_UUID)
|
|
body = dict(start="")
|
|
self.assertRaises(webob.exc.HTTPConflict,
|
|
self.controller._start_server, req, FAKE_UUID, body)
|
|
|
|
@mock.patch.object(compute_api.API, 'stop')
|
|
def test_stop(self, mock_stop):
|
|
req = fakes.HTTPRequestV21.blank('/fake/servers/%s/action' % FAKE_UUID)
|
|
body = dict(stop="")
|
|
self.controller._stop_server(req, FAKE_UUID, body)
|
|
mock_stop.assert_called_once_with(mock.ANY, mock.ANY)
|
|
|
|
@mock.patch.object(compute_api.API, 'stop', fake_start_stop_not_ready)
|
|
def test_stop_not_ready(self):
|
|
req = fakes.HTTPRequestV21.blank('/fake/servers/%s/action' % FAKE_UUID)
|
|
body = dict(stop="")
|
|
self.assertRaises(webob.exc.HTTPConflict,
|
|
self.controller._stop_server, req, FAKE_UUID, body)
|
|
|
|
@mock.patch.object(
|
|
compute_api.API, 'stop', fakes.fake_actions_to_locked_server)
|
|
def test_stop_locked_server(self):
|
|
req = fakes.HTTPRequestV21.blank('/fake/servers/%s/action' % FAKE_UUID)
|
|
body = dict(stop="")
|
|
self.assertRaises(webob.exc.HTTPConflict,
|
|
self.controller._stop_server, req, FAKE_UUID, body)
|
|
|
|
@mock.patch.object(compute_api.API, 'stop', fake_start_stop_invalid_state)
|
|
def test_stop_invalid_state(self):
|
|
req = fakes.HTTPRequestV21.blank('/fake/servers/%s/action' % FAKE_UUID)
|
|
body = dict(start="")
|
|
self.assertRaises(webob.exc.HTTPConflict,
|
|
self.controller._stop_server, req, FAKE_UUID, body)
|
|
|
|
@mock.patch(
|
|
'nova.db.api.instance_get_by_uuid',
|
|
fake_instance_get_by_uuid_not_found)
|
|
def test_start_with_bogus_id(self):
|
|
req = fakes.HTTPRequestV21.blank('/fake/servers/test_inst/action')
|
|
body = dict(start="")
|
|
self.assertRaises(webob.exc.HTTPNotFound,
|
|
self.controller._start_server, req, 'test_inst', body)
|
|
|
|
@mock.patch(
|
|
'nova.db.api.instance_get_by_uuid',
|
|
fake_instance_get_by_uuid_not_found)
|
|
def test_stop_with_bogus_id(self):
|
|
req = fakes.HTTPRequestV21.blank('/fake/servers/test_inst/action')
|
|
body = dict(stop="")
|
|
self.assertRaises(webob.exc.HTTPNotFound,
|
|
self.controller._stop_server, req, 'test_inst', body)
|
|
|
|
|
|
class ServersControllerRebuildTestV254(ServersControllerRebuildInstanceTest):
|
|
expected_key_name = True
|
|
|
|
def setUp(self):
|
|
super(ServersControllerRebuildTestV254, self).setUp()
|
|
fakes.stub_out_key_pair_funcs(self)
|
|
self.req.api_version_request = \
|
|
api_version_request.APIVersionRequest('2.54')
|
|
|
|
def _test_set_key_name_rebuild(self, set_key_name=True):
|
|
key_name = "key"
|
|
fake_get = fakes.fake_compute_get(vm_state=vm_states.ACTIVE,
|
|
key_name=key_name,
|
|
project_id=self.req_project_id,
|
|
user_id=self.req_user_id)
|
|
self.mock_get.side_effect = fake_get
|
|
|
|
if set_key_name:
|
|
self.body['rebuild']['key_name'] = key_name
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
server = self.controller._action_rebuild(
|
|
self.req, FAKE_UUID,
|
|
body=self.body).obj['server']
|
|
self.assertEqual(server['id'], FAKE_UUID)
|
|
self.assertEqual(server['key_name'], key_name)
|
|
|
|
def test_rebuild_accepted_with_keypair_name(self):
|
|
self._test_set_key_name_rebuild()
|
|
|
|
def test_rebuild_key_not_changed(self):
|
|
self._test_set_key_name_rebuild(set_key_name=False)
|
|
|
|
def test_rebuild_invalid_microversion_253(self):
|
|
self.req.api_version_request = \
|
|
api_version_request.APIVersionRequest('2.53')
|
|
body = {
|
|
"rebuild": {
|
|
"imageRef": self.image_uuid,
|
|
"key_name": "key"
|
|
},
|
|
}
|
|
excpt = self.assertRaises(exception.ValidationError,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID, body=body)
|
|
self.assertIn('key_name', six.text_type(excpt))
|
|
|
|
def test_rebuild_with_not_existed_keypair_name(self):
|
|
body = {
|
|
"rebuild": {
|
|
"imageRef": self.image_uuid,
|
|
"key_name": "nonexistentkey"
|
|
},
|
|
}
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID, body=body)
|
|
|
|
def test_rebuild_user_has_no_key_pair(self):
|
|
def no_key_pair(context, user_id, name):
|
|
raise exception.KeypairNotFound(user_id=user_id, name=name)
|
|
self.stub_out('nova.db.api.key_pair_get', no_key_pair)
|
|
fake_get = fakes.fake_compute_get(vm_state=vm_states.ACTIVE,
|
|
key_name=None,
|
|
project_id=self.req_project_id,
|
|
user_id=self.req_user_id)
|
|
self.mock_get.side_effect = fake_get
|
|
self.body['rebuild']['key_name'] = "a-key-name"
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID, body=self.body)
|
|
|
|
def test_rebuild_with_non_string_keypair_name(self):
|
|
body = {
|
|
"rebuild": {
|
|
"imageRef": self.image_uuid,
|
|
"key_name": 12345
|
|
},
|
|
}
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID, body=body)
|
|
|
|
def test_rebuild_with_invalid_keypair_name(self):
|
|
body = {
|
|
"rebuild": {
|
|
"imageRef": self.image_uuid,
|
|
"key_name": "123\0d456"
|
|
},
|
|
}
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID, body=body)
|
|
|
|
def test_rebuild_with_empty_keypair_name(self):
|
|
body = {
|
|
"rebuild": {
|
|
"imageRef": self.image_uuid,
|
|
"key_name": ''
|
|
},
|
|
}
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID, body=body)
|
|
|
|
def test_rebuild_with_none_keypair_name(self):
|
|
key_name = None
|
|
fake_get = fakes.fake_compute_get(vm_state=vm_states.ACTIVE,
|
|
key_name=key_name,
|
|
project_id=self.req_project_id,
|
|
user_id=self.req_user_id)
|
|
self.mock_get.side_effect = fake_get
|
|
with mock.patch.object(objects.KeyPair, 'get_by_name') as key_get:
|
|
self.body['rebuild']['key_name'] = key_name
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.controller._action_rebuild(
|
|
self.req, FAKE_UUID,
|
|
body=self.body)
|
|
# NOTE: because the api will call _get_server twice. The server
|
|
# response will always be the same one. So we just use
|
|
# objects.KeyPair.get_by_name to verify test.
|
|
key_get.assert_not_called()
|
|
|
|
def test_rebuild_with_too_large_keypair_name(self):
|
|
body = {
|
|
"rebuild": {
|
|
"imageRef": self.image_uuid,
|
|
"key_name": 256 * "k"
|
|
},
|
|
}
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID, body=body)
|
|
|
|
|
|
class ServersControllerRebuildTestV257(ServersControllerRebuildTestV254):
|
|
"""Tests server rebuild at microversion 2.57 where user_data can be
|
|
provided and personality files are no longer accepted.
|
|
"""
|
|
|
|
def setUp(self):
|
|
super(ServersControllerRebuildTestV257, self).setUp()
|
|
self.req.api_version_request = \
|
|
api_version_request.APIVersionRequest('2.57')
|
|
|
|
def test_rebuild_personality(self):
|
|
"""Tests that trying to rebuild with personality files fails."""
|
|
body = {
|
|
"rebuild": {
|
|
"imageRef": self.image_uuid,
|
|
"personality": [{
|
|
"path": "/path/to/file",
|
|
"contents": base64.encode_as_text("Test String"),
|
|
}]
|
|
}
|
|
}
|
|
ex = self.assertRaises(exception.ValidationError,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID, body=body)
|
|
self.assertIn('personality', six.text_type(ex))
|
|
|
|
def test_rebuild_user_data_old_version(self):
|
|
"""Tests that trying to rebuild with user_data before 2.57 fails."""
|
|
body = {
|
|
"rebuild": {
|
|
"imageRef": self.image_uuid,
|
|
"user_data": "ZWNobyAiaGVsbG8gd29ybGQi"
|
|
}
|
|
}
|
|
self.req.api_version_request = \
|
|
api_version_request.APIVersionRequest('2.55')
|
|
ex = self.assertRaises(exception.ValidationError,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID, body=body)
|
|
self.assertIn('user_data', six.text_type(ex))
|
|
|
|
def test_rebuild_user_data_malformed(self):
|
|
"""Tests that trying to rebuild with malformed user_data fails."""
|
|
body = {
|
|
"rebuild": {
|
|
"imageRef": self.image_uuid,
|
|
"user_data": b'invalid'
|
|
}
|
|
}
|
|
ex = self.assertRaises(exception.ValidationError,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID, body=body)
|
|
self.assertIn('user_data', six.text_type(ex))
|
|
|
|
def test_rebuild_user_data_too_large(self):
|
|
"""Tests that passing user_data to rebuild that is too large fails."""
|
|
body = {
|
|
"rebuild": {
|
|
"imageRef": self.image_uuid,
|
|
"user_data": ('MQ==' * 16384)
|
|
}
|
|
}
|
|
ex = self.assertRaises(exception.ValidationError,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID, body=body)
|
|
self.assertIn('user_data', six.text_type(ex))
|
|
|
|
@mock.patch.object(context.RequestContext, 'can')
|
|
@mock.patch('nova.db.api.instance_update_and_get_original')
|
|
def test_rebuild_reset_user_data(self, mock_update, mock_policy):
|
|
"""Tests that passing user_data=None resets the user_data on the
|
|
instance.
|
|
"""
|
|
body = {
|
|
"rebuild": {
|
|
"imageRef": self.image_uuid,
|
|
"user_data": None
|
|
}
|
|
}
|
|
|
|
self.mock_get.side_effect = None
|
|
self.mock_get.return_value = fakes.stub_instance_obj(
|
|
context.RequestContext(self.req_user_id, self.req_project_id),
|
|
user_data='ZWNobyAiaGVsbG8gd29ybGQi')
|
|
|
|
def fake_instance_update_and_get_original(
|
|
ctxt, instance_uuid, values, **kwargs):
|
|
# save() is called twice and the second one has system_metadata
|
|
# in the updates, so we can ignore that one.
|
|
if 'system_metadata' not in values:
|
|
self.assertIn('user_data', values)
|
|
self.assertIsNone(values['user_data'])
|
|
return instance_update_and_get_original(
|
|
ctxt, instance_uuid, values, **kwargs)
|
|
mock_update.side_effect = fake_instance_update_and_get_original
|
|
self.controller._action_rebuild(self.req, FAKE_UUID, body=body)
|
|
self.assertEqual(2, mock_update.call_count)
|
|
|
|
|
|
class ServersControllerRebuildTestV219(ServersControllerRebuildInstanceTest):
|
|
|
|
def setUp(self):
|
|
super(ServersControllerRebuildTestV219, self).setUp()
|
|
self.req.api_version_request = \
|
|
api_version_request.APIVersionRequest('2.19')
|
|
|
|
def _rebuild_server(self, set_desc, desc):
|
|
fake_get = fakes.fake_compute_get(vm_state=vm_states.ACTIVE,
|
|
display_description=desc,
|
|
project_id=self.req_project_id,
|
|
user_id=self.req_user_id)
|
|
self.mock_get.side_effect = fake_get
|
|
|
|
if set_desc:
|
|
self.body['rebuild']['description'] = desc
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
server = self.controller._action_rebuild(self.req, FAKE_UUID,
|
|
body=self.body).obj['server']
|
|
self.assertEqual(server['id'], FAKE_UUID)
|
|
self.assertEqual(server['description'], desc)
|
|
|
|
def test_rebuild_server_with_description(self):
|
|
self._rebuild_server(True, 'server desc')
|
|
|
|
def test_rebuild_server_empty_description(self):
|
|
self._rebuild_server(True, '')
|
|
|
|
def test_rebuild_server_without_description(self):
|
|
self._rebuild_server(False, '')
|
|
|
|
def test_rebuild_server_remove_description(self):
|
|
self._rebuild_server(True, None)
|
|
|
|
def test_rebuild_server_description_too_long(self):
|
|
self.body['rebuild']['description'] = 'x' * 256
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID, body=self.body)
|
|
|
|
def test_rebuild_server_description_invalid(self):
|
|
# Invalid non-printable control char in the desc.
|
|
self.body['rebuild']['description'] = "123\0d456"
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID, body=self.body)
|
|
|
|
|
|
# NOTE(jaypipes): Not based from ServersControllerRebuildInstanceTest because
|
|
# that test case's setUp is completely b0rked
|
|
class ServersControllerRebuildTestV263(ControllerTest):
|
|
|
|
image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
|
|
|
def setUp(self):
|
|
super(ServersControllerRebuildTestV263, self).setUp()
|
|
self.req = fakes.HTTPRequest.blank('/fake/servers/a/action')
|
|
self.req.method = 'POST'
|
|
self.req.headers["content-type"] = "application/json"
|
|
self.req_user_id = self.req.environ['nova.context'].user_id
|
|
self.req_project_id = self.req.environ['nova.context'].project_id
|
|
self.req.api_version_request = \
|
|
api_version_request.APIVersionRequest('2.63')
|
|
self.body = {
|
|
'rebuild': {
|
|
'name': 'new_name',
|
|
'imageRef': self.image_uuid,
|
|
'metadata': {
|
|
'open': 'stack',
|
|
},
|
|
},
|
|
}
|
|
|
|
@mock.patch('nova.compute.api.API.get')
|
|
def _rebuild_server(self, mock_get, certs=None,
|
|
conf_enabled=True, conf_certs=None):
|
|
fakes.stub_out_trusted_certs(self, certs=certs)
|
|
ctx = self.req.environ['nova.context']
|
|
mock_get.return_value = fakes.stub_instance_obj(ctx,
|
|
vm_state=vm_states.ACTIVE, trusted_certs=certs,
|
|
project_id=self.req_project_id, user_id=self.req_user_id)
|
|
|
|
self.flags(default_trusted_certificate_ids=conf_certs, group='glance')
|
|
|
|
if conf_enabled:
|
|
self.flags(verify_glance_signatures=True, group='glance')
|
|
self.flags(enable_certificate_validation=True, group='glance')
|
|
|
|
self.body['rebuild']['trusted_image_certificates'] = certs
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
server = self.controller._action_rebuild(
|
|
self.req, FAKE_UUID, body=self.body).obj['server']
|
|
|
|
if certs:
|
|
self.assertEqual(certs, server['trusted_image_certificates'])
|
|
else:
|
|
if conf_enabled:
|
|
# configuration file default is used
|
|
self.assertEqual(
|
|
conf_certs, server['trusted_image_certificates'])
|
|
else:
|
|
# either not set or empty
|
|
self.assertIsNone(server['trusted_image_certificates'])
|
|
|
|
@mock.patch('nova.objects.Service.get_minimum_version',
|
|
return_value=compute_api.MIN_COMPUTE_TRUSTED_CERTS)
|
|
def test_rebuild_server_with_trusted_certs(self, get_min_ver):
|
|
"""Test rebuild with valid trusted_image_certificates argument"""
|
|
self._rebuild_server(
|
|
certs=['0b5d2c72-12cc-4ba6-a8d7-3ff5cc1d8cb8',
|
|
'674736e3-f25c-405c-8362-bbf991e0ce0a'])
|
|
|
|
def test_rebuild_server_without_trusted_certs(self):
|
|
"""Test rebuild without trusted image certificates"""
|
|
self._rebuild_server()
|
|
|
|
@mock.patch('nova.objects.Service.get_minimum_version',
|
|
return_value=compute_api.MIN_COMPUTE_TRUSTED_CERTS)
|
|
def test_rebuild_server_conf_options_turned_off_set(self, get_min_ver):
|
|
"""Test rebuild with feature disabled and certs specified"""
|
|
self._rebuild_server(
|
|
certs=['0b5d2c72-12cc-4ba6-a8d7-3ff5cc1d8cb8'], conf_enabled=False)
|
|
|
|
def test_rebuild_server_conf_options_turned_off_empty(self):
|
|
"""Test rebuild with feature disabled"""
|
|
self._rebuild_server(conf_enabled=False)
|
|
|
|
def test_rebuild_server_default_trusted_certificates_empty(self):
|
|
"""Test rebuild with feature enabled and no certs specified"""
|
|
self._rebuild_server(conf_enabled=True)
|
|
|
|
@mock.patch('nova.objects.Service.get_minimum_version',
|
|
return_value=compute_api.MIN_COMPUTE_TRUSTED_CERTS)
|
|
def test_rebuild_server_default_trusted_certificates(self, get_min_ver):
|
|
"""Test rebuild with certificate specified in configurations"""
|
|
self._rebuild_server(conf_enabled=True, conf_certs=['conf-id'])
|
|
|
|
def test_rebuild_server_with_empty_trusted_cert_id(self):
|
|
"""Make sure that we can't rebuild with an empty certificate ID"""
|
|
self.body['rebuild']['trusted_image_certificates'] = ['']
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
ex = self.assertRaises(exception.ValidationError,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID, body=self.body)
|
|
self.assertIn('is too short', six.text_type(ex))
|
|
|
|
def test_rebuild_server_with_empty_trusted_certs(self):
|
|
"""Make sure that we can't rebuild with an empty array of IDs"""
|
|
self.body['rebuild']['trusted_image_certificates'] = []
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
ex = self.assertRaises(exception.ValidationError,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID, body=self.body)
|
|
self.assertIn('is too short', six.text_type(ex))
|
|
|
|
def test_rebuild_server_with_too_many_trusted_certs(self):
|
|
"""Make sure that we can't rebuild with an array of >50 unique IDs"""
|
|
self.body['rebuild']['trusted_image_certificates'] = [
|
|
'cert{}'.format(i) for i in range(51)]
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
ex = self.assertRaises(exception.ValidationError,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID, body=self.body)
|
|
self.assertIn('is too long', six.text_type(ex))
|
|
|
|
def test_rebuild_server_with_nonunique_trusted_certs(self):
|
|
"""Make sure that we can't rebuild with a non-unique array of IDs"""
|
|
self.body['rebuild']['trusted_image_certificates'] = ['cert', 'cert']
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
ex = self.assertRaises(exception.ValidationError,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID, body=self.body)
|
|
self.assertIn('has non-unique elements', six.text_type(ex))
|
|
|
|
def test_rebuild_server_with_invalid_trusted_cert_id(self):
|
|
"""Make sure that we can't rebuild with non-string certificate IDs"""
|
|
self.body['rebuild']['trusted_image_certificates'] = [1, 2]
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
ex = self.assertRaises(exception.ValidationError,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID, body=self.body)
|
|
self.assertIn('is not of type', six.text_type(ex))
|
|
|
|
def test_rebuild_server_with_invalid_trusted_certs(self):
|
|
"""Make sure that we can't rebuild with certificates in a non-array"""
|
|
self.body['rebuild']['trusted_image_certificates'] = "not-an-array"
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
ex = self.assertRaises(exception.ValidationError,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID, body=self.body)
|
|
self.assertIn('is not of type', six.text_type(ex))
|
|
|
|
@mock.patch('nova.objects.Service.get_minimum_version',
|
|
return_value=compute_api.MIN_COMPUTE_TRUSTED_CERTS)
|
|
def test_rebuild_server_with_trusted_certs_pre_2_63_fails(self,
|
|
get_min_ver):
|
|
"""Make sure we can't use trusted_certs before 2.63"""
|
|
self._rebuild_server(certs=['trusted-cert-id'])
|
|
self.req.api_version_request = \
|
|
api_version_request.APIVersionRequest('2.62')
|
|
ex = self.assertRaises(exception.ValidationError,
|
|
self.controller._action_rebuild,
|
|
self.req, FAKE_UUID, body=self.body)
|
|
self.assertIn('Additional properties are not allowed',
|
|
six.text_type(ex))
|
|
|
|
def test_rebuild_server_with_trusted_certs_policy_failed(self):
|
|
rule_name = "os_compute_api:servers:rebuild:trusted_certs"
|
|
rules = {"os_compute_api:servers:rebuild": "@",
|
|
rule_name: "project:fake"}
|
|
self.policy.set_rules(rules)
|
|
exc = self.assertRaises(exception.PolicyNotAuthorized,
|
|
self._rebuild_server,
|
|
certs=['0b5d2c72-12cc-4ba6-a8d7-3ff5cc1d8cb8'])
|
|
self.assertEqual(
|
|
"Policy doesn't allow %s to be performed." % rule_name,
|
|
exc.format_message())
|
|
|
|
@mock.patch.object(compute_api.API, 'rebuild')
|
|
def test_rebuild_server_with_cert_validation_error(
|
|
self, mock_rebuild):
|
|
mock_rebuild.side_effect = exception.CertificateValidationFailed(
|
|
cert_uuid="cert id", reason="test cert validation error")
|
|
|
|
ex = self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self._rebuild_server,
|
|
certs=['trusted-cert-id'])
|
|
self.assertIn('test cert validation error',
|
|
six.text_type(ex))
|
|
|
|
@mock.patch('nova.objects.Service.get_minimum_version',
|
|
return_value=compute_api.MIN_COMPUTE_TRUSTED_CERTS - 1)
|
|
def test_rebuild_server_with_cert_validation_not_available(
|
|
self, get_min_ver):
|
|
ex = self.assertRaises(webob.exc.HTTPConflict,
|
|
self._rebuild_server,
|
|
certs=['trusted-cert-id'])
|
|
self.assertIn('Image signature certificate validation support '
|
|
'is not yet available',
|
|
six.text_type(ex))
|
|
|
|
|
|
class ServersControllerUpdateTest(ControllerTest):
|
|
|
|
def _get_request(self, body=None):
|
|
req = fakes.HTTPRequestV21.blank('/fake/servers/%s' % FAKE_UUID)
|
|
req.method = 'PUT'
|
|
req.content_type = 'application/json'
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
fake_get = fakes.fake_compute_get(
|
|
project_id=req.environ['nova.context'].project_id,
|
|
user_id=req.environ['nova.context'].user_id)
|
|
self.mock_get.side_effect = fake_get
|
|
return req
|
|
|
|
def test_update_server_all_attributes(self):
|
|
body = {'server': {
|
|
'name': 'server_test',
|
|
}}
|
|
req = self._get_request(body)
|
|
res_dict = self.controller.update(req, FAKE_UUID, body=body)
|
|
|
|
self.assertEqual(res_dict['server']['id'], FAKE_UUID)
|
|
self.assertEqual(res_dict['server']['name'], 'server_test')
|
|
|
|
def test_update_server_name(self):
|
|
body = {'server': {'name': 'server_test'}}
|
|
req = self._get_request(body)
|
|
res_dict = self.controller.update(req, FAKE_UUID, body=body)
|
|
|
|
self.assertEqual(res_dict['server']['id'], FAKE_UUID)
|
|
self.assertEqual(res_dict['server']['name'], 'server_test')
|
|
|
|
def test_update_response_has_no_show_server_only_attributes(self):
|
|
# There are some old server attributes which were added only for
|
|
# GET server APIs not for PUT. GET server and PUT server share the
|
|
# same view builder method SHOW() to build the response, So make sure
|
|
# attributes which are not supposed to be included for PUT
|
|
# response are not present.
|
|
body = {'server': {'name': 'server_test'}}
|
|
req = self._get_request(body)
|
|
res_dict = self.controller.update(req, FAKE_UUID, body=body)
|
|
get_only_fields = ['OS-EXT-AZ:availability_zone', 'config_drive',
|
|
'OS-EXT-SRV-ATTR:host',
|
|
'OS-EXT-SRV-ATTR:hypervisor_hostname',
|
|
'OS-EXT-SRV-ATTR:instance_name',
|
|
'OS-EXT-SRV-ATTR:hostname'
|
|
'OS-EXT-SRV-ATTR:kernel_id',
|
|
'OS-EXT-SRV-ATTR:launch_index',
|
|
'OS-EXT-SRV-ATTR:ramdisk_id',
|
|
'OS-EXT-SRV-ATTR:reservation_id',
|
|
'OS-EXT-SRV-ATTR:root_device_name',
|
|
'OS-EXT-SRV-ATTR:user_data', 'host_status',
|
|
'key_name', 'OS-SRV-USG:launched_at',
|
|
'OS-SRV-USG:terminated_at']
|
|
for field in get_only_fields:
|
|
self.assertNotIn(field, res_dict['server'])
|
|
|
|
def test_update_server_name_too_long(self):
|
|
body = {'server': {'name': 'x' * 256}}
|
|
req = self._get_request(body)
|
|
self.assertRaises(exception.ValidationError, self.controller.update,
|
|
req, FAKE_UUID, body=body)
|
|
|
|
def test_update_server_name_all_blank_spaces(self):
|
|
self.stub_out('nova.db.api.instance_get',
|
|
fakes.fake_instance_get(name='server_test'))
|
|
req = fakes.HTTPRequest.blank('/fake/servers/%s' % FAKE_UUID)
|
|
req.method = 'PUT'
|
|
req.content_type = 'application/json'
|
|
body = {'server': {'name': ' ' * 64}}
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
self.assertRaises(exception.ValidationError, self.controller.update,
|
|
req, FAKE_UUID, body=body)
|
|
|
|
def test_update_server_name_with_spaces_in_the_middle(self):
|
|
body = {'server': {'name': 'abc def'}}
|
|
req = self._get_request(body)
|
|
self.controller.update(req, FAKE_UUID, body=body)
|
|
|
|
def test_update_server_name_with_leading_trailing_spaces(self):
|
|
self.stub_out('nova.db.api.instance_get',
|
|
fakes.fake_instance_get(name='server_test'))
|
|
req = fakes.HTTPRequest.blank('/fake/servers/%s' % FAKE_UUID)
|
|
req.method = 'PUT'
|
|
req.content_type = 'application/json'
|
|
body = {'server': {'name': ' abc def '}}
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.update, req, FAKE_UUID, body=body)
|
|
|
|
def test_update_server_name_with_leading_trailing_spaces_compat_mode(self):
|
|
body = {'server': {'name': ' abc def '}}
|
|
req = self._get_request(body)
|
|
req.set_legacy_v2()
|
|
self.controller.update(req, FAKE_UUID, body=body)
|
|
|
|
def test_update_server_admin_password_extra_arg(self):
|
|
inst_dict = dict(name='server_test', admin_password='bacon')
|
|
body = dict(server=inst_dict)
|
|
|
|
req = fakes.HTTPRequest.blank('/fake/servers/%s' % FAKE_UUID)
|
|
req.method = 'PUT'
|
|
req.content_type = "application/json"
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
self.assertRaises(exception.ValidationError, self.controller.update,
|
|
req, FAKE_UUID, body=body)
|
|
|
|
def test_update_server_host_id(self):
|
|
inst_dict = dict(host_id='123')
|
|
body = dict(server=inst_dict)
|
|
|
|
req = fakes.HTTPRequest.blank('/fake/servers/%s' % FAKE_UUID)
|
|
req.method = 'PUT'
|
|
req.content_type = "application/json"
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
self.assertRaises(exception.ValidationError, self.controller.update,
|
|
req, FAKE_UUID, body=body)
|
|
|
|
def test_update_server_not_found(self):
|
|
self.mock_get.side_effect = exception.InstanceNotFound(
|
|
instance_id='fake')
|
|
body = {'server': {'name': 'server_test'}}
|
|
req = fakes.HTTPRequest.blank('/fake/servers/%s' % FAKE_UUID)
|
|
req.method = 'PUT'
|
|
req.content_type = "application/json"
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
self.assertRaises(webob.exc.HTTPNotFound, self.controller.update,
|
|
req, FAKE_UUID, body=body)
|
|
|
|
@mock.patch.object(compute_api.API, 'update_instance')
|
|
def test_update_server_not_found_on_update(self, mock_update_instance):
|
|
def fake_update(*args, **kwargs):
|
|
raise exception.InstanceNotFound(instance_id='fake')
|
|
|
|
mock_update_instance.side_effect = fake_update
|
|
body = {'server': {'name': 'server_test'}}
|
|
req = self._get_request(body)
|
|
self.assertRaises(webob.exc.HTTPNotFound, self.controller.update,
|
|
req, FAKE_UUID, body=body)
|
|
|
|
def test_update_server_policy_fail(self):
|
|
rule = {'compute:update': 'role:admin'}
|
|
policy.set_rules(oslo_policy.Rules.from_dict(rule))
|
|
body = {'server': {'name': 'server_test'}}
|
|
req = self._get_request(body)
|
|
self.assertRaises(exception.PolicyNotAuthorized,
|
|
self.controller.update, req, FAKE_UUID, body=body)
|
|
|
|
|
|
class ServersControllerTriggerCrashDumpTest(ControllerTest):
|
|
|
|
def setUp(self):
|
|
super(ServersControllerTriggerCrashDumpTest, self).setUp()
|
|
|
|
self.instance = fakes.stub_instance_obj(None,
|
|
vm_state=vm_states.ACTIVE,
|
|
project_id='fake')
|
|
|
|
def fake_get(ctrl, ctxt, uuid):
|
|
if uuid != FAKE_UUID:
|
|
raise webob.exc.HTTPNotFound(explanation='fakeout')
|
|
return self.instance
|
|
|
|
self.useFixture(
|
|
fixtures.MonkeyPatch('nova.api.openstack.compute.servers.'
|
|
'ServersController._get_instance',
|
|
fake_get))
|
|
|
|
self.req = fakes.HTTPRequest.blank('/servers/%s/action' % FAKE_UUID)
|
|
self.req.api_version_request =\
|
|
api_version_request.APIVersionRequest('2.17')
|
|
self.body = dict(trigger_crash_dump=None)
|
|
|
|
@mock.patch.object(compute_api.API, 'trigger_crash_dump')
|
|
def test_trigger_crash_dump(self, mock_trigger_crash_dump):
|
|
ctxt = self.req.environ['nova.context']
|
|
self.controller._action_trigger_crash_dump(self.req, FAKE_UUID,
|
|
body=self.body)
|
|
mock_trigger_crash_dump.assert_called_with(ctxt, self.instance)
|
|
|
|
def test_trigger_crash_dump_policy_failed(self):
|
|
rule_name = "os_compute_api:servers:trigger_crash_dump"
|
|
self.policy.set_rules({rule_name: "project_id:non_fake"})
|
|
exc = self.assertRaises(exception.PolicyNotAuthorized,
|
|
self.controller._action_trigger_crash_dump,
|
|
self.req, FAKE_UUID, body=self.body)
|
|
self.assertIn("os_compute_api:servers:trigger_crash_dump",
|
|
exc.format_message())
|
|
|
|
@mock.patch.object(compute_api.API, 'trigger_crash_dump',
|
|
fake_start_stop_not_ready)
|
|
def test_trigger_crash_dump_not_ready(self):
|
|
self.assertRaises(webob.exc.HTTPConflict,
|
|
self.controller._action_trigger_crash_dump,
|
|
self.req, FAKE_UUID, body=self.body)
|
|
|
|
@mock.patch.object(compute_api.API, 'trigger_crash_dump',
|
|
fakes.fake_actions_to_locked_server)
|
|
def test_trigger_crash_dump_locked_server(self):
|
|
self.assertRaises(webob.exc.HTTPConflict,
|
|
self.controller._action_trigger_crash_dump,
|
|
self.req, FAKE_UUID, body=self.body)
|
|
|
|
@mock.patch.object(compute_api.API, 'trigger_crash_dump',
|
|
fake_start_stop_invalid_state)
|
|
def test_trigger_crash_dump_invalid_state(self):
|
|
self.assertRaises(webob.exc.HTTPConflict,
|
|
self.controller._action_trigger_crash_dump,
|
|
self.req, FAKE_UUID, body=self.body)
|
|
|
|
def test_trigger_crash_dump_with_bogus_id(self):
|
|
self.assertRaises(webob.exc.HTTPNotFound,
|
|
self.controller._action_trigger_crash_dump,
|
|
self.req, 'test_inst', body=self.body)
|
|
|
|
def test_trigger_crash_dump_schema_invalid_type(self):
|
|
self.body['trigger_crash_dump'] = 'not null'
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller._action_trigger_crash_dump,
|
|
self.req, FAKE_UUID, body=self.body)
|
|
|
|
def test_trigger_crash_dump_schema_extra_property(self):
|
|
self.body['extra_property'] = 'extra'
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller._action_trigger_crash_dump,
|
|
self.req, FAKE_UUID, body=self.body)
|
|
|
|
|
|
class ServersControllerUpdateTestV219(ServersControllerUpdateTest):
|
|
def _get_request(self, body=None):
|
|
req = super(ServersControllerUpdateTestV219, self)._get_request(
|
|
body=body)
|
|
req.api_version_request = api_version_request.APIVersionRequest('2.19')
|
|
return req
|
|
|
|
def _update_server_desc(self, set_desc, desc=None):
|
|
body = {'server': {}}
|
|
if set_desc:
|
|
body['server']['description'] = desc
|
|
req = self._get_request()
|
|
res_dict = self.controller.update(req, FAKE_UUID, body=body)
|
|
return res_dict
|
|
|
|
def test_update_server_description(self):
|
|
res_dict = self._update_server_desc(True, 'server_desc')
|
|
self.assertEqual(res_dict['server']['id'], FAKE_UUID)
|
|
self.assertEqual(res_dict['server']['description'], 'server_desc')
|
|
|
|
def test_update_server_empty_description(self):
|
|
res_dict = self._update_server_desc(True, '')
|
|
self.assertEqual(res_dict['server']['id'], FAKE_UUID)
|
|
self.assertEqual(res_dict['server']['description'], '')
|
|
|
|
def test_update_server_without_description(self):
|
|
res_dict = self._update_server_desc(False)
|
|
self.assertEqual(res_dict['server']['id'], FAKE_UUID)
|
|
self.assertIsNone(res_dict['server']['description'])
|
|
|
|
def test_update_server_remove_description(self):
|
|
res_dict = self._update_server_desc(True)
|
|
self.assertEqual(res_dict['server']['id'], FAKE_UUID)
|
|
self.assertIsNone(res_dict['server']['description'])
|
|
|
|
def test_update_server_all_attributes(self):
|
|
body = {'server': {
|
|
'name': 'server_test',
|
|
'description': 'server_desc'
|
|
}}
|
|
req = self._get_request(body)
|
|
res_dict = self.controller.update(req, FAKE_UUID, body=body)
|
|
|
|
self.assertEqual(res_dict['server']['id'], FAKE_UUID)
|
|
self.assertEqual(res_dict['server']['name'], 'server_test')
|
|
self.assertEqual(res_dict['server']['description'], 'server_desc')
|
|
|
|
def test_update_server_description_too_long(self):
|
|
body = {'server': {'description': 'x' * 256}}
|
|
req = self._get_request(body)
|
|
self.assertRaises(exception.ValidationError, self.controller.update,
|
|
req, FAKE_UUID, body=body)
|
|
|
|
def test_update_server_description_invalid(self):
|
|
# Invalid non-printable control char in the desc.
|
|
body = {'server': {'description': "123\0d456"}}
|
|
req = self._get_request(body)
|
|
self.assertRaises(exception.ValidationError, self.controller.update,
|
|
req, FAKE_UUID, body=body)
|
|
|
|
|
|
class ServerStatusTest(test.TestCase):
|
|
|
|
def setUp(self):
|
|
super(ServerStatusTest, self).setUp()
|
|
fakes.stub_out_nw_api(self)
|
|
|
|
self.controller = servers.ServersController()
|
|
|
|
def _get_with_state(self, vm_state, task_state=None):
|
|
self.stub_out('nova.db.api.instance_get_by_uuid',
|
|
fakes.fake_instance_get(vm_state=vm_state,
|
|
task_state=task_state))
|
|
|
|
request = fakes.HTTPRequestV21.blank('/fake/servers/%s' % FAKE_UUID)
|
|
return self.controller.show(request, FAKE_UUID)
|
|
|
|
def test_active(self):
|
|
response = self._get_with_state(vm_states.ACTIVE)
|
|
self.assertEqual(response['server']['status'], 'ACTIVE')
|
|
|
|
def test_reboot(self):
|
|
response = self._get_with_state(vm_states.ACTIVE,
|
|
task_states.REBOOTING)
|
|
self.assertEqual(response['server']['status'], 'REBOOT')
|
|
|
|
def test_reboot_hard(self):
|
|
response = self._get_with_state(vm_states.ACTIVE,
|
|
task_states.REBOOTING_HARD)
|
|
self.assertEqual(response['server']['status'], 'HARD_REBOOT')
|
|
|
|
def test_reboot_resize_policy_fail(self):
|
|
rule = {'compute:reboot': 'role:admin'}
|
|
policy.set_rules(oslo_policy.Rules.from_dict(rule))
|
|
req = fakes.HTTPRequestV21.blank('/fake/servers/1234/action')
|
|
self.assertRaises(exception.PolicyNotAuthorized,
|
|
self.controller._action_reboot, req, '1234',
|
|
body={'reboot': {'type': 'HARD'}})
|
|
|
|
def test_rebuild(self):
|
|
response = self._get_with_state(vm_states.ACTIVE,
|
|
task_states.REBUILDING)
|
|
self.assertEqual(response['server']['status'], 'REBUILD')
|
|
|
|
def test_rebuild_error(self):
|
|
response = self._get_with_state(vm_states.ERROR)
|
|
self.assertEqual(response['server']['status'], 'ERROR')
|
|
|
|
def test_resize(self):
|
|
response = self._get_with_state(vm_states.ACTIVE,
|
|
task_states.RESIZE_PREP)
|
|
self.assertEqual(response['server']['status'], 'RESIZE')
|
|
|
|
def test_confirm_resize_policy_fail(self):
|
|
rule = {'compute:confirm_resize': 'role:admin'}
|
|
policy.set_rules(oslo_policy.Rules.from_dict(rule))
|
|
req = fakes.HTTPRequestV21.blank('/fake/servers/1234/action')
|
|
self.assertRaises(exception.PolicyNotAuthorized,
|
|
self.controller._action_confirm_resize, req, '1234', {})
|
|
|
|
def test_verify_resize(self):
|
|
response = self._get_with_state(vm_states.RESIZED, None)
|
|
self.assertEqual(response['server']['status'], 'VERIFY_RESIZE')
|
|
|
|
def test_revert_resize(self):
|
|
response = self._get_with_state(vm_states.RESIZED,
|
|
task_states.RESIZE_REVERTING)
|
|
self.assertEqual(response['server']['status'], 'REVERT_RESIZE')
|
|
|
|
def test_revert_resize_policy_fail(self):
|
|
rule = {'compute:revert_resize': 'role:admin'}
|
|
policy.set_rules(oslo_policy.Rules.from_dict(rule))
|
|
req = fakes.HTTPRequestV21.blank('/fake/servers/1234/action')
|
|
self.assertRaises(exception.PolicyNotAuthorized,
|
|
self.controller._action_revert_resize, req, '1234', {})
|
|
|
|
def test_password_update(self):
|
|
response = self._get_with_state(vm_states.ACTIVE,
|
|
task_states.UPDATING_PASSWORD)
|
|
self.assertEqual(response['server']['status'], 'PASSWORD')
|
|
|
|
def test_stopped(self):
|
|
response = self._get_with_state(vm_states.STOPPED)
|
|
self.assertEqual(response['server']['status'], 'SHUTOFF')
|
|
|
|
|
|
class ServersControllerCreateTest(test.TestCase):
|
|
image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
|
flavor_ref = 'http://localhost/123/flavors/3'
|
|
|
|
def setUp(self):
|
|
"""Shared implementation for tests below that create instance."""
|
|
super(ServersControllerCreateTest, self).setUp()
|
|
|
|
self.flags(enable_instance_password=True, group='api')
|
|
self.instance_cache_num = 0
|
|
self.instance_cache_by_id = {}
|
|
self.instance_cache_by_uuid = {}
|
|
|
|
fakes.stub_out_nw_api(self)
|
|
|
|
self.controller = servers.ServersController()
|
|
|
|
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/fake/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',
|
|
'display_description': inst['display_description'] or '',
|
|
'uuid': FAKE_UUID,
|
|
'instance_type': inst_type,
|
|
'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": "",
|
|
"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_and_get_original(
|
|
context, instance_uuid, params, columns_to_join=None):
|
|
inst = self.instance_cache_by_uuid[instance_uuid]
|
|
inst.update(params)
|
|
return (inst, inst)
|
|
|
|
fakes.stub_out_key_pair_funcs(self)
|
|
fake.stub_out_image_service(self)
|
|
self.stub_out('nova.db.api.project_get_networks',
|
|
lambda c, u: dict(id='1', host='localhost'))
|
|
self.stub_out('nova.db.api.instance_create', instance_create)
|
|
self.stub_out('nova.db.api.instance_system_metadata_update',
|
|
lambda *a, **kw: None)
|
|
self.stub_out('nova.db.api.instance_get', instance_get)
|
|
self.stub_out('nova.db.api.instance_update', instance_update)
|
|
self.stub_out('nova.db.api.instance_update_and_get_original',
|
|
server_update_and_get_original)
|
|
self.stub_out('nova.network.manager.VlanManager.allocate_fixed_ip',
|
|
lambda *a, **kw: None)
|
|
self.body = {
|
|
'server': {
|
|
'name': 'server_test',
|
|
'imageRef': self.image_uuid,
|
|
'flavorRef': self.flavor_ref,
|
|
'metadata': {
|
|
'hello': 'world',
|
|
'open': 'stack',
|
|
},
|
|
'networks': [{
|
|
'uuid': 'ff608d40-75e9-48cb-b745-77bb55b5eaf2'
|
|
}],
|
|
},
|
|
}
|
|
self.bdm_v2 = [{
|
|
'no_device': None,
|
|
'source_type': 'volume',
|
|
'destination_type': 'volume',
|
|
'uuid': 'fake',
|
|
'device_name': 'vdb',
|
|
'delete_on_termination': False,
|
|
}]
|
|
|
|
self.bdm = [{
|
|
'no_device': None,
|
|
'virtual_name': 'root',
|
|
'volume_id': fakes.FAKE_UUID,
|
|
'device_name': 'vda',
|
|
'delete_on_termination': False
|
|
}]
|
|
|
|
self.req = fakes.HTTPRequest.blank('/fake/servers')
|
|
self.req.method = 'POST'
|
|
self.req.headers["content-type"] = "application/json"
|
|
|
|
def _check_admin_password_len(self, server_dict):
|
|
"""utility function - check server_dict for admin_password length."""
|
|
self.assertEqual(CONF.password_length,
|
|
len(server_dict["adminPass"]))
|
|
|
|
def _check_admin_password_missing(self, server_dict):
|
|
"""utility function - check server_dict for admin_password absence."""
|
|
self.assertNotIn("adminPass", server_dict)
|
|
|
|
def _test_create_instance(self, flavor=2):
|
|
self.stub_out('uuid.uuid4', lambda: FAKE_UUID)
|
|
image_uuid = 'c905cedb-7281-47e4-8a62-f26bc5fc4c77'
|
|
self.body['server']['imageRef'] = image_uuid
|
|
self.body['server']['flavorRef'] = flavor
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
server = self.controller.create(self.req, body=self.body).obj['server']
|
|
self._check_admin_password_len(server)
|
|
self.assertEqual(FAKE_UUID, server['id'])
|
|
|
|
def test_create_instance_with_none_value_port(self):
|
|
self.body['server'] = {'networks': [{'port': None, 'uuid': FAKE_UUID}]}
|
|
self.body['server']['name'] = 'test'
|
|
self._test_create_instance()
|
|
|
|
def test_create_instance_private_flavor(self):
|
|
values = {
|
|
'name': 'fake_name',
|
|
'memory': 512,
|
|
'vcpus': 1,
|
|
'root_gb': 10,
|
|
'ephemeral_gb': 10,
|
|
'flavorid': '1324',
|
|
'swap': 0,
|
|
'rxtx_factor': 0.5,
|
|
'is_public': False,
|
|
}
|
|
flavors.create(**values)
|
|
ex = self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self._test_create_instance,
|
|
flavor=1324)
|
|
self.assertEqual('Flavor 1324 could not be found.', six.text_type(ex))
|
|
|
|
def test_create_server_bad_image_uuid(self):
|
|
self.body['server']['min_count'] = 1
|
|
self.body['server']['imageRef'] = 1,
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.create,
|
|
self.req, body=self.body)
|
|
# TODO(cyeoh): bp-v3-api-unittests
|
|
# This needs to be ported to the os-networks extension tests
|
|
# def test_create_server_with_invalid_networks_parameter(self):
|
|
# self.ext_mgr.extensions = {'os-networks': 'fake'}
|
|
# image_href = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
|
# flavor_ref = 'http://localhost/123/flavors/3'
|
|
# body = {
|
|
# 'server': {
|
|
# 'name': 'server_test',
|
|
# 'imageRef': image_href,
|
|
# 'flavorRef': flavor_ref,
|
|
# 'networks': {'uuid': '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'},
|
|
# }
|
|
# }
|
|
# req = fakes.HTTPRequest.blank('/fake/servers')
|
|
# req.method = 'POST'
|
|
# req.body = jsonutils.dump_as_bytes(body)
|
|
# req.headers["content-type"] = "application/json"
|
|
# self.assertRaises(webob.exc.HTTPBadRequest,
|
|
# self.controller.create,
|
|
# req,
|
|
# body)
|
|
|
|
def test_create_server_with_deleted_image(self):
|
|
# Get the fake image service so we can set the status to deleted
|
|
(image_service, image_id) = glance.get_remote_image_service(
|
|
context, '')
|
|
image_service.update(context, self.image_uuid, {'status': 'DELETED'})
|
|
self.addCleanup(image_service.update, context, self.image_uuid,
|
|
{'status': 'active'})
|
|
|
|
self.body['server']['flavorRef'] = 2
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
with testtools.ExpectedException(
|
|
webob.exc.HTTPBadRequest,
|
|
'Image 76fa36fc-c930-4bf3-8c8a-ea2a2420deb6 is not active.'):
|
|
self.controller.create(self.req, body=self.body)
|
|
|
|
def test_create_server_image_too_large(self):
|
|
# Get the fake image service so we can update the size of the image
|
|
(image_service, image_id) = glance.get_remote_image_service(
|
|
context, self.image_uuid)
|
|
|
|
image = image_service.show(context, image_id)
|
|
|
|
orig_size = image['size']
|
|
new_size = str(1000 * (1024 ** 3))
|
|
image_service.update(context, self.image_uuid, {'size': new_size})
|
|
|
|
self.addCleanup(image_service.update, context, self.image_uuid,
|
|
{'size': orig_size})
|
|
|
|
self.body['server']['flavorRef'] = 2
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
|
|
with testtools.ExpectedException(
|
|
webob.exc.HTTPBadRequest,
|
|
"Flavor's disk is too small for requested image."):
|
|
self.controller.create(self.req, body=self.body)
|
|
|
|
def test_create_instance_with_image_non_uuid(self):
|
|
self.body['server']['imageRef'] = 'not-uuid'
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.create,
|
|
self.req, body=self.body)
|
|
|
|
def test_create_instance_with_image_as_full_url(self):
|
|
image_href = ('http://localhost/v2/fake/images/'
|
|
'76fa36fc-c930-4bf3-8c8a-ea2a2420deb6')
|
|
self.body['server']['imageRef'] = image_href
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.create,
|
|
self.req, body=self.body)
|
|
|
|
def test_create_instance_with_image_as_empty_string(self):
|
|
self.body['server']['imageRef'] = ''
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.create,
|
|
self.req, body=self.body)
|
|
|
|
def test_create_instance_no_key_pair(self):
|
|
fakes.stub_out_key_pair_funcs(self, have_key_pair=False)
|
|
self._test_create_instance()
|
|
|
|
def _test_create_extra(self, params, no_image=False):
|
|
self.body['server']['flavorRef'] = 2
|
|
if no_image:
|
|
self.body['server'].pop('imageRef', None)
|
|
self.body['server'].update(params)
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.req.headers["content-type"] = "application/json"
|
|
self.controller.create(self.req, body=self.body).obj['server']
|
|
|
|
# TODO(cyeoh): bp-v3-api-unittests
|
|
# This needs to be ported to the os-keypairs extension tests
|
|
# def test_create_instance_with_keypairs_enabled(self):
|
|
# self.ext_mgr.extensions = {'os-keypairs': 'fake'}
|
|
# key_name = 'green'
|
|
#
|
|
# params = {'key_name': key_name}
|
|
# old_create = compute_api.API.create
|
|
#
|
|
# # NOTE(sdague): key pair goes back to the database,
|
|
# # so we need to stub it out for tests
|
|
# def key_pair_get(context, user_id, name):
|
|
# return {'public_key': 'FAKE_KEY',
|
|
# 'fingerprint': 'FAKE_FINGERPRINT',
|
|
# 'name': name}
|
|
#
|
|
# def create(*args, **kwargs):
|
|
# self.assertEqual(kwargs['key_name'], key_name)
|
|
# return old_create(*args, **kwargs)
|
|
#
|
|
# self.stub_out('nova.db.api.key_pair_get', key_pair_get)
|
|
# 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-networks extension tests
|
|
# def test_create_instance_with_networks_enabled(self):
|
|
# self.ext_mgr.extensions = {'os-networks': 'fake'}
|
|
# net_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
|
# requested_networks = [{'uuid': net_uuid}]
|
|
# params = {'networks': requested_networks}
|
|
# old_create = compute_api.API.create
|
|
|
|
# def create(*args, **kwargs):
|
|
# result = [('76fa36fc-c930-4bf3-8c8a-ea2a2420deb6', None)]
|
|
# self.assertEqual(kwargs['requested_networks'], result)
|
|
# return old_create(*args, **kwargs)
|
|
|
|
# self.stubs.Set(compute_api.API, 'create', create)
|
|
# self._test_create_extra(params)
|
|
|
|
@mock.patch.object(compute_api.API, 'create',
|
|
side_effect=exception.PortRequiresFixedIP(
|
|
port_id=uuids.port))
|
|
def test_create_instance_with_port_with_no_fixed_ips(self, mock_create):
|
|
requested_networks = [{'port': uuids.port}]
|
|
params = {'networks': requested_networks}
|
|
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self._test_create_extra, params)
|
|
|
|
def test_create_instance_raise_user_data_too_large(self):
|
|
self.body['server']['user_data'] = (b'1' * 65536)
|
|
ex = self.assertRaises(exception.ValidationError,
|
|
self.controller.create,
|
|
self.req, body=self.body)
|
|
# Make sure the failure was about user_data and not something else.
|
|
self.assertIn('user_data', six.text_type(ex))
|
|
|
|
@mock.patch.object(compute_api.API, 'create',
|
|
side_effect=exception.NetworkRequiresSubnet(
|
|
network_uuid=uuids.network))
|
|
def test_create_instance_with_network_with_no_subnet(self, mock_create):
|
|
requested_networks = [{'uuid': uuids.network}]
|
|
params = {'networks': requested_networks}
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self._test_create_extra, params)
|
|
|
|
@mock.patch.object(compute_api.API, 'create',
|
|
side_effect=exception.NoUniqueMatch(
|
|
"No Unique match found for ..."))
|
|
def test_create_instance_with_non_unique_secgroup_name(self, mock_create):
|
|
requested_networks = [{'uuid': uuids.network}]
|
|
params = {'networks': requested_networks,
|
|
'security_groups': [{'name': 'dup'}, {'name': 'dup'}]}
|
|
self.assertRaises(webob.exc.HTTPConflict,
|
|
self._test_create_extra, params)
|
|
|
|
def test_create_instance_secgroup_leading_trailing_spaces(self):
|
|
network = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee'
|
|
requested_networks = [{'uuid': network}]
|
|
params = {'networks': requested_networks,
|
|
'security_groups': [{'name': ' sg '}]}
|
|
|
|
self.assertRaises(exception.ValidationError,
|
|
self._test_create_extra, params)
|
|
|
|
@mock.patch.object(compute_api.API, 'create')
|
|
def test_create_instance_secgroup_leading_trailing_spaces_compat_mode(
|
|
self, mock_create):
|
|
requested_networks = [{'uuid': uuids.network}]
|
|
params = {'networks': requested_networks,
|
|
'security_groups': [{'name': ' sg '}]}
|
|
|
|
def fake_create(*args, **kwargs):
|
|
self.assertEqual([' sg '], kwargs['security_groups'])
|
|
return (objects.InstanceList(objects=[fakes.stub_instance_obj(
|
|
self.req.environ['nova.context'])]), None)
|
|
mock_create.side_effect = fake_create
|
|
|
|
self.req.set_legacy_v2()
|
|
self._test_create_extra(params)
|
|
|
|
def test_create_instance_with_networks_disabled_neutronv2(self):
|
|
self.flags(use_neutron=True)
|
|
net_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
|
requested_networks = [{'uuid': net_uuid}]
|
|
params = {'networks': requested_networks}
|
|
old_create = compute_api.API.create
|
|
|
|
def create(*args, **kwargs):
|
|
result = [('76fa36fc-c930-4bf3-8c8a-ea2a2420deb6', None,
|
|
None, None)]
|
|
self.assertEqual(result, kwargs['requested_networks'].as_tuples())
|
|
return old_create(*args, **kwargs)
|
|
|
|
with mock.patch('nova.compute.api.API.create', create):
|
|
self._test_create_extra(params)
|
|
|
|
def test_create_instance_with_pass_disabled(self):
|
|
# test with admin passwords disabled See lp bug 921814
|
|
self.flags(enable_instance_password=False, group='api')
|
|
self.stub_out('uuid.uuid4', lambda: FAKE_UUID)
|
|
self.flags(enable_instance_password=False, group='api')
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
res = self.controller.create(self.req, body=self.body).obj
|
|
|
|
server = res['server']
|
|
self._check_admin_password_missing(server)
|
|
self.assertEqual(FAKE_UUID, server['id'])
|
|
|
|
def test_create_instance_name_too_long(self):
|
|
self.body['server']['name'] = 'X' * 256
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(exception.ValidationError, self.controller.create,
|
|
self.req, body=self.body)
|
|
|
|
def test_create_instance_name_with_spaces_in_the_middle(self):
|
|
self.body['server']['name'] = 'abc def'
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.controller.create(self.req, body=self.body)
|
|
|
|
def test_create_instance_name_with_leading_trailing_spaces(self):
|
|
self.body['server']['name'] = ' abc def '
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.create, self.req, body=self.body)
|
|
|
|
def test_create_instance_name_with_leading_trailing_spaces_in_compat_mode(
|
|
self):
|
|
self.body['server']['name'] = ' abc def '
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.req.set_legacy_v2()
|
|
self.controller.create(self.req, body=self.body)
|
|
|
|
def test_create_instance_name_all_blank_spaces(self):
|
|
image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
|
flavor_ref = 'http://localhost/fake/flavors/3'
|
|
body = {
|
|
'server': {
|
|
'name': ' ' * 64,
|
|
'imageRef': image_uuid,
|
|
'flavorRef': flavor_ref,
|
|
'metadata': {
|
|
'hello': 'world',
|
|
'open': 'stack',
|
|
},
|
|
},
|
|
}
|
|
|
|
req = fakes.HTTPRequest.blank('/fake/servers')
|
|
req.method = 'POST'
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
req.headers["content-type"] = "application/json"
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.create, req, body=body)
|
|
|
|
def test_create_az_with_leading_trailing_spaces(self):
|
|
self.body['server']['availability_zone'] = ' zone1 '
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.create, self.req, body=self.body)
|
|
|
|
def test_create_az_with_leading_trailing_spaces_in_compat_mode(
|
|
self):
|
|
self.body['server']['name'] = ' abc def '
|
|
self.body['server']['availability_zones'] = ' zone1 '
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.req.set_legacy_v2()
|
|
with mock.patch.object(availability_zones, 'get_availability_zones',
|
|
return_value=[' zone1 ']):
|
|
self.controller.create(self.req, body=self.body)
|
|
|
|
def test_create_instance(self):
|
|
self.stub_out('uuid.uuid4', lambda: FAKE_UUID)
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
res = self.controller.create(self.req, body=self.body).obj
|
|
|
|
server = res['server']
|
|
self._check_admin_password_len(server)
|
|
self.assertEqual(FAKE_UUID, server['id'])
|
|
|
|
def test_create_instance_pass_disabled(self):
|
|
self.stub_out('uuid.uuid4', lambda: FAKE_UUID)
|
|
self.flags(enable_instance_password=False, group='api')
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
res = self.controller.create(self.req, body=self.body).obj
|
|
|
|
server = res['server']
|
|
self._check_admin_password_missing(server)
|
|
self.assertEqual(FAKE_UUID, server['id'])
|
|
|
|
@mock.patch('nova.virt.hardware.numa_get_constraints')
|
|
def _test_create_instance_numa_topology_wrong(self, exc,
|
|
numa_constraints_mock):
|
|
numa_constraints_mock.side_effect = exc(**{'name': None,
|
|
'cpunum': 0,
|
|
'cpumax': 0,
|
|
'cpuset': None,
|
|
'memsize': 0,
|
|
'memtotal': 0})
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.create, self.req, body=self.body)
|
|
|
|
def test_create_instance_numa_topology_wrong(self):
|
|
for exc in [exception.ImageNUMATopologyIncomplete,
|
|
exception.ImageNUMATopologyForbidden,
|
|
exception.ImageNUMATopologyAsymmetric,
|
|
exception.ImageNUMATopologyCPUOutOfRange,
|
|
exception.ImageNUMATopologyCPUDuplicates,
|
|
exception.ImageNUMATopologyCPUsUnassigned,
|
|
exception.ImageNUMATopologyMemoryOutOfRange]:
|
|
self._test_create_instance_numa_topology_wrong(exc)
|
|
|
|
def test_create_instance_too_much_metadata(self):
|
|
self.flags(metadata_items=1, group='quota')
|
|
self.body['server']['metadata']['vote'] = 'fiddletown'
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(webob.exc.HTTPForbidden,
|
|
self.controller.create, self.req, body=self.body)
|
|
|
|
def test_create_instance_metadata_key_too_long(self):
|
|
self.flags(metadata_items=1, group='quota')
|
|
self.body['server']['metadata'] = {('a' * 260): '12345'}
|
|
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.create, self.req, body=self.body)
|
|
|
|
def test_create_instance_metadata_value_too_long(self):
|
|
self.flags(metadata_items=1, group='quota')
|
|
self.body['server']['metadata'] = {'key1': ('a' * 260)}
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.create, self.req, body=self.body)
|
|
|
|
def test_create_instance_metadata_key_blank(self):
|
|
self.flags(metadata_items=1, group='quota')
|
|
self.body['server']['metadata'] = {'': 'abcd'}
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.create, self.req, body=self.body)
|
|
|
|
def test_create_instance_metadata_not_dict(self):
|
|
self.flags(metadata_items=1, group='quota')
|
|
self.body['server']['metadata'] = 'string'
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.create, self.req, body=self.body)
|
|
|
|
def test_create_instance_metadata_key_not_string(self):
|
|
self.flags(metadata_items=1, group='quota')
|
|
self.body['server']['metadata'] = {1: 'test'}
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.create, self.req, body=self.body)
|
|
|
|
def test_create_instance_metadata_value_not_string(self):
|
|
self.flags(metadata_items=1, group='quota')
|
|
self.body['server']['metadata'] = {'test': ['a', 'list']}
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.create, self.req, body=self.body)
|
|
|
|
def test_create_user_data_malformed_bad_request(self):
|
|
params = {'user_data': 'u1234'}
|
|
self.assertRaises(exception.ValidationError,
|
|
self._test_create_extra, params)
|
|
|
|
def _create_instance_body_of_config_drive(self, param):
|
|
def create(*args, **kwargs):
|
|
self.assertIn('config_drive', kwargs)
|
|
return old_create(*args, **kwargs)
|
|
|
|
old_create = compute_api.API.create
|
|
self.stub_out('nova.compute.api.API.create', create)
|
|
self.body['server']['config_drive'] = param
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
|
|
def test_create_instance_with_config_drive(self):
|
|
param = True
|
|
self._create_instance_body_of_config_drive(param)
|
|
self.controller.create(self.req, body=self.body).obj
|
|
|
|
def test_create_instance_with_config_drive_as_boolean_string(self):
|
|
param = 'false'
|
|
self._create_instance_body_of_config_drive(param)
|
|
self.controller.create(self.req, body=self.body).obj
|
|
|
|
def test_create_instance_with_bad_config_drive(self):
|
|
param = 12345
|
|
self._create_instance_body_of_config_drive(param)
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.create, self.req, body=self.body)
|
|
|
|
def test_create_instance_without_config_drive(self):
|
|
def create(*args, **kwargs):
|
|
self.assertIsNone(kwargs['config_drive'])
|
|
return old_create(*args, **kwargs)
|
|
|
|
old_create = compute_api.API.create
|
|
self.stub_out('nova.compute.api.API.create', create)
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.controller.create(self.req, body=self.body).obj
|
|
|
|
def test_create_instance_with_empty_config_drive(self):
|
|
param = ''
|
|
self._create_instance_body_of_config_drive(param)
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.create, self.req, body=self.body)
|
|
|
|
def _test_create(self, params, no_image=False):
|
|
self. body['server'].update(params)
|
|
if no_image:
|
|
del self.body['server']['imageRef']
|
|
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.controller.create(self.req, body=self.body).obj['server']
|
|
|
|
def test_create_instance_with_volumes_enabled_no_image(self):
|
|
"""Test that the create will fail if there is no image
|
|
and no bdms supplied in the request
|
|
"""
|
|
old_create = compute_api.API.create
|
|
|
|
def create(*args, **kwargs):
|
|
self.assertNotIn('imageRef', kwargs)
|
|
return old_create(*args, **kwargs)
|
|
|
|
self.stub_out('nova.compute.api.API.create', create)
|
|
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self._test_create, {}, no_image=True)
|
|
|
|
@mock.patch.object(compute_api.API, '_validate_bdm')
|
|
@mock.patch.object(compute_api.API, '_get_bdm_image_metadata')
|
|
def test_create_instance_with_bdms_and_no_image(
|
|
self, mock_bdm_image_metadata, mock_validate_bdm):
|
|
mock_bdm_image_metadata.return_value = {}
|
|
mock_validate_bdm.return_value = True
|
|
old_create = compute_api.API.create
|
|
|
|
def create(*args, **kwargs):
|
|
self.assertThat(
|
|
block_device.BlockDeviceDict(self.bdm_v2[0]),
|
|
matchers.DictMatches(kwargs['block_device_mapping'][0])
|
|
)
|
|
return old_create(*args, **kwargs)
|
|
|
|
self.stub_out('nova.compute.api.API.create', create)
|
|
|
|
params = {'block_device_mapping_v2': self.bdm_v2}
|
|
self._test_create(params, no_image=True)
|
|
|
|
mock_validate_bdm.assert_called_once_with(
|
|
mock.ANY, mock.ANY, mock.ANY, mock.ANY, mock.ANY)
|
|
mock_bdm_image_metadata.assert_called_once_with(
|
|
mock.ANY, mock.ANY, False)
|
|
|
|
@mock.patch.object(compute_api.API, '_validate_bdm')
|
|
@mock.patch.object(compute_api.API, '_get_bdm_image_metadata')
|
|
def test_create_instance_with_bdms_and_empty_imageRef(
|
|
self, mock_bdm_image_metadata, mock_validate_bdm):
|
|
mock_bdm_image_metadata.return_value = {}
|
|
mock_validate_bdm.return_value = True
|
|
old_create = compute_api.API.create
|
|
|
|
def create(*args, **kwargs):
|
|
self.assertThat(
|
|
block_device.BlockDeviceDict(self.bdm_v2[0]),
|
|
matchers.DictMatches(kwargs['block_device_mapping'][0])
|
|
)
|
|
return old_create(*args, **kwargs)
|
|
|
|
self.stub_out('nova.compute.api.API.create', create)
|
|
|
|
params = {'block_device_mapping_v2': self.bdm_v2,
|
|
'imageRef': ''}
|
|
self._test_create(params)
|
|
|
|
def test_create_instance_with_imageRef_as_full_url(self):
|
|
bdm = [{'device_name': 'foo'}]
|
|
image_href = ('http://localhost/v2/fake/images/'
|
|
'76fa36fc-c930-4bf3-8c8a-ea2a2420deb6')
|
|
params = {'block_device_mapping_v2': bdm,
|
|
'imageRef': image_href}
|
|
self.assertRaises(exception.ValidationError,
|
|
self._test_create, params)
|
|
|
|
def test_create_instance_with_non_uuid_imageRef(self):
|
|
bdm = [{'device_name': 'foo'}]
|
|
|
|
params = {'block_device_mapping_v2': bdm,
|
|
'imageRef': '123123abcd'}
|
|
self.assertRaises(exception.ValidationError,
|
|
self._test_create, params)
|
|
|
|
def test_create_instance_with_invalid_bdm_in_2nd_dict(self):
|
|
bdm_1st = {"source_type": "image", "delete_on_termination": True,
|
|
"boot_index": 0,
|
|
"uuid": "2ff3a1d3-ed70-4c3f-94ac-941461153bc0",
|
|
"destination_type": "local"}
|
|
bdm_2nd = {"source_type": "volume",
|
|
"uuid": "99d92140-3d0c-4ea5-a49c-f94c38c607f0",
|
|
"destination_type": "invalid"}
|
|
bdm = [bdm_1st, bdm_2nd]
|
|
|
|
params = {'block_device_mapping_v2': bdm,
|
|
'imageRef': '2ff3a1d3-ed70-4c3f-94ac-941461153bc0'}
|
|
self.assertRaises(exception.ValidationError,
|
|
self._test_create, params)
|
|
|
|
def test_create_instance_with_boot_index_none_ok(self):
|
|
"""Tests creating a server with two block devices. One is the boot
|
|
device and the other is a non-bootable device.
|
|
"""
|
|
# From the docs:
|
|
# To disable a device from booting, set the boot index to a negative
|
|
# value or use the default boot index value, which is None. The
|
|
# simplest usage is, set the boot index of the boot device to 0 and use
|
|
# the default boot index value, None, for any other devices.
|
|
bdms = [
|
|
# This is the bootable device that would create a 20GB cinder
|
|
# volume from the given image.
|
|
{
|
|
'source_type': 'image',
|
|
'destination_type': 'volume',
|
|
'boot_index': 0,
|
|
'uuid': '155d900f-4e14-4e4c-a73d-069cbf4541e6',
|
|
'volume_size': 20
|
|
},
|
|
# This is the non-bootable 10GB ext4 ephemeral block device.
|
|
{
|
|
'source_type': 'blank',
|
|
'destination_type': 'local',
|
|
'boot_index': None,
|
|
# If 'guest_format' is 'swap' then a swap device is created.
|
|
'guest_format': 'ext4'
|
|
}
|
|
]
|
|
params = {'block_device_mapping_v2': bdms}
|
|
self._test_create(params, no_image=True)
|
|
|
|
def test_create_instance_with_boot_index_none_image_local_fails(self):
|
|
"""Tests creating a server with a local image-based block device which
|
|
has a boot_index of None which is invalid.
|
|
"""
|
|
bdms = [{
|
|
'source_type': 'image',
|
|
'destination_type': 'local',
|
|
'boot_index': None,
|
|
'uuid': '155d900f-4e14-4e4c-a73d-069cbf4541e6'
|
|
}]
|
|
params = {'block_device_mapping_v2': bdms}
|
|
self.assertRaises(webob.exc.HTTPBadRequest, self._test_create,
|
|
params, no_image=True)
|
|
|
|
def test_create_instance_with_invalid_boot_index(self):
|
|
bdm = [{"source_type": "image", "delete_on_termination": True,
|
|
"boot_index": 'invalid',
|
|
"uuid": "2ff3a1d3-ed70-4c3f-94ac-941461153bc0",
|
|
"destination_type": "local"}]
|
|
|
|
params = {'block_device_mapping_v2': bdm,
|
|
'imageRef': '2ff3a1d3-ed70-4c3f-94ac-941461153bc0'}
|
|
self.assertRaises(exception.ValidationError,
|
|
self._test_create, params)
|
|
|
|
def test_create_instance_with_device_name_not_string(self):
|
|
self.bdm_v2[0]['device_name'] = 123
|
|
old_create = compute_api.API.create
|
|
|
|
def create(*args, **kwargs):
|
|
self.assertEqual(kwargs['block_device_mapping'], self.bdm_v2)
|
|
return old_create(*args, **kwargs)
|
|
|
|
self.stub_out('nova.compute.api.API.create', create)
|
|
|
|
params = {'block_device_mapping_v2': self.bdm_v2}
|
|
self.assertRaises(exception.ValidationError,
|
|
self._test_create, params, no_image=True)
|
|
|
|
@mock.patch.object(compute_api.API, 'create')
|
|
def test_create_instance_with_bdm_param_not_list(self, mock_create):
|
|
self.params = {'block_device_mapping': '/dev/vdb'}
|
|
self.assertRaises(exception.ValidationError,
|
|
self._test_create, self.params)
|
|
|
|
def test_create_instance_with_device_name_empty(self):
|
|
self.bdm_v2[0]['device_name'] = ''
|
|
|
|
old_create = compute_api.API.create
|
|
|
|
def create(*args, **kwargs):
|
|
self.assertEqual(kwargs['block_device_mapping'], self.bdm_v2)
|
|
return old_create(*args, **kwargs)
|
|
|
|
self.stub_out('nova.compute.api.API.create', create)
|
|
|
|
params = {'block_device_mapping_v2': self.bdm_v2}
|
|
self.assertRaises(exception.ValidationError,
|
|
self._test_create, params, no_image=True)
|
|
|
|
def test_create_instance_with_device_name_too_long(self):
|
|
self.bdm_v2[0]['device_name'] = 'a' * 256
|
|
|
|
old_create = compute_api.API.create
|
|
|
|
def create(*args, **kwargs):
|
|
self.assertEqual(kwargs['block_device_mapping'], self.bdm_v2)
|
|
return old_create(*args, **kwargs)
|
|
|
|
self.stub_out('nova.compute.api.API.create', create)
|
|
|
|
params = {'block_device_mapping_v2': self.bdm_v2}
|
|
self.assertRaises(exception.ValidationError,
|
|
self._test_create, params, no_image=True)
|
|
|
|
def test_create_instance_with_space_in_device_name(self):
|
|
self.bdm_v2[0]['device_name'] = 'v da'
|
|
|
|
old_create = compute_api.API.create
|
|
|
|
def create(*args, **kwargs):
|
|
self.assertTrue(kwargs['legacy_bdm'])
|
|
self.assertEqual(kwargs['block_device_mapping'], self.bdm_v2)
|
|
return old_create(*args, **kwargs)
|
|
|
|
self.stub_out('nova.compute.api.API.create', create)
|
|
|
|
params = {'block_device_mapping_v2': self.bdm_v2}
|
|
self.assertRaises(exception.ValidationError,
|
|
self._test_create, params, no_image=True)
|
|
|
|
def test_create_instance_with_invalid_size(self):
|
|
self.bdm_v2[0]['volume_size'] = 'hello world'
|
|
|
|
old_create = compute_api.API.create
|
|
|
|
def create(*args, **kwargs):
|
|
self.assertEqual(kwargs['block_device_mapping'], self.bdm_v2)
|
|
return old_create(*args, **kwargs)
|
|
|
|
self.stub_out('nova.compute.api.API.create', create)
|
|
|
|
params = {'block_device_mapping_v2': self.bdm_v2}
|
|
self.assertRaises(exception.ValidationError,
|
|
self._test_create, params, no_image=True)
|
|
|
|
def _test_create_instance_with_destination_type_error(self,
|
|
destination_type):
|
|
self.bdm_v2[0]['destination_type'] = destination_type
|
|
|
|
params = {'block_device_mapping_v2': self.bdm_v2}
|
|
self.assertRaises(exception.ValidationError,
|
|
self._test_create, params, no_image=True)
|
|
|
|
def test_create_instance_with_destination_type_empty_string(self):
|
|
self._test_create_instance_with_destination_type_error('')
|
|
|
|
def test_create_instance_with_invalid_destination_type(self):
|
|
self._test_create_instance_with_destination_type_error('fake')
|
|
|
|
@mock.patch.object(compute_api.API, '_validate_bdm')
|
|
def test_create_instance_bdm(self, mock_validate_bdm):
|
|
bdm = [{
|
|
'source_type': 'volume',
|
|
'device_name': 'fake_dev',
|
|
'uuid': 'fake_vol'
|
|
}]
|
|
bdm_expected = [{
|
|
'source_type': 'volume',
|
|
'device_name': 'fake_dev',
|
|
'volume_id': 'fake_vol'
|
|
}]
|
|
|
|
old_create = compute_api.API.create
|
|
|
|
def create(*args, **kwargs):
|
|
self.assertFalse(kwargs['legacy_bdm'])
|
|
for expected, received in zip(bdm_expected,
|
|
kwargs['block_device_mapping']):
|
|
self.assertThat(block_device.BlockDeviceDict(expected),
|
|
matchers.DictMatches(received))
|
|
return old_create(*args, **kwargs)
|
|
|
|
self.stub_out('nova.compute.api.API.create', create)
|
|
|
|
params = {'block_device_mapping_v2': bdm}
|
|
self._test_create(params, no_image=True)
|
|
mock_validate_bdm.assert_called_once_with(mock.ANY,
|
|
mock.ANY,
|
|
mock.ANY,
|
|
mock.ANY,
|
|
mock.ANY)
|
|
|
|
@mock.patch.object(compute_api.API, '_validate_bdm')
|
|
def test_create_instance_bdm_missing_device_name(self, mock_validate_bdm):
|
|
del self.bdm_v2[0]['device_name']
|
|
|
|
old_create = compute_api.API.create
|
|
|
|
def create(*args, **kwargs):
|
|
self.assertFalse(kwargs['legacy_bdm'])
|
|
self.assertNotIn(None,
|
|
kwargs['block_device_mapping'][0]['device_name'])
|
|
return old_create(*args, **kwargs)
|
|
|
|
self.stub_out('nova.compute.api.API.create', create)
|
|
|
|
params = {'block_device_mapping_v2': self.bdm_v2}
|
|
self._test_create(params, no_image=True)
|
|
mock_validate_bdm.assert_called_once_with(mock.ANY,
|
|
mock.ANY,
|
|
mock.ANY,
|
|
mock.ANY,
|
|
mock.ANY)
|
|
|
|
@mock.patch.object(
|
|
block_device.BlockDeviceDict, '_validate',
|
|
side_effect=exception.InvalidBDMFormat(details='Wrong BDM'))
|
|
def test_create_instance_bdm_validation_error(self, mock_validate):
|
|
params = {'block_device_mapping_v2': self.bdm_v2}
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self._test_create, params, no_image=True)
|
|
|
|
@mock.patch('nova.compute.api.API._get_bdm_image_metadata')
|
|
def test_create_instance_non_bootable_volume_fails(self, fake_bdm_meta):
|
|
params = {'block_device_mapping_v2': self.bdm_v2}
|
|
fake_bdm_meta.side_effect = exception.InvalidBDMVolumeNotBootable(id=1)
|
|
self.assertRaises(webob.exc.HTTPBadRequest, self._test_create, params,
|
|
no_image=True)
|
|
|
|
def test_create_instance_bdm_api_validation_fails(self):
|
|
self.validation_fail_test_validate_called = False
|
|
self.validation_fail_instance_destroy_called = False
|
|
|
|
bdm_exceptions = ((exception.InvalidBDMSnapshot, {'id': 'fake'}),
|
|
(exception.InvalidBDMVolume, {'id': 'fake'}),
|
|
(exception.InvalidBDMImage, {'id': 'fake'}),
|
|
(exception.InvalidBDMBootSequence, {}),
|
|
(exception.InvalidBDMLocalsLimit, {}))
|
|
|
|
ex_iter = iter(bdm_exceptions)
|
|
|
|
def _validate_bdm(*args, **kwargs):
|
|
self.validation_fail_test_validate_called = True
|
|
ex, kargs = next(ex_iter)
|
|
raise ex(**kargs)
|
|
|
|
def _instance_destroy(*args, **kwargs):
|
|
self.validation_fail_instance_destroy_called = True
|
|
|
|
self.stub_out('nova.compute.api.API._validate_bdm', _validate_bdm)
|
|
self.stub_out('nova.objects.Instance.destroy', _instance_destroy)
|
|
|
|
for _unused in range(len(bdm_exceptions)):
|
|
params = {'block_device_mapping_v2':
|
|
[self.bdm_v2[0].copy()]}
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self._test_create, params)
|
|
self.assertTrue(self.validation_fail_test_validate_called)
|
|
self.assertFalse(self.validation_fail_instance_destroy_called)
|
|
self.validation_fail_test_validate_called = False
|
|
self.validation_fail_instance_destroy_called = False
|
|
|
|
@mock.patch.object(compute_api.API, '_validate_bdm')
|
|
def _test_create_bdm(self, params, mock_validate_bdm, no_image=False):
|
|
self.body['server'].update(params)
|
|
if no_image:
|
|
del self.body['server']['imageRef']
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
|
|
self.controller.create(self.req, body=self.body).obj['server']
|
|
mock_validate_bdm.assert_called_once_with(
|
|
test.MatchType(fakes.FakeRequestContext),
|
|
test.MatchType(objects.Instance),
|
|
test.MatchType(objects.Flavor),
|
|
test.MatchType(objects.BlockDeviceMappingList),
|
|
False)
|
|
|
|
def test_create_instance_with_volumes_enabled(self):
|
|
params = {'block_device_mapping': self.bdm}
|
|
old_create = compute_api.API.create
|
|
|
|
def create(*args, **kwargs):
|
|
self.assertEqual(kwargs['block_device_mapping'], self.bdm)
|
|
return old_create(*args, **kwargs)
|
|
|
|
self.stub_out('nova.compute.api.API.create', create)
|
|
self._test_create_bdm(params)
|
|
|
|
@mock.patch.object(compute_api.API, '_get_bdm_image_metadata')
|
|
def test_create_instance_with_volumes_enabled_and_bdms_no_image(
|
|
self, mock_get_bdm_image_metadata):
|
|
"""Test that the create works if there is no image supplied but
|
|
os-volumes extension is enabled and bdms are supplied
|
|
"""
|
|
volume = {
|
|
'id': uuids.volume_id,
|
|
'status': 'active',
|
|
'volume_image_metadata':
|
|
{'test_key': 'test_value'}
|
|
}
|
|
mock_get_bdm_image_metadata.return_value = volume
|
|
params = {'block_device_mapping': self.bdm}
|
|
old_create = compute_api.API.create
|
|
|
|
def create(*args, **kwargs):
|
|
self.assertEqual(kwargs['block_device_mapping'], self.bdm)
|
|
self.assertNotIn('imageRef', kwargs)
|
|
return old_create(*args, **kwargs)
|
|
|
|
self.stub_out('nova.compute.api.API.create', create)
|
|
self._test_create_bdm(params, no_image=True)
|
|
mock_get_bdm_image_metadata.assert_called_once_with(
|
|
mock.ANY, self.bdm, True)
|
|
|
|
@mock.patch.object(compute_api.API, '_get_bdm_image_metadata')
|
|
def test_create_instance_with_imageRef_as_empty_string(
|
|
self, mock_bdm_image_metadata):
|
|
volume = {
|
|
'id': uuids.volume_id,
|
|
'status': 'active',
|
|
'volume_image_metadata':
|
|
{'test_key': 'test_value'}
|
|
}
|
|
mock_bdm_image_metadata.return_value = volume
|
|
params = {'block_device_mapping': self.bdm,
|
|
'imageRef': ''}
|
|
old_create = compute_api.API.create
|
|
|
|
def create(*args, **kwargs):
|
|
self.assertEqual(kwargs['block_device_mapping'], self.bdm)
|
|
return old_create(*args, **kwargs)
|
|
|
|
self.stub_out('nova.compute.api.API.create', create)
|
|
self._test_create_bdm(params)
|
|
|
|
def test_create_instance_with_imageRef_as_full_url_legacy_bdm(self):
|
|
bdm = [{
|
|
'volume_id': fakes.FAKE_UUID,
|
|
'device_name': 'vda'
|
|
}]
|
|
image_href = ('http://localhost/v2/fake/images/'
|
|
'76fa36fc-c930-4bf3-8c8a-ea2a2420deb6')
|
|
params = {'block_device_mapping': bdm,
|
|
'imageRef': image_href}
|
|
self.assertRaises(exception.ValidationError,
|
|
self._test_create_bdm, params)
|
|
|
|
def test_create_instance_with_non_uuid_imageRef_legacy_bdm(self):
|
|
bdm = [{
|
|
'volume_id': fakes.FAKE_UUID,
|
|
'device_name': 'vda'
|
|
}]
|
|
params = {'block_device_mapping': bdm,
|
|
'imageRef': 'bad-format'}
|
|
self.assertRaises(exception.ValidationError,
|
|
self._test_create_bdm, params)
|
|
|
|
@mock.patch('nova.compute.api.API._get_bdm_image_metadata')
|
|
def test_create_instance_non_bootable_volume_fails_legacy_bdm(
|
|
self, fake_bdm_meta):
|
|
bdm = [{
|
|
'volume_id': fakes.FAKE_UUID,
|
|
'device_name': 'vda'
|
|
}]
|
|
params = {'block_device_mapping': bdm}
|
|
fake_bdm_meta.side_effect = exception.InvalidBDMVolumeNotBootable(id=1)
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self._test_create_bdm, params, no_image=True)
|
|
|
|
def test_create_instance_with_device_name_not_string_legacy_bdm(self):
|
|
self.bdm[0]['device_name'] = 123
|
|
old_create = compute_api.API.create
|
|
self.params = {'block_device_mapping': self.bdm}
|
|
|
|
def create(*args, **kwargs):
|
|
self.assertEqual(kwargs['block_device_mapping'], self.bdm)
|
|
return old_create(*args, **kwargs)
|
|
|
|
self.stub_out('nova.compute.api.API.create', create)
|
|
self.assertRaises(exception.ValidationError,
|
|
self._test_create_bdm, self.params)
|
|
|
|
def test_create_instance_with_snapshot_volume_id_none(self):
|
|
old_create = compute_api.API.create
|
|
bdm = [{
|
|
'no_device': None,
|
|
'snapshot_id': None,
|
|
'volume_id': None,
|
|
'device_name': 'vda',
|
|
'delete_on_termination': False
|
|
}]
|
|
self.params = {'block_device_mapping': bdm}
|
|
|
|
def create(*args, **kwargs):
|
|
self.assertEqual(kwargs['block_device_mapping'], bdm)
|
|
return old_create(*args, **kwargs)
|
|
|
|
self.stub_out('nova.compute.api.API.create', create)
|
|
self.assertRaises(exception.ValidationError,
|
|
self._test_create_bdm, self.params)
|
|
|
|
@mock.patch.object(compute_api.API, 'create')
|
|
def test_create_instance_with_legacy_bdm_param_not_list(self, mock_create):
|
|
self.params = {'block_device_mapping': '/dev/vdb'}
|
|
self.assertRaises(exception.ValidationError,
|
|
self._test_create_bdm, self.params)
|
|
|
|
def test_create_instance_with_device_name_empty_legacy_bdm(self):
|
|
self.bdm[0]['device_name'] = ''
|
|
params = {'block_device_mapping': self.bdm}
|
|
old_create = compute_api.API.create
|
|
|
|
def create(*args, **kwargs):
|
|
self.assertEqual(kwargs['block_device_mapping'], self.bdm)
|
|
return old_create(*args, **kwargs)
|
|
|
|
self.stub_out('nova.compute.api.API.create', create)
|
|
self.assertRaises(exception.ValidationError,
|
|
self._test_create_bdm, params)
|
|
|
|
def test_create_instance_with_device_name_too_long_legacy_bdm(self):
|
|
self.bdm[0]['device_name'] = 'a' * 256,
|
|
params = {'block_device_mapping': self.bdm}
|
|
old_create = compute_api.API.create
|
|
|
|
def create(*args, **kwargs):
|
|
self.assertEqual(kwargs['block_device_mapping'], self.bdm)
|
|
return old_create(*args, **kwargs)
|
|
|
|
self.stub_out('nova.compute.api.API.create', create)
|
|
self.assertRaises(exception.ValidationError,
|
|
self._test_create_bdm, params)
|
|
|
|
def test_create_instance_with_space_in_device_name_legacy_bdm(self):
|
|
self.bdm[0]['device_name'] = 'vd a',
|
|
params = {'block_device_mapping': self.bdm}
|
|
old_create = compute_api.API.create
|
|
|
|
def create(*args, **kwargs):
|
|
self.assertTrue(kwargs['legacy_bdm'])
|
|
self.assertEqual(kwargs['block_device_mapping'], self.bdm)
|
|
return old_create(*args, **kwargs)
|
|
|
|
self.stub_out('nova.compute.api.API.create', create)
|
|
self.assertRaises(exception.ValidationError,
|
|
self._test_create_bdm, params)
|
|
|
|
def _test_create_bdm_instance_with_size_error(self, size):
|
|
bdm = [{'delete_on_termination': True,
|
|
'device_name': 'vda',
|
|
'volume_size': size,
|
|
'volume_id': '11111111-1111-1111-1111-111111111111'}]
|
|
params = {'block_device_mapping': bdm}
|
|
old_create = compute_api.API.create
|
|
|
|
def create(*args, **kwargs):
|
|
self.assertEqual(kwargs['block_device_mapping'], bdm)
|
|
return old_create(*args, **kwargs)
|
|
|
|
self.stub_out('nova.compute.api.API.create', create)
|
|
self.assertRaises(exception.ValidationError,
|
|
self._test_create_bdm, params)
|
|
|
|
def test_create_instance_with_invalid_size_legacy_bdm(self):
|
|
self._test_create_bdm_instance_with_size_error("hello world")
|
|
|
|
def test_create_instance_with_size_empty_string(self):
|
|
self._test_create_bdm_instance_with_size_error('')
|
|
|
|
def test_create_instance_with_size_zero(self):
|
|
self._test_create_bdm_instance_with_size_error("0")
|
|
|
|
def test_create_instance_with_size_greater_than_limit(self):
|
|
self._test_create_bdm_instance_with_size_error(db.MAX_INT + 1)
|
|
|
|
def test_create_instance_with_bdm_delete_on_termination(self):
|
|
bdm = [{'device_name': 'foo1', 'volume_id': fakes.FAKE_UUID,
|
|
'delete_on_termination': 'True'},
|
|
{'device_name': 'foo2', 'volume_id': fakes.FAKE_UUID,
|
|
'delete_on_termination': True},
|
|
{'device_name': 'foo3', 'volume_id': fakes.FAKE_UUID,
|
|
'delete_on_termination': 'False'},
|
|
{'device_name': 'foo4', 'volume_id': fakes.FAKE_UUID,
|
|
'delete_on_termination': False},
|
|
{'device_name': 'foo5', 'volume_id': fakes.FAKE_UUID,
|
|
'delete_on_termination': False}]
|
|
expected_bdm = [
|
|
{'device_name': 'foo1', 'volume_id': fakes.FAKE_UUID,
|
|
'delete_on_termination': True},
|
|
{'device_name': 'foo2', 'volume_id': fakes.FAKE_UUID,
|
|
'delete_on_termination': True},
|
|
{'device_name': 'foo3', 'volume_id': fakes.FAKE_UUID,
|
|
'delete_on_termination': False},
|
|
{'device_name': 'foo4', 'volume_id': fakes.FAKE_UUID,
|
|
'delete_on_termination': False},
|
|
{'device_name': 'foo5', 'volume_id': fakes.FAKE_UUID,
|
|
'delete_on_termination': False}]
|
|
params = {'block_device_mapping': bdm}
|
|
old_create = compute_api.API.create
|
|
|
|
def create(*args, **kwargs):
|
|
self.assertEqual(expected_bdm, kwargs['block_device_mapping'])
|
|
return old_create(*args, **kwargs)
|
|
|
|
self.stub_out('nova.compute.api.API.create', create)
|
|
self._test_create_bdm(params)
|
|
|
|
def test_create_instance_with_bdm_delete_on_termination_invalid_2nd(self):
|
|
bdm = [{'device_name': 'foo1', 'volume_id': fakes.FAKE_UUID,
|
|
'delete_on_termination': 'True'},
|
|
{'device_name': 'foo2', 'volume_id': fakes.FAKE_UUID,
|
|
'delete_on_termination': 'invalid'}]
|
|
|
|
params = {'block_device_mapping': bdm}
|
|
self.assertRaises(exception.ValidationError,
|
|
self._test_create_bdm, params)
|
|
|
|
def test_create_instance_decide_format_legacy(self):
|
|
bdm = [{'device_name': 'foo1',
|
|
'volume_id': fakes.FAKE_UUID,
|
|
'delete_on_termination': True}]
|
|
|
|
expected_legacy_flag = True
|
|
|
|
old_create = compute_api.API.create
|
|
|
|
def create(*args, **kwargs):
|
|
legacy_bdm = kwargs.get('legacy_bdm', True)
|
|
self.assertEqual(legacy_bdm, expected_legacy_flag)
|
|
return old_create(*args, **kwargs)
|
|
|
|
self.stub_out('nova.compute.api.API.create', create)
|
|
|
|
self._test_create_bdm({})
|
|
|
|
params = {'block_device_mapping': bdm}
|
|
self._test_create_bdm(params)
|
|
|
|
def test_create_instance_both_bdm_formats(self):
|
|
bdm = [{'device_name': 'foo'}]
|
|
bdm_v2 = [{'source_type': 'volume',
|
|
'uuid': 'fake_vol'}]
|
|
params = {'block_device_mapping': bdm,
|
|
'block_device_mapping_v2': bdm_v2}
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self._test_create_bdm, params)
|
|
|
|
def test_create_instance_invalid_key_name(self):
|
|
self.body['server']['key_name'] = 'nonexistentkey'
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.create, self.req, body=self.body)
|
|
|
|
def test_create_instance_valid_key_name(self):
|
|
self.stub_out('uuid.uuid4', lambda: FAKE_UUID)
|
|
self.body['server']['key_name'] = 'key'
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
res = self.controller.create(self.req, body=self.body).obj
|
|
|
|
self.assertEqual(FAKE_UUID, res["server"]["id"])
|
|
self._check_admin_password_len(res["server"])
|
|
|
|
def test_create_server_keypair_name_with_leading_trailing(self):
|
|
self.body['server']['key_name'] = ' abc '
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.create, self.req, body=self.body)
|
|
|
|
@mock.patch.object(compute_api.API, 'create')
|
|
def test_create_server_keypair_name_with_leading_trailing_compat_mode(
|
|
self, mock_create):
|
|
params = {'key_name': ' abc '}
|
|
|
|
def fake_create(*args, **kwargs):
|
|
self.assertEqual(' abc ', kwargs['key_name'])
|
|
return (objects.InstanceList(objects=[fakes.stub_instance_obj(
|
|
self.req.environ['nova.context'])]), None)
|
|
mock_create.side_effect = fake_create
|
|
|
|
self.req.set_legacy_v2()
|
|
self._test_create_extra(params)
|
|
|
|
def test_create_instance_invalid_flavor_href(self):
|
|
flavor_ref = 'http://localhost/v2/flavors/asdf'
|
|
self.body['server']['flavorRef'] = flavor_ref
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.create, self.req, body=self.body)
|
|
|
|
def test_create_instance_invalid_flavor_id_int(self):
|
|
flavor_ref = -1
|
|
self.body['server']['flavorRef'] = flavor_ref
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.create, self.req, body=self.body)
|
|
|
|
@mock.patch.object(nova.compute.flavors, 'get_flavor_by_flavor_id',
|
|
return_value=objects.Flavor())
|
|
@mock.patch.object(compute_api.API, 'create')
|
|
def test_create_instance_with_non_existing_snapshot_id(
|
|
self, mock_create,
|
|
mock_get_flavor_by_flavor_id):
|
|
mock_create.side_effect = exception.SnapshotNotFound(snapshot_id='123')
|
|
|
|
self.body['server'] = {'name': 'server_test',
|
|
'flavorRef': self.flavor_ref,
|
|
'block_device_mapping_v2':
|
|
[{'source_type': 'snapshot',
|
|
'uuid': '123'}]}
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.create, self.req, body=self.body)
|
|
|
|
def test_create_instance_invalid_flavor_id_empty(self):
|
|
flavor_ref = ""
|
|
self.body['server']['flavorRef'] = flavor_ref
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.create, self.req, body=self.body)
|
|
|
|
def test_create_instance_bad_flavor_href(self):
|
|
flavor_ref = 'http://localhost/v2/flavors/17'
|
|
self.body['server']['flavorRef'] = flavor_ref
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.create, self.req, body=self.body)
|
|
|
|
def test_create_instance_local_href(self):
|
|
self.stub_out('uuid.uuid4', lambda: FAKE_UUID)
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
res = self.controller.create(self.req, body=self.body).obj
|
|
|
|
server = res['server']
|
|
self.assertEqual(FAKE_UUID, server['id'])
|
|
|
|
def test_create_instance_admin_password(self):
|
|
self.body['server']['flavorRef'] = 3
|
|
self.body['server']['adminPass'] = 'testpass'
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
res = self.controller.create(self.req, body=self.body).obj
|
|
|
|
server = res['server']
|
|
self.assertEqual(server['adminPass'],
|
|
self.body['server']['adminPass'])
|
|
|
|
def test_create_instance_admin_password_pass_disabled(self):
|
|
self.flags(enable_instance_password=False, group='api')
|
|
self.body['server']['flavorRef'] = 3
|
|
self.body['server']['adminPass'] = 'testpass'
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
res = self.controller.create(self.req, body=self.body).obj
|
|
|
|
self.assertIn('server', res)
|
|
self.assertIn('adminPass', self.body['server'])
|
|
|
|
def test_create_instance_admin_password_empty(self):
|
|
self.body['server']['flavorRef'] = 3
|
|
self.body['server']['adminPass'] = ''
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
|
|
# The fact that the action doesn't raise is enough validation
|
|
self.controller.create(self.req, body=self.body)
|
|
|
|
def test_create_location(self):
|
|
self.stub_out('uuid.uuid4', lambda: FAKE_UUID)
|
|
selfhref = 'http://localhost/v2/fake/servers/%s' % FAKE_UUID
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
robj = self.controller.create(self.req, body=self.body)
|
|
|
|
self.assertEqual(encodeutils.safe_decode(robj['Location']), selfhref)
|
|
|
|
@mock.patch('nova.objects.Quotas.get_all_by_project')
|
|
@mock.patch('nova.objects.Quotas.get_all_by_project_and_user')
|
|
@mock.patch('nova.objects.Quotas.count_as_dict')
|
|
def _do_test_create_instance_above_quota(self, resource, allowed,
|
|
quota, expected_msg, mock_count, mock_get_all_pu,
|
|
mock_get_all_p):
|
|
count = {'project': {}, 'user': {}}
|
|
for res in ('instances', 'ram', 'cores'):
|
|
if res == resource:
|
|
value = quota - allowed
|
|
count['project'][res] = count['user'][res] = value
|
|
else:
|
|
count['project'][res] = count['user'][res] = 0
|
|
mock_count.return_value = count
|
|
mock_get_all_p.return_value = {'project_id': 'fake'}
|
|
mock_get_all_pu.return_value = {'project_id': 'fake',
|
|
'user_id': 'fake_user'}
|
|
if resource in db_api.PER_PROJECT_QUOTAS:
|
|
mock_get_all_p.return_value[resource] = quota
|
|
else:
|
|
mock_get_all_pu.return_value[resource] = quota
|
|
fakes.stub_out_instance_quota(self, allowed, quota, resource)
|
|
self.body['server']['flavorRef'] = 3
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
try:
|
|
self.controller.create(self.req, body=self.body).obj['server']
|
|
self.fail('expected quota to be exceeded')
|
|
except webob.exc.HTTPForbidden as e:
|
|
self.assertEqual(e.explanation, expected_msg)
|
|
|
|
def test_create_instance_above_quota_instances(self):
|
|
msg = ('Quota exceeded for instances: Requested 1, but'
|
|
' already used 10 of 10 instances')
|
|
self._do_test_create_instance_above_quota('instances', 0, 10, msg)
|
|
|
|
def test_create_instance_above_quota_ram(self):
|
|
msg = ('Quota exceeded for ram: Requested 4096, but'
|
|
' already used 8192 of 10240 ram')
|
|
self._do_test_create_instance_above_quota('ram', 2048, 10 * 1024, msg)
|
|
|
|
def test_create_instance_above_quota_cores(self):
|
|
msg = ('Quota exceeded for cores: Requested 2, but'
|
|
' already used 9 of 10 cores')
|
|
self._do_test_create_instance_above_quota('cores', 1, 10, msg)
|
|
|
|
@mock.patch.object(fakes.QUOTAS, 'limit_check')
|
|
def test_create_instance_above_quota_server_group_members(
|
|
self, mock_limit_check):
|
|
ctxt = self.req.environ['nova.context']
|
|
fake_group = objects.InstanceGroup(ctxt)
|
|
fake_group.project_id = ctxt.project_id
|
|
fake_group.user_id = ctxt.user_id
|
|
fake_group.create()
|
|
|
|
real_count = fakes.QUOTAS.count_as_dict
|
|
|
|
def fake_count(context, name, group, user_id):
|
|
if name == 'server_group_members':
|
|
self.assertEqual(group.uuid, fake_group.uuid)
|
|
self.assertEqual(user_id,
|
|
self.req.environ['nova.context'].user_id)
|
|
return {'user': {'server_group_members': 10}}
|
|
else:
|
|
return real_count(context, name, group, user_id)
|
|
|
|
def fake_limit_check(context, **kwargs):
|
|
if 'server_group_members' in kwargs:
|
|
raise exception.OverQuota(overs={})
|
|
|
|
def fake_instance_destroy(context, uuid, constraint):
|
|
return fakes.stub_instance(1)
|
|
|
|
mock_limit_check.side_effect = fake_limit_check
|
|
self.stub_out('nova.db.api.instance_destroy', fake_instance_destroy)
|
|
self.body['os:scheduler_hints'] = {'group': fake_group.uuid}
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
expected_msg = "Quota exceeded, too many servers in group"
|
|
|
|
try:
|
|
with mock.patch.object(fakes.QUOTAS, 'count_as_dict',
|
|
side_effect=fake_count):
|
|
self.controller.create(self.req, body=self.body).obj
|
|
self.fail('expected quota to be exceeded')
|
|
except webob.exc.HTTPForbidden as e:
|
|
self.assertEqual(e.explanation, expected_msg)
|
|
|
|
def test_create_instance_with_group_hint(self):
|
|
ctxt = self.req.environ['nova.context']
|
|
test_group = objects.InstanceGroup(ctxt)
|
|
test_group.project_id = ctxt.project_id
|
|
test_group.user_id = ctxt.user_id
|
|
test_group.create()
|
|
|
|
def fake_instance_destroy(context, uuid, constraint):
|
|
return fakes.stub_instance(1)
|
|
|
|
self.stub_out('nova.db.api.instance_destroy', fake_instance_destroy)
|
|
self.body['os:scheduler_hints'] = {'group': test_group.uuid}
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
server = self.controller.create(self.req, body=self.body).obj['server']
|
|
|
|
test_group = objects.InstanceGroup.get_by_uuid(ctxt, test_group.uuid)
|
|
self.assertIn(server['id'], test_group.members)
|
|
|
|
def _test_create_instance_with_group_hint(self, hint,
|
|
hint_name='os:scheduler_hints'):
|
|
def fake_instance_destroy(context, uuid, constraint):
|
|
return fakes.stub_instance(1)
|
|
|
|
def fake_create(*args, **kwargs):
|
|
self.assertEqual(kwargs['scheduler_hints'], hint)
|
|
return ([fakes.stub_instance(1)], '')
|
|
|
|
self.stub_out('nova.compute.api.API.create', fake_create)
|
|
self.stub_out('nova.db.instance_destroy', fake_instance_destroy)
|
|
self.body[hint_name] = hint
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
return self.controller.create(self.req, body=self.body).obj['server']
|
|
|
|
def test_create_instance_with_group_hint_legacy(self):
|
|
self._test_create_instance_with_group_hint(
|
|
{'different_host': '9c47bf55-e9d8-42da-94ab-7f9e80cd1857'},
|
|
hint_name='OS-SCH-HNT:scheduler_hints')
|
|
|
|
def test_create_server_with_different_host_hint(self):
|
|
self._test_create_instance_with_group_hint(
|
|
{'different_host': '9c47bf55-e9d8-42da-94ab-7f9e80cd1857'})
|
|
|
|
self._test_create_instance_with_group_hint(
|
|
{'different_host': ['9c47bf55-e9d8-42da-94ab-7f9e80cd1857',
|
|
'82412fa6-0365-43a9-95e4-d8b20e00c0de']})
|
|
|
|
def test_create_instance_with_group_hint_group_not_found(self):
|
|
def fake_instance_destroy(context, uuid, constraint):
|
|
return fakes.stub_instance(1)
|
|
|
|
self.stub_out('nova.db.api.instance_destroy', fake_instance_destroy)
|
|
self.body['os:scheduler_hints'] = {
|
|
'group': '5b674f73-c8cf-40ef-9965-3b6fe4b304b1'}
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.create, self.req, body=self.body)
|
|
|
|
def test_create_instance_with_group_hint_wrong_uuid_format(self):
|
|
self.body['os:scheduler_hints'] = {
|
|
'group': 'non-uuid'}
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.create, self.req, body=self.body)
|
|
|
|
def test_create_server_bad_hints_non_dict(self):
|
|
sch_hints = ['os:scheduler_hints', 'OS-SCH-HNT:scheduler_hints']
|
|
for hint in sch_hints:
|
|
self.body[hint] = 'non-dict'
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.create, self.req, body=self.body)
|
|
|
|
def test_create_server_bad_hints_long_group(self):
|
|
self.body['os:scheduler_hints'] = {
|
|
'group': 'a' * 256}
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.create, self.req, body=self.body)
|
|
|
|
def test_create_server_with_bad_different_host_hint(self):
|
|
self.body['os:scheduler_hints'] = {
|
|
'different_host': 'non-server-id'}
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.create, self.req, body=self.body)
|
|
|
|
self.body['os:scheduler_hints'] = {
|
|
'different_host': ['non-server-id01', 'non-server-id02']}
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.create, self.req, body=self.body)
|
|
|
|
@mock.patch.object(compute_api.API, 'create',
|
|
side_effect=exception.PortInUse(port_id=uuids.port))
|
|
def test_create_instance_with_neutronv2_port_in_use(self, mock_create):
|
|
requested_networks = [{'uuid': uuids.network, 'port': uuids.port}]
|
|
params = {'networks': requested_networks}
|
|
self.assertRaises(webob.exc.HTTPConflict,
|
|
self._test_create_extra, params)
|
|
|
|
@mock.patch.object(compute_api.API, 'create')
|
|
def test_create_instance_public_network_non_admin(self, mock_create):
|
|
public_network_uuid = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
|
params = {'networks': [{'uuid': public_network_uuid}]}
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
mock_create.side_effect = exception.ExternalNetworkAttachForbidden(
|
|
network_uuid=public_network_uuid)
|
|
self.assertRaises(webob.exc.HTTPForbidden,
|
|
self._test_create_extra, params)
|
|
|
|
def test_multiple_create_with_string_type_min_and_max(self):
|
|
min_count = '2'
|
|
max_count = '3'
|
|
params = {
|
|
'min_count': min_count,
|
|
'max_count': max_count,
|
|
}
|
|
old_create = compute_api.API.create
|
|
|
|
def create(*args, **kwargs):
|
|
self.assertIsInstance(kwargs['min_count'], int)
|
|
self.assertIsInstance(kwargs['max_count'], int)
|
|
self.assertEqual(kwargs['min_count'], 2)
|
|
self.assertEqual(kwargs['max_count'], 3)
|
|
return old_create(*args, **kwargs)
|
|
|
|
self.stub_out('nova.compute.api.API.create', create)
|
|
self._test_create_extra(params)
|
|
|
|
def test_create_instance_with_multiple_create_enabled(self):
|
|
min_count = 2
|
|
max_count = 3
|
|
params = {
|
|
'min_count': min_count,
|
|
'max_count': max_count,
|
|
}
|
|
old_create = compute_api.API.create
|
|
|
|
def create(*args, **kwargs):
|
|
self.assertEqual(kwargs['min_count'], 2)
|
|
self.assertEqual(kwargs['max_count'], 3)
|
|
return old_create(*args, **kwargs)
|
|
|
|
self.stub_out('nova.compute.api.API.create', create)
|
|
self._test_create_extra(params)
|
|
|
|
def test_create_instance_invalid_negative_min(self):
|
|
image_href = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
|
flavor_ref = 'http://localhost/123/flavors/3'
|
|
|
|
body = {
|
|
'server': {
|
|
'min_count': -1,
|
|
'name': 'server_test',
|
|
'imageRef': image_href,
|
|
'flavorRef': flavor_ref,
|
|
}
|
|
}
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.create,
|
|
self.req,
|
|
body=body)
|
|
|
|
def test_create_instance_invalid_negative_max(self):
|
|
image_href = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
|
flavor_ref = 'http://localhost/123/flavors/3'
|
|
|
|
body = {
|
|
'server': {
|
|
'max_count': -1,
|
|
'name': 'server_test',
|
|
'imageRef': image_href,
|
|
'flavorRef': flavor_ref,
|
|
}
|
|
}
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.create,
|
|
self.req,
|
|
body=body)
|
|
|
|
def test_create_instance_with_blank_min(self):
|
|
image_href = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
|
flavor_ref = 'http://localhost/123/flavors/3'
|
|
|
|
body = {
|
|
'server': {
|
|
'min_count': '',
|
|
'name': 'server_test',
|
|
'imageRef': image_href,
|
|
'flavorRef': flavor_ref,
|
|
}
|
|
}
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.create,
|
|
self.req,
|
|
body=body)
|
|
|
|
def test_create_instance_with_blank_max(self):
|
|
image_href = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
|
flavor_ref = 'http://localhost/123/flavors/3'
|
|
|
|
body = {
|
|
'server': {
|
|
'max_count': '',
|
|
'name': 'server_test',
|
|
'imageRef': image_href,
|
|
'flavorRef': flavor_ref,
|
|
}
|
|
}
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.create,
|
|
self.req,
|
|
body=body)
|
|
|
|
def test_create_instance_invalid_min_greater_than_max(self):
|
|
image_href = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
|
flavor_ref = 'http://localhost/123/flavors/3'
|
|
|
|
body = {
|
|
'server': {
|
|
'min_count': 4,
|
|
'max_count': 2,
|
|
'name': 'server_test',
|
|
'imageRef': image_href,
|
|
'flavorRef': flavor_ref,
|
|
}
|
|
}
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.create,
|
|
self.req,
|
|
body=body)
|
|
|
|
def test_create_instance_invalid_alpha_min(self):
|
|
image_href = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
|
flavor_ref = 'http://localhost/123/flavors/3'
|
|
|
|
body = {
|
|
'server': {
|
|
'min_count': 'abcd',
|
|
'name': 'server_test',
|
|
'imageRef': image_href,
|
|
'flavorRef': flavor_ref,
|
|
}
|
|
}
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.create,
|
|
self.req,
|
|
body=body)
|
|
|
|
def test_create_instance_invalid_alpha_max(self):
|
|
image_href = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
|
flavor_ref = 'http://localhost/123/flavors/3'
|
|
|
|
body = {
|
|
'server': {
|
|
'max_count': 'abcd',
|
|
'name': 'server_test',
|
|
'imageRef': image_href,
|
|
'flavorRef': flavor_ref,
|
|
}
|
|
}
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.create,
|
|
self.req,
|
|
body=body)
|
|
|
|
def test_create_multiple_instances(self):
|
|
"""Test creating multiple instances but not asking for
|
|
reservation_id
|
|
"""
|
|
image_href = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
|
flavor_ref = 'http://localhost/123/flavors/3'
|
|
body = {
|
|
'server': {
|
|
'min_count': 2,
|
|
'name': 'server_test',
|
|
'imageRef': image_href,
|
|
'flavorRef': flavor_ref,
|
|
'metadata': {'hello': 'world',
|
|
'open': 'stack'},
|
|
}
|
|
}
|
|
|
|
def create_db_entry_for_new_instance(*args, **kwargs):
|
|
instance = args[4]
|
|
self.instance_cache_by_uuid[instance.uuid] = instance
|
|
return instance
|
|
self.stub_out('nova.compute.api.API.create_db_entry_for_new_instance',
|
|
create_db_entry_for_new_instance)
|
|
res = self.controller.create(self.req, body=body).obj
|
|
|
|
instance_uuids = self.instance_cache_by_uuid.keys()
|
|
self.assertIn(res["server"]["id"], instance_uuids)
|
|
self._check_admin_password_len(res["server"])
|
|
|
|
def test_create_multiple_instances_pass_disabled(self):
|
|
"""Test creating multiple instances but not asking for
|
|
reservation_id
|
|
"""
|
|
self.flags(enable_instance_password=False, group='api')
|
|
image_href = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
|
flavor_ref = 'http://localhost/123/flavors/3'
|
|
body = {
|
|
'server': {
|
|
'min_count': 2,
|
|
'name': 'server_test',
|
|
'imageRef': image_href,
|
|
'flavorRef': flavor_ref,
|
|
'metadata': {'hello': 'world',
|
|
'open': 'stack'},
|
|
}
|
|
}
|
|
|
|
def create_db_entry_for_new_instance(*args, **kwargs):
|
|
instance = args[4]
|
|
self.instance_cache_by_uuid[instance.uuid] = instance
|
|
return instance
|
|
self.stub_out('nova.compute.api.API.create_db_entry_for_new_instance',
|
|
create_db_entry_for_new_instance)
|
|
res = self.controller.create(self.req, body=body).obj
|
|
|
|
instance_uuids = self.instance_cache_by_uuid.keys()
|
|
self.assertIn(res["server"]["id"], instance_uuids)
|
|
self._check_admin_password_missing(res["server"])
|
|
|
|
def _create_multiple_instances_resv_id_return(self, resv_id_return):
|
|
"""Test creating multiple instances with asking for
|
|
reservation_id
|
|
"""
|
|
def create_db_entry_for_new_instance(*args, **kwargs):
|
|
instance = args[4]
|
|
self.instance_cache_by_uuid[instance.uuid] = instance
|
|
return instance
|
|
|
|
self.stub_out('nova.compute.api.API.create_db_entry_for_new_instance',
|
|
create_db_entry_for_new_instance)
|
|
image_href = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
|
flavor_ref = 'http://localhost/123/flavors/3'
|
|
body = {
|
|
'server': {
|
|
'min_count': 2,
|
|
'name': 'server_test',
|
|
'imageRef': image_href,
|
|
'flavorRef': flavor_ref,
|
|
'metadata': {'hello': 'world',
|
|
'open': 'stack'},
|
|
'return_reservation_id': resv_id_return
|
|
}
|
|
}
|
|
|
|
res = self.controller.create(self.req, body=body)
|
|
reservation_id = res.obj['reservation_id']
|
|
self.assertNotEqual(reservation_id, "")
|
|
self.assertIsNotNone(reservation_id)
|
|
self.assertGreater(len(reservation_id), 1)
|
|
|
|
def test_create_multiple_instances_with_resv_id_return(self):
|
|
self._create_multiple_instances_resv_id_return(True)
|
|
|
|
def test_create_multiple_instances_with_string_resv_id_return(self):
|
|
self._create_multiple_instances_resv_id_return("True")
|
|
|
|
def test_create_multiple_instances_with_multiple_volume_bdm(self):
|
|
"""Test that a BadRequest is raised if multiple instances
|
|
are requested with a list of block device mappings for volumes.
|
|
"""
|
|
min_count = 2
|
|
bdm = [{'source_type': 'volume', 'uuid': 'vol-xxxx'},
|
|
{'source_type': 'volume', 'uuid': 'vol-yyyy'}
|
|
]
|
|
params = {
|
|
'block_device_mapping_v2': bdm,
|
|
'min_count': min_count
|
|
}
|
|
old_create = compute_api.API.create
|
|
|
|
def create(*args, **kwargs):
|
|
self.assertEqual(kwargs['min_count'], 2)
|
|
self.assertEqual(len(kwargs['block_device_mapping']), 2)
|
|
return old_create(*args, **kwargs)
|
|
|
|
self.stub_out('nova.compute.api.API.create', create)
|
|
exc = self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self._test_create_extra, params, no_image=True)
|
|
self.assertEqual("Cannot attach one or more volumes to multiple "
|
|
"instances", exc.explanation)
|
|
|
|
def test_create_multiple_instances_with_single_volume_bdm(self):
|
|
"""Test that a BadRequest is raised if multiple instances
|
|
are requested to boot from a single volume.
|
|
"""
|
|
min_count = 2
|
|
bdm = [{'source_type': 'volume', 'uuid': 'vol-xxxx'}]
|
|
params = {
|
|
'block_device_mapping_v2': bdm,
|
|
'min_count': min_count
|
|
}
|
|
old_create = compute_api.API.create
|
|
|
|
def create(*args, **kwargs):
|
|
self.assertEqual(kwargs['min_count'], 2)
|
|
self.assertEqual(kwargs['block_device_mapping'][0]['volume_id'],
|
|
'vol-xxxx')
|
|
return old_create(*args, **kwargs)
|
|
|
|
self.stub_out('nova.compute.api.API.create', create)
|
|
exc = self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self._test_create_extra, params, no_image=True)
|
|
self.assertEqual("Cannot attach one or more volumes to multiple "
|
|
"instances", exc.explanation)
|
|
|
|
def test_create_multiple_instance_with_non_integer_max_count(self):
|
|
image_href = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
|
flavor_ref = 'http://localhost/123/flavors/3'
|
|
body = {
|
|
'server': {
|
|
'max_count': 2.5,
|
|
'name': 'server_test',
|
|
'imageRef': image_href,
|
|
'flavorRef': flavor_ref,
|
|
'metadata': {'hello': 'world',
|
|
'open': 'stack'},
|
|
}
|
|
}
|
|
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.create, self.req, body=body)
|
|
|
|
def test_create_multiple_instance_with_non_integer_min_count(self):
|
|
image_href = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
|
flavor_ref = 'http://localhost/123/flavors/3'
|
|
body = {
|
|
'server': {
|
|
'min_count': 2.5,
|
|
'name': 'server_test',
|
|
'imageRef': image_href,
|
|
'flavorRef': flavor_ref,
|
|
'metadata': {'hello': 'world',
|
|
'open': 'stack'},
|
|
}
|
|
}
|
|
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.create, self.req, body=body)
|
|
|
|
def test_create_multiple_instance_max_count_overquota_min_count_ok(self):
|
|
self.flags(instances=3, group='quota')
|
|
image_href = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
|
flavor_ref = 'http://localhost/123/flavors/3'
|
|
body = {
|
|
'server': {
|
|
'min_count': 2,
|
|
'max_count': 5,
|
|
'name': 'server_test',
|
|
'imageRef': image_href,
|
|
'flavorRef': flavor_ref,
|
|
}
|
|
}
|
|
|
|
def create_db_entry_for_new_instance(*args, **kwargs):
|
|
instance = args[4]
|
|
self.instance_cache_by_uuid[instance.uuid] = instance
|
|
return instance
|
|
self.stub_out('nova.compute.api.API.create_db_entry_for_new_instance',
|
|
create_db_entry_for_new_instance)
|
|
res = self.controller.create(self.req, body=body).obj
|
|
instance_uuids = self.instance_cache_by_uuid.keys()
|
|
self.assertIn(res["server"]["id"], instance_uuids)
|
|
|
|
def test_create_multiple_instance_max_count_overquota_min_count_over(self):
|
|
self.flags(instances=3, group='quota')
|
|
image_href = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
|
flavor_ref = 'http://localhost/123/flavors/3'
|
|
body = {
|
|
'server': {
|
|
'min_count': 4,
|
|
'max_count': 5,
|
|
'name': 'server_test',
|
|
'imageRef': image_href,
|
|
'flavorRef': flavor_ref,
|
|
}
|
|
}
|
|
self.assertRaises(webob.exc.HTTPForbidden, self.controller.create,
|
|
self.req, body=body)
|
|
|
|
@mock.patch.object(compute_api.API, 'create')
|
|
def test_create_multiple_instance_with_specified_ip_neutronv2(self,
|
|
_api_mock):
|
|
_api_mock.side_effect = exception.InvalidFixedIpAndMaxCountRequest(
|
|
reason="")
|
|
network = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
|
port = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee'
|
|
address = '10.0.0.1'
|
|
requested_networks = [{'uuid': network, 'fixed_ip': address,
|
|
'port': port}]
|
|
params = {'networks': requested_networks}
|
|
self.body['server']['max_count'] = 2
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self._test_create_extra, params)
|
|
|
|
@mock.patch.object(compute_api.API, 'create',
|
|
side_effect=exception.MultiplePortsNotApplicable(
|
|
reason="Unable to launch multiple instances with "
|
|
"a single configured port ID. Please "
|
|
"launch your instance one by one with "
|
|
"different ports."))
|
|
def test_create_multiple_instance_with_neutronv2_port(self, mock_create):
|
|
requested_networks = [{'uuid': uuids.network, 'port': uuids.port}]
|
|
params = {'networks': requested_networks}
|
|
self.body['server']['max_count'] = 2
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self._test_create_extra, params)
|
|
|
|
@mock.patch.object(compute_api.API, 'create',
|
|
side_effect=exception.NetworkNotFound(
|
|
network_id=uuids.network))
|
|
def test_create_instance_with_neutronv2_not_found_network(
|
|
self, mock_create):
|
|
requested_networks = [{'uuid': uuids.network}]
|
|
params = {'networks': requested_networks}
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self._test_create_extra, params)
|
|
|
|
@mock.patch.object(compute_api.API, 'create',
|
|
side_effect=exception.PortNotFound(port_id=uuids.port))
|
|
def test_create_instance_with_neutronv2_port_not_found(self, mock_create):
|
|
requested_networks = [{'uuid': uuids.network, 'port': uuids.port}]
|
|
params = {'networks': requested_networks}
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self._test_create_extra, params)
|
|
|
|
@mock.patch.object(compute_api.API, 'create')
|
|
def test_create_instance_with_network_ambiguous(self, mock_create):
|
|
mock_create.side_effect = exception.NetworkAmbiguous()
|
|
self.assertRaises(webob.exc.HTTPConflict,
|
|
self._test_create_extra, {})
|
|
|
|
@mock.patch.object(compute_api.API, 'create',
|
|
side_effect=exception.UnableToAutoAllocateNetwork(
|
|
project_id=FAKE_UUID))
|
|
def test_create_instance_with_unable_to_auto_allocate_network(self,
|
|
mock_create):
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self._test_create_extra, {})
|
|
|
|
@mock.patch.object(compute_api.API, 'create',
|
|
side_effect=exception.ImageNotAuthorized(
|
|
image_id=FAKE_UUID))
|
|
def test_create_instance_with_image_not_authorized(self,
|
|
mock_create):
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self._test_create_extra, {})
|
|
|
|
@mock.patch.object(compute_api.API, 'create',
|
|
side_effect=exception.InstanceExists(
|
|
name='instance-name'))
|
|
def test_create_instance_raise_instance_exists(self, mock_create):
|
|
self.assertRaises(webob.exc.HTTPConflict,
|
|
self.controller.create,
|
|
self.req, body=self.body)
|
|
|
|
@mock.patch.object(compute_api.API, 'create',
|
|
side_effect=exception.InvalidBDMEphemeralSize)
|
|
def test_create_instance_raise_invalid_bdm_ephsize(self, mock_create):
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.create,
|
|
self.req, body=self.body)
|
|
|
|
@mock.patch.object(compute_api.API, 'create',
|
|
side_effect=exception.InvalidNUMANodesNumber(
|
|
nodes='-1'))
|
|
def test_create_instance_raise_invalid_numa_nodes(self, mock_create):
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.create,
|
|
self.req, body=self.body)
|
|
|
|
@mock.patch.object(compute_api.API, 'create',
|
|
side_effect=exception.InvalidBDMFormat(details=''))
|
|
def test_create_instance_raise_invalid_bdm_format(self, mock_create):
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.create,
|
|
self.req, body=self.body)
|
|
|
|
@mock.patch.object(compute_api.API, 'create',
|
|
side_effect=exception.InvalidBDMSwapSize)
|
|
def test_create_instance_raise_invalid_bdm_swapsize(self, mock_create):
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.create,
|
|
self.req, body=self.body)
|
|
|
|
@mock.patch.object(compute_api.API, 'create',
|
|
side_effect=exception.InvalidBDM)
|
|
def test_create_instance_raise_invalid_bdm(self, mock_create):
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.create,
|
|
self.req, body=self.body)
|
|
|
|
@mock.patch.object(compute_api.API, 'create',
|
|
side_effect=exception.ImageBadRequest(
|
|
image_id='dummy', response='dummy'))
|
|
def test_create_instance_raise_image_bad_request(self, mock_create):
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.create,
|
|
self.req, body=self.body)
|
|
|
|
def test_create_instance_invalid_availability_zone(self):
|
|
self.body['server']['availability_zone'] = 'invalid::::zone'
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.create,
|
|
self.req, body=self.body)
|
|
|
|
@mock.patch.object(compute_api.API, 'create',
|
|
side_effect=exception.FixedIpNotFoundForAddress(
|
|
address='dummy'))
|
|
def test_create_instance_raise_fixed_ip_not_found_bad_request(self,
|
|
mock_create):
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.create,
|
|
self.req, body=self.body)
|
|
|
|
@mock.patch('nova.virt.hardware.numa_get_constraints',
|
|
side_effect=exception.CPUThreadPolicyConfigurationInvalid())
|
|
def test_create_instance_raise_cpu_thread_policy_configuration_invalid(
|
|
self, mock_numa):
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.create,
|
|
self.req, body=self.body)
|
|
|
|
@mock.patch('nova.virt.hardware.numa_get_constraints',
|
|
side_effect=exception.ImageCPUPinningForbidden())
|
|
def test_create_instance_raise_image_cpu_pinning_forbidden(
|
|
self, mock_numa):
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.create,
|
|
self.req, body=self.body)
|
|
|
|
@mock.patch('nova.virt.hardware.numa_get_constraints',
|
|
side_effect=exception.ImageCPUThreadPolicyForbidden())
|
|
def test_create_instance_raise_image_cpu_thread_policy_forbidden(
|
|
self, mock_numa):
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.create,
|
|
self.req, body=self.body)
|
|
|
|
@mock.patch('nova.virt.hardware.numa_get_constraints',
|
|
side_effect=exception.MemoryPageSizeInvalid(pagesize='-1'))
|
|
def test_create_instance_raise_memory_page_size_invalid(self, mock_numa):
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.create,
|
|
self.req, body=self.body)
|
|
|
|
@mock.patch('nova.virt.hardware.numa_get_constraints',
|
|
side_effect=exception.MemoryPageSizeForbidden(pagesize='1',
|
|
against='2'))
|
|
def test_create_instance_raise_memory_page_size_forbidden(self, mock_numa):
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.create,
|
|
self.req, body=self.body)
|
|
|
|
@mock.patch('nova.virt.hardware.numa_get_constraints',
|
|
side_effect=exception.RealtimeConfigurationInvalid())
|
|
def test_create_instance_raise_realtime_configuration_invalid(
|
|
self, mock_numa):
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.create,
|
|
self.req, body=self.body)
|
|
|
|
@mock.patch('nova.virt.hardware.numa_get_constraints',
|
|
side_effect=exception.RealtimeMaskNotFoundOrInvalid())
|
|
def test_create_instance_raise_realtime_mask_not_found_or_invalid(
|
|
self, mock_numa):
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.create,
|
|
self.req, body=self.body)
|
|
|
|
@mock.patch.object(compute_api.API, 'create')
|
|
def test_create_instance_invalid_personality(self, mock_create):
|
|
# Personality files have been deprecated as of v2.57
|
|
self.req.api_version_request = \
|
|
api_version_request.APIVersionRequest('2.56')
|
|
|
|
codec = 'utf8'
|
|
content = encodeutils.safe_encode(
|
|
'b25zLiINCg0KLVJpY2hhcmQgQ$$%QQmFjaA==')
|
|
start_position = 19
|
|
end_position = 20
|
|
msg = 'invalid start byte'
|
|
mock_create.side_effect = UnicodeDecodeError(codec, content,
|
|
start_position,
|
|
end_position, msg)
|
|
|
|
self.body['server']['personality'] = [
|
|
{
|
|
"path": "/etc/banner.txt",
|
|
"contents": "b25zLiINCg0KLVJpY2hhcmQgQ$$%QQmFjaA==",
|
|
},
|
|
]
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.create, self.req, body=self.body)
|
|
|
|
def test_create_instance_without_personality_should_get_empty_list(self):
|
|
# Personality files have been deprecated as of v2.57
|
|
self.req.api_version_request = \
|
|
api_version_request.APIVersionRequest('2.56')
|
|
|
|
old_create = compute_api.API.create
|
|
|
|
def create(*args, **kwargs):
|
|
self.assertEqual([], kwargs['injected_files'])
|
|
return old_create(*args, **kwargs)
|
|
|
|
self.stub_out('nova.compute.api.API.create', create)
|
|
|
|
self._test_create_instance()
|
|
|
|
def test_create_instance_with_extra_personality_arg(self):
|
|
# Personality files have been deprecated as of v2.57
|
|
self.req.api_version_request = \
|
|
api_version_request.APIVersionRequest('2.56')
|
|
|
|
self.body['server']['personality'] = [
|
|
{
|
|
"path": "/etc/banner.txt",
|
|
"contents": "b25zLiINCg0KLVJpY2hhcmQgQ$$%QQmFjaA==",
|
|
"extra_arg": "extra value"
|
|
},
|
|
]
|
|
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.create,
|
|
self.req, body=self.body)
|
|
|
|
@mock.patch.object(compute_api.API, 'create',
|
|
side_effect=exception.PciRequestAliasNotDefined(
|
|
alias='fake_name'))
|
|
def test_create_instance_pci_alias_not_defined(self, mock_create):
|
|
# Tests that PciRequestAliasNotDefined is translated to a 400 error.
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self._test_create_extra, {})
|
|
|
|
def test_create_instance_with_user_data(self):
|
|
value = base64.encode_as_text("A random string")
|
|
params = {'user_data': value}
|
|
self._test_create_extra(params)
|
|
|
|
def test_create_instance_with_bad_user_data(self):
|
|
value = "A random string"
|
|
params = {'user_data': value}
|
|
self.assertRaises(exception.ValidationError,
|
|
self._test_create_extra, params)
|
|
|
|
@mock.patch('nova.compute.api.API.create')
|
|
def test_create_instance_with_none_allowd_for_v20_compat_mode(self,
|
|
mock_create):
|
|
|
|
def create(context, *args, **kwargs):
|
|
self.assertIsNone(kwargs['user_data'])
|
|
return ([fakes.stub_instance_obj(context)], None)
|
|
|
|
mock_create.side_effect = create
|
|
self.req.set_legacy_v2()
|
|
params = {'user_data': None}
|
|
self._test_create_extra(params)
|
|
|
|
|
|
class ServersControllerCreateTestV219(ServersControllerCreateTest):
|
|
def _create_instance_req(self, set_desc, desc=None):
|
|
if set_desc:
|
|
self.body['server']['description'] = desc
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.req.api_version_request = \
|
|
api_version_request.APIVersionRequest('2.19')
|
|
|
|
def test_create_instance_with_description(self):
|
|
self._create_instance_req(True, 'server_desc')
|
|
# The fact that the action doesn't raise is enough validation
|
|
self.controller.create(self.req, body=self.body).obj
|
|
|
|
def test_create_instance_with_none_description(self):
|
|
self._create_instance_req(True)
|
|
# The fact that the action doesn't raise is enough validation
|
|
self.controller.create(self.req, body=self.body).obj
|
|
|
|
def test_create_instance_with_empty_description(self):
|
|
self._create_instance_req(True, '')
|
|
# The fact that the action doesn't raise is enough validation
|
|
self.controller.create(self.req, body=self.body).obj
|
|
|
|
def test_create_instance_without_description(self):
|
|
self._create_instance_req(False)
|
|
# The fact that the action doesn't raise is enough validation
|
|
self.controller.create(self.req, body=self.body).obj
|
|
|
|
def test_create_instance_description_too_long(self):
|
|
self._create_instance_req(True, 'X' * 256)
|
|
self.assertRaises(exception.ValidationError, self.controller.create,
|
|
self.req, body=self.body)
|
|
|
|
def test_create_instance_description_invalid(self):
|
|
self._create_instance_req(True, "abc\0ddef")
|
|
self.assertRaises(exception.ValidationError, self.controller.create,
|
|
self.req, body=self.body)
|
|
|
|
|
|
class ServersControllerCreateTestV232(test.NoDBTestCase):
|
|
def setUp(self):
|
|
super(ServersControllerCreateTestV232, self).setUp()
|
|
self.flags(use_neutron=True)
|
|
|
|
self.controller = servers.ServersController()
|
|
|
|
self.body = {
|
|
'server': {
|
|
'name': 'device-tagging-server',
|
|
'imageRef': '6b0edabb-8cde-4684-a3f4-978960a51378',
|
|
'flavorRef': '2',
|
|
'networks': [{
|
|
'uuid': 'ff608d40-75e9-48cb-b745-77bb55b5eaf2'
|
|
}],
|
|
'block_device_mapping_v2': [{
|
|
'uuid': '70a599e0-31e7-49b7-b260-868f441e862b',
|
|
'source_type': 'image',
|
|
'destination_type': 'volume',
|
|
'boot_index': 0,
|
|
'volume_size': '1'
|
|
}]
|
|
}
|
|
}
|
|
|
|
self.req = fakes.HTTPRequestV21.blank('/fake/servers', version='2.32')
|
|
self.req.method = 'POST'
|
|
self.req.headers['content-type'] = 'application/json'
|
|
|
|
def _create_server(self):
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.controller.create(self.req, body=self.body)
|
|
|
|
def test_create_server_no_tags_old_compute(self):
|
|
with test.nested(
|
|
mock.patch('nova.objects.service.get_minimum_version_all_cells',
|
|
return_value=13),
|
|
mock.patch.object(nova.compute.flavors, 'get_flavor_by_flavor_id',
|
|
return_value=objects.Flavor()),
|
|
mock.patch.object(
|
|
compute_api.API, 'create',
|
|
return_value=(
|
|
[{'uuid': 'f60012d9-5ba4-4547-ab48-f94ff7e62d4e'}],
|
|
1)),
|
|
):
|
|
self._create_server()
|
|
|
|
@mock.patch('nova.objects.service.get_minimum_version_all_cells',
|
|
return_value=13)
|
|
def test_create_server_tagged_nic_old_compute_fails(self, get_min_ver):
|
|
self.body['server']['networks'][0]['tag'] = 'foo'
|
|
self.assertRaises(webob.exc.HTTPBadRequest, self._create_server)
|
|
|
|
@mock.patch('nova.objects.service.get_minimum_version_all_cells',
|
|
return_value=13)
|
|
def test_create_server_tagged_bdm_old_compute_fails(self, get_min_ver):
|
|
self.body['server']['block_device_mapping_v2'][0]['tag'] = 'foo'
|
|
self.assertRaises(webob.exc.HTTPBadRequest, self._create_server)
|
|
|
|
def test_create_server_tagged_nic_new_compute(self):
|
|
with test.nested(
|
|
mock.patch('nova.objects.service.get_minimum_version_all_cells',
|
|
return_value=14),
|
|
mock.patch.object(nova.compute.flavors, 'get_flavor_by_flavor_id',
|
|
return_value=objects.Flavor()),
|
|
mock.patch.object(
|
|
compute_api.API, 'create',
|
|
return_value=(
|
|
[{'uuid': 'f60012d9-5ba4-4547-ab48-f94ff7e62d4e'}],
|
|
1)),
|
|
):
|
|
self.body['server']['networks'][0]['tag'] = 'foo'
|
|
self._create_server()
|
|
|
|
def test_create_server_tagged_bdm_new_compute(self):
|
|
with test.nested(
|
|
mock.patch('nova.objects.service.get_minimum_version_all_cells',
|
|
return_value=14),
|
|
mock.patch.object(nova.compute.flavors, 'get_flavor_by_flavor_id',
|
|
return_value=objects.Flavor()),
|
|
mock.patch.object(
|
|
compute_api.API, 'create',
|
|
return_value=(
|
|
[{'uuid': 'f60012d9-5ba4-4547-ab48-f94ff7e62d4e'}],
|
|
1)),
|
|
):
|
|
self.body['server']['block_device_mapping_v2'][0]['tag'] = 'foo'
|
|
self._create_server()
|
|
|
|
|
|
class ServersControllerCreateTestV237(test.NoDBTestCase):
|
|
"""Tests server create scenarios with the v2.37 microversion.
|
|
|
|
These tests are mostly about testing the validation on the 2.37
|
|
server create request with emphasis on negative scenarios.
|
|
"""
|
|
def setUp(self):
|
|
super(ServersControllerCreateTestV237, self).setUp()
|
|
# Set the use_neutron flag to process requested networks.
|
|
self.flags(use_neutron=True)
|
|
# Create the server controller.
|
|
self.controller = servers.ServersController()
|
|
# Define a basic server create request body which tests can customize.
|
|
self.body = {
|
|
'server': {
|
|
'name': 'auto-allocate-test',
|
|
'imageRef': '6b0edabb-8cde-4684-a3f4-978960a51378',
|
|
'flavorRef': '2',
|
|
},
|
|
}
|
|
# Create a fake request using the 2.37 microversion.
|
|
self.req = fakes.HTTPRequestV21.blank('/fake/servers', version='2.37')
|
|
self.req.method = 'POST'
|
|
self.req.headers['content-type'] = 'application/json'
|
|
|
|
def _create_server(self, networks):
|
|
self.body['server']['networks'] = networks
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
return self.controller.create(self.req, body=self.body).obj['server']
|
|
|
|
def test_create_server_auth_pre_2_37_fails(self):
|
|
"""Negative test to make sure you can't pass 'auto' before 2.37"""
|
|
self.req.api_version_request = \
|
|
api_version_request.APIVersionRequest('2.36')
|
|
self.assertRaises(exception.ValidationError, self._create_server,
|
|
'auto')
|
|
|
|
def test_create_server_no_requested_networks_fails(self):
|
|
"""Negative test for a server create request with no networks requested
|
|
which should fail with the v2.37 schema validation.
|
|
"""
|
|
self.assertRaises(exception.ValidationError, self._create_server, None)
|
|
|
|
def test_create_server_network_id_not_uuid_fails(self):
|
|
"""Negative test for a server create request where the requested
|
|
network id is not one of the auto/none enums.
|
|
"""
|
|
self.assertRaises(exception.ValidationError, self._create_server,
|
|
'not-auto-or-none')
|
|
|
|
def test_create_server_network_id_empty_string_fails(self):
|
|
"""Negative test for a server create request where the requested
|
|
network id is the empty string.
|
|
"""
|
|
self.assertRaises(exception.ValidationError, self._create_server, '')
|
|
|
|
@mock.patch.object(context.RequestContext, 'can')
|
|
def test_create_server_networks_none_skip_policy(self, context_can):
|
|
"""Test to ensure skip checking policy rule create:attach_network,
|
|
when networks is 'none' which means no network will be allocated.
|
|
"""
|
|
with test.nested(
|
|
mock.patch('nova.objects.service.get_minimum_version_all_cells',
|
|
return_value=14),
|
|
mock.patch.object(nova.compute.flavors, 'get_flavor_by_flavor_id',
|
|
return_value=objects.Flavor()),
|
|
mock.patch.object(
|
|
compute_api.API, 'create',
|
|
return_value=(
|
|
[{'uuid': 'f9bccadf-5ab1-4a56-9156-c00c178fe5f5'}],
|
|
1)),
|
|
):
|
|
network_policy = server_policies.SERVERS % 'create:attach_network'
|
|
self._create_server('none')
|
|
call_list = [c for c in context_can.call_args_list
|
|
if c[0][0] == network_policy]
|
|
self.assertEqual(0, len(call_list))
|
|
|
|
@mock.patch.object(objects.Flavor, 'get_by_flavor_id',
|
|
side_effect=exception.FlavorNotFound(flavor_id='2'))
|
|
def test_create_server_auto_flavornotfound(self, get_flavor):
|
|
"""Tests that requesting auto networking is OK. This test
|
|
short-circuits on a FlavorNotFound error.
|
|
"""
|
|
self.useFixture(nova_fixtures.AllServicesCurrent())
|
|
ex = self.assertRaises(
|
|
webob.exc.HTTPBadRequest, self._create_server, 'auto')
|
|
# make sure it was a flavor not found error and not something else
|
|
self.assertIn('Flavor 2 could not be found', six.text_type(ex))
|
|
|
|
@mock.patch.object(objects.Flavor, 'get_by_flavor_id',
|
|
side_effect=exception.FlavorNotFound(flavor_id='2'))
|
|
def test_create_server_none_flavornotfound(self, get_flavor):
|
|
"""Tests that requesting none for networking is OK. This test
|
|
short-circuits on a FlavorNotFound error.
|
|
"""
|
|
self.useFixture(nova_fixtures.AllServicesCurrent())
|
|
ex = self.assertRaises(
|
|
webob.exc.HTTPBadRequest, self._create_server, 'none')
|
|
# make sure it was a flavor not found error and not something else
|
|
self.assertIn('Flavor 2 could not be found', six.text_type(ex))
|
|
|
|
@mock.patch.object(objects.Flavor, 'get_by_flavor_id',
|
|
side_effect=exception.FlavorNotFound(flavor_id='2'))
|
|
def test_create_server_multiple_specific_nics_flavornotfound(self,
|
|
get_flavor):
|
|
"""Tests that requesting multiple specific network IDs is OK. This test
|
|
short-circuits on a FlavorNotFound error.
|
|
"""
|
|
self.useFixture(nova_fixtures.AllServicesCurrent())
|
|
ex = self.assertRaises(
|
|
webob.exc.HTTPBadRequest, self._create_server,
|
|
[{'uuid': 'e3b686a8-b91d-4a61-a3fc-1b74bb619ddb'},
|
|
{'uuid': 'e0f00941-f85f-46ec-9315-96ded58c2f14'}])
|
|
# make sure it was a flavor not found error and not something else
|
|
self.assertIn('Flavor 2 could not be found', six.text_type(ex))
|
|
|
|
def test_create_server_legacy_neutron_network_id_fails(self):
|
|
"""Tests that we no longer support the legacy br-<uuid> format for
|
|
a network id.
|
|
"""
|
|
uuid = 'br-00000000-0000-0000-0000-000000000000'
|
|
self.assertRaises(exception.ValidationError, self._create_server,
|
|
[{'uuid': uuid}])
|
|
|
|
|
|
@ddt.ddt
|
|
class ServersControllerCreateTestV252(test.NoDBTestCase):
|
|
def setUp(self):
|
|
super(ServersControllerCreateTestV252, self).setUp()
|
|
self.controller = servers.ServersController()
|
|
|
|
self.body = {
|
|
'server': {
|
|
'name': 'device-tagging-server',
|
|
'imageRef': '6b0edabb-8cde-4684-a3f4-978960a51378',
|
|
'flavorRef': '2',
|
|
'networks': [{
|
|
'uuid': 'ff608d40-75e9-48cb-b745-77bb55b5eaf2'
|
|
}]
|
|
}
|
|
}
|
|
|
|
self.req = fakes.HTTPRequestV21.blank('/fake/servers', version='2.52')
|
|
self.req.method = 'POST'
|
|
self.req.headers['content-type'] = 'application/json'
|
|
|
|
def _create_server(self, tags):
|
|
self.body['server']['tags'] = tags
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
return self.controller.create(self.req, body=self.body).obj['server']
|
|
|
|
def test_create_server_with_tags_pre_2_52_fails(self):
|
|
"""Negative test to make sure you can't pass 'tags' before 2.52"""
|
|
self.req.api_version_request = \
|
|
api_version_request.APIVersionRequest('2.51')
|
|
self.assertRaises(
|
|
exception.ValidationError, self._create_server, ['tag1'])
|
|
|
|
@ddt.data([','],
|
|
['/'],
|
|
['a' * (tag.MAX_TAG_LENGTH + 1)],
|
|
['a'] * (instance_obj.MAX_TAG_COUNT + 1),
|
|
[''],
|
|
[1, 2, 3],
|
|
{'tag': 'tag'})
|
|
def test_create_server_with_tags_incorrect_tags(self, tags):
|
|
"""Negative test to incorrect tags are not allowed"""
|
|
self.req.api_version_request = \
|
|
api_version_request.APIVersionRequest('2.52')
|
|
self.assertRaises(
|
|
exception.ValidationError, self._create_server, tags)
|
|
|
|
|
|
class ServersControllerCreateTestV257(test.NoDBTestCase):
|
|
"""Tests that trying to create a server with personality files using
|
|
microversion 2.57 fails.
|
|
"""
|
|
def test_create_server_with_personality_fails(self):
|
|
controller = servers.ServersController()
|
|
body = {
|
|
'server': {
|
|
'name': 'no-personality-files',
|
|
'imageRef': '6b0edabb-8cde-4684-a3f4-978960a51378',
|
|
'flavorRef': '2',
|
|
'networks': 'auto',
|
|
'personality': [{
|
|
'path': '/path/to/file',
|
|
'contents': 'ZWNobyAiaGVsbG8gd29ybGQi'
|
|
}]
|
|
}
|
|
}
|
|
req = fakes.HTTPRequestV21.blank('/servers', version='2.57')
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
req.method = 'POST'
|
|
req.headers['content-type'] = 'application/json'
|
|
ex = self.assertRaises(
|
|
exception.ValidationError, controller.create, req, body=body)
|
|
self.assertIn('personality', six.text_type(ex))
|
|
|
|
|
|
@mock.patch('nova.compute.utils.check_num_instances_quota',
|
|
new=lambda *args, **kwargs: 1)
|
|
class ServersControllerCreateTestV260(test.NoDBTestCase):
|
|
"""Negative tests for creating a server with a multiattach volume."""
|
|
def setUp(self):
|
|
super(ServersControllerCreateTestV260, self).setUp()
|
|
self.useFixture(nova_fixtures.NoopQuotaDriverFixture())
|
|
self.controller = servers.ServersController()
|
|
get_flavor_mock = mock.patch(
|
|
'nova.compute.flavors.get_flavor_by_flavor_id',
|
|
return_value=fake_flavor.fake_flavor_obj(
|
|
context.get_admin_context(), flavorid='1'))
|
|
get_flavor_mock.start()
|
|
self.addCleanup(get_flavor_mock.stop)
|
|
reqspec_create_mock = mock.patch(
|
|
'nova.objects.RequestSpec.create')
|
|
reqspec_create_mock.start()
|
|
self.addCleanup(reqspec_create_mock.stop)
|
|
volume_get_mock = mock.patch(
|
|
'nova.volume.cinder.API.get',
|
|
return_value={'id': uuids.fake_volume_id, 'multiattach': True})
|
|
volume_get_mock.start()
|
|
self.addCleanup(volume_get_mock.stop)
|
|
|
|
def _post_server(self, version=None):
|
|
body = {
|
|
'server': {
|
|
'name': 'multiattach',
|
|
'flavorRef': '1',
|
|
'networks': 'none',
|
|
'block_device_mapping_v2': [{
|
|
'uuid': uuids.fake_volume_id,
|
|
'source_type': 'volume',
|
|
'destination_type': 'volume',
|
|
'boot_index': 0,
|
|
'delete_on_termination': True}]
|
|
}
|
|
}
|
|
req = fakes.HTTPRequestV21.blank(
|
|
'/servers', version=version or '2.60')
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
req.method = 'POST'
|
|
req.headers['content-type'] = 'application/json'
|
|
return self.controller.create(req, body=body)
|
|
|
|
def test_create_server_with_multiattach_fails_old_microversion(self):
|
|
"""Tests the case that the user tries to boot from volume with a
|
|
multiattach volume but before using microversion 2.60.
|
|
"""
|
|
self.useFixture(nova_fixtures.AllServicesCurrent())
|
|
ex = self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self._post_server, '2.59')
|
|
self.assertIn('Multiattach volumes are only supported starting with '
|
|
'compute API version 2.60', six.text_type(ex))
|
|
|
|
@mock.patch('nova.objects.service.get_minimum_version_all_cells',
|
|
return_value=compute_api.MIN_COMPUTE_MULTIATTACH - 1)
|
|
def test_create_server_with_multiattach_fails_not_available(
|
|
self, mock_get_min_version_all_cells):
|
|
"""Tests the case that the user tries to boot from volume with a
|
|
multiattach volume but before the deployment is fully upgraded.
|
|
"""
|
|
ex = self.assertRaises(webob.exc.HTTPConflict, self._post_server)
|
|
self.assertIn('Multiattach volume support is not yet available',
|
|
six.text_type(ex))
|
|
|
|
|
|
class ServersControllerCreateTestV263(ServersControllerCreateTest):
|
|
def _create_instance_req(self, certs=None):
|
|
self.body['server']['trusted_image_certificates'] = certs
|
|
|
|
self.flags(verify_glance_signatures=True, group='glance')
|
|
self.flags(enable_certificate_validation=True, group='glance')
|
|
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.req.api_version_request = \
|
|
api_version_request.APIVersionRequest('2.63')
|
|
|
|
@mock.patch('nova.objects.service.get_minimum_version_all_cells',
|
|
return_value=compute_api.MIN_COMPUTE_TRUSTED_CERTS)
|
|
def test_create_instance_with_trusted_certs(self, get_min_ver):
|
|
"""Test create with valid trusted_image_certificates argument"""
|
|
self._create_instance_req(
|
|
['0b5d2c72-12cc-4ba6-a8d7-3ff5cc1d8cb8',
|
|
'674736e3-f25c-405c-8362-bbf991e0ce0a'])
|
|
# The fact that the action doesn't raise is enough validation
|
|
self.controller.create(self.req, body=self.body).obj
|
|
|
|
def test_create_instance_without_trusted_certs(self):
|
|
"""Test create without trusted image certificates"""
|
|
self._create_instance_req()
|
|
# The fact that the action doesn't raise is enough validation
|
|
self.controller.create(self.req, body=self.body).obj
|
|
|
|
def test_create_instance_with_empty_trusted_cert_id(self):
|
|
"""Make sure we can't create with an empty certificate ID"""
|
|
self._create_instance_req([''])
|
|
ex = self.assertRaises(
|
|
exception.ValidationError, self.controller.create, self.req,
|
|
body=self.body)
|
|
self.assertIn('is too short', six.text_type(ex))
|
|
|
|
def test_create_instance_with_empty_trusted_certs(self):
|
|
"""Make sure we can't create with an empty array of IDs"""
|
|
self.body['server']['trusted_image_certificates'] = []
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.req.api_version_request = \
|
|
api_version_request.APIVersionRequest('2.63')
|
|
ex = self.assertRaises(
|
|
exception.ValidationError, self.controller.create, self.req,
|
|
body=self.body)
|
|
self.assertIn('is too short', six.text_type(ex))
|
|
|
|
def test_create_instance_with_too_many_trusted_certs(self):
|
|
"""Make sure we can't create with an array of >50 unique IDs"""
|
|
self._create_instance_req(['cert{}'.format(i) for i in range(51)])
|
|
ex = self.assertRaises(
|
|
exception.ValidationError, self.controller.create, self.req,
|
|
body=self.body)
|
|
self.assertIn('is too long', six.text_type(ex))
|
|
|
|
def test_create_instance_with_nonunique_trusted_certs(self):
|
|
"""Make sure we can't create with a non-unique array of IDs"""
|
|
self._create_instance_req(['cert', 'cert'])
|
|
ex = self.assertRaises(
|
|
exception.ValidationError, self.controller.create, self.req,
|
|
body=self.body)
|
|
self.assertIn('has non-unique elements', six.text_type(ex))
|
|
|
|
def test_create_instance_with_invalid_trusted_cert_id(self):
|
|
"""Make sure we can't create with non-string certificate IDs"""
|
|
self._create_instance_req([1, 2])
|
|
ex = self.assertRaises(
|
|
exception.ValidationError, self.controller.create, self.req,
|
|
body=self.body)
|
|
self.assertIn('is not of type', six.text_type(ex))
|
|
|
|
def test_create_instance_with_invalid_trusted_certs(self):
|
|
"""Make sure we can't create with certificates in a non-array"""
|
|
self._create_instance_req("not-an-array")
|
|
ex = self.assertRaises(
|
|
exception.ValidationError, self.controller.create, self.req,
|
|
body=self.body)
|
|
self.assertIn('is not of type', six.text_type(ex))
|
|
|
|
def test_create_server_with_trusted_certs_pre_2_63_fails(self):
|
|
"""Make sure we can't use trusted_certs before 2.63"""
|
|
self._create_instance_req(['trusted-cert-id'])
|
|
self.req.api_version_request = \
|
|
api_version_request.APIVersionRequest('2.62')
|
|
ex = self.assertRaises(
|
|
exception.ValidationError, self.controller.create, self.req,
|
|
body=self.body)
|
|
self.assertIn('Additional properties are not allowed',
|
|
six.text_type(ex))
|
|
|
|
def test_create_server_with_trusted_certs_policy_failed(self):
|
|
rule_name = "os_compute_api:servers:create:trusted_certs"
|
|
rules = {"os_compute_api:servers:create": "@",
|
|
"os_compute_api:servers:create:forced_host": "@",
|
|
"os_compute_api:servers:create:attach_volume": "@",
|
|
"os_compute_api:servers:create:attach_network": "@",
|
|
rule_name: "project:fake"}
|
|
self._create_instance_req(['0b5d2c72-12cc-4ba6-a8d7-3ff5cc1d8cb8'])
|
|
self.policy.set_rules(rules)
|
|
exc = self.assertRaises(exception.PolicyNotAuthorized,
|
|
self.controller.create, self.req,
|
|
body=self.body)
|
|
self.assertEqual(
|
|
"Policy doesn't allow %s to be performed." % rule_name,
|
|
exc.format_message())
|
|
|
|
@mock.patch.object(compute_api.API, 'create')
|
|
def test_create_server_with_cert_validation_error(
|
|
self, mock_create):
|
|
mock_create.side_effect = exception.CertificateValidationFailed(
|
|
cert_uuid="cert id", reason="test cert validation error")
|
|
|
|
self._create_instance_req(['trusted-cert-id'])
|
|
ex = self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.create, self.req,
|
|
body=self.body)
|
|
self.assertIn('test cert validation error',
|
|
six.text_type(ex))
|
|
|
|
@mock.patch('nova.objects.service.get_minimum_version_all_cells',
|
|
return_value=compute_api.MIN_COMPUTE_TRUSTED_CERTS - 1)
|
|
def test_create_server_with_cert_validation_not_available(
|
|
self, mock_get_min_version_all_cells):
|
|
self._create_instance_req(['trusted-cert-id'])
|
|
ex = self.assertRaises(webob.exc.HTTPConflict,
|
|
self.controller.create, self.req,
|
|
body=self.body)
|
|
self.assertIn('Image signature certificate validation support '
|
|
'is not yet available',
|
|
six.text_type(ex))
|
|
|
|
|
|
class ServersControllerCreateTestWithMock(test.TestCase):
|
|
image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
|
flavor_ref = 'http://localhost/123/flavors/3'
|
|
|
|
def setUp(self):
|
|
"""Shared implementation for tests below that create instance."""
|
|
super(ServersControllerCreateTestWithMock, self).setUp()
|
|
|
|
self.flags(enable_instance_password=True, group='api')
|
|
self.instance_cache_num = 0
|
|
self.instance_cache_by_id = {}
|
|
self.instance_cache_by_uuid = {}
|
|
|
|
self.controller = servers.ServersController()
|
|
|
|
self.body = {
|
|
'server': {
|
|
'name': 'server_test',
|
|
'imageRef': self.image_uuid,
|
|
'flavorRef': self.flavor_ref,
|
|
'metadata': {
|
|
'hello': 'world',
|
|
'open': 'stack',
|
|
},
|
|
},
|
|
}
|
|
self.req = fakes.HTTPRequest.blank('/fake/servers')
|
|
self.req.method = 'POST'
|
|
self.req.headers["content-type"] = "application/json"
|
|
|
|
def _test_create_extra(self, params, no_image=False):
|
|
self.body['server']['flavorRef'] = 2
|
|
if no_image:
|
|
self.body['server'].pop('imageRef', None)
|
|
self.body['server'].update(params)
|
|
self.req.body = jsonutils.dump_as_bytes(self.body)
|
|
self.req.headers["content-type"] = "application/json"
|
|
self.controller.create(self.req, body=self.body).obj['server']
|
|
|
|
@mock.patch.object(compute_api.API, 'create')
|
|
def test_create_instance_with_neutronv2_fixed_ip_already_in_use(self,
|
|
create_mock):
|
|
network = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
|
address = '10.0.2.3'
|
|
requested_networks = [{'uuid': network, 'fixed_ip': address}]
|
|
params = {'networks': requested_networks}
|
|
create_mock.side_effect = exception.FixedIpAlreadyInUse(
|
|
address=address,
|
|
instance_uuid=network)
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self._test_create_extra, params)
|
|
self.assertEqual(1, len(create_mock.call_args_list))
|
|
|
|
@mock.patch.object(compute_api.API, 'create')
|
|
def test_create_instance_with_neutronv2_invalid_fixed_ip(self,
|
|
create_mock):
|
|
self.flags(use_neutron=True)
|
|
network = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
|
address = '999.0.2.3'
|
|
requested_networks = [{'uuid': network, 'fixed_ip': address}]
|
|
params = {'networks': requested_networks}
|
|
self.assertRaises(exception.ValidationError,
|
|
self._test_create_extra, params)
|
|
self.assertFalse(create_mock.called)
|
|
|
|
@mock.patch.object(compute_api.API, 'create',
|
|
side_effect=exception.InvalidVolume(reason='error'))
|
|
def test_create_instance_with_invalid_volume_error(self, create_mock):
|
|
# Tests that InvalidVolume is translated to a 400 error.
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self._test_create_extra, {})
|
|
|
|
|
|
class ServersViewBuilderTest(test.TestCase):
|
|
|
|
def setUp(self):
|
|
super(ServersViewBuilderTest, self).setUp()
|
|
self.flags(use_ipv6=True)
|
|
self.flags(group='glance', api_servers=['http://localhost:9292'])
|
|
nw_cache_info = self._generate_nw_cache_info()
|
|
db_inst = fakes.stub_instance(
|
|
id=1,
|
|
image_ref="5",
|
|
uuid="deadbeef-feed-edee-beef-d0ea7beefedd",
|
|
display_name="test_server",
|
|
include_fake_metadata=False,
|
|
availability_zone='nova',
|
|
nw_cache=nw_cache_info,
|
|
launched_at=None,
|
|
terminated_at=None)
|
|
|
|
privates = ['172.19.0.1']
|
|
publics = ['192.168.0.3']
|
|
public6s = ['b33f::fdee:ddff:fecc:bbaa']
|
|
|
|
def nw_info(*args, **kwargs):
|
|
return [(None, {'label': 'public',
|
|
'ips': [dict(ip=ip) for ip in publics],
|
|
'ip6s': [dict(ip=ip) for ip in public6s]}),
|
|
(None, {'label': 'private',
|
|
'ips': [dict(ip=ip) for ip in privates]})]
|
|
|
|
fakes.stub_out_nw_api_get_instance_nw_info(self, nw_info)
|
|
|
|
self.uuid = db_inst['uuid']
|
|
self.view_builder = views.servers.ViewBuilder()
|
|
self.request = fakes.HTTPRequestV21.blank("/fake")
|
|
self.request.context = context.RequestContext('fake', 'fake')
|
|
self.instance = fake_instance.fake_instance_obj(
|
|
self.request.context,
|
|
expected_attrs=instance_obj.INSTANCE_DEFAULT_FIELDS,
|
|
**db_inst)
|
|
self.self_link = "http://localhost/v2/fake/servers/%s" % self.uuid
|
|
self.bookmark_link = "http://localhost/fake/servers/%s" % self.uuid
|
|
|
|
def _generate_nw_cache_info(self):
|
|
fixed_ipv4 = ('192.168.1.100', '192.168.2.100', '192.168.3.100')
|
|
fixed_ipv6 = ('2001:db8:0:1::1',)
|
|
|
|
def _ip(ip):
|
|
return {'address': ip, 'type': 'fixed'}
|
|
|
|
nw_cache = [
|
|
{'address': 'aa:aa:aa:aa:aa:aa',
|
|
'id': 1,
|
|
'network': {'bridge': 'br0',
|
|
'id': 1,
|
|
'label': 'test1',
|
|
'subnets': [{'cidr': '192.168.1.0/24',
|
|
'ips': [_ip(fixed_ipv4[0])]},
|
|
{'cidr': 'b33f::/64',
|
|
'ips': [_ip(fixed_ipv6[0])]}]}},
|
|
{'address': 'bb:bb:bb:bb:bb:bb',
|
|
'id': 2,
|
|
'network': {'bridge': 'br0',
|
|
'id': 1,
|
|
'label': 'test1',
|
|
'subnets': [{'cidr': '192.168.2.0/24',
|
|
'ips': [_ip(fixed_ipv4[1])]}]}},
|
|
{'address': 'cc:cc:cc:cc:cc:cc',
|
|
'id': 3,
|
|
'network': {'bridge': 'br0',
|
|
'id': 2,
|
|
'label': 'test2',
|
|
'subnets': [{'cidr': '192.168.3.0/24',
|
|
'ips': [_ip(fixed_ipv4[2])]}]}}]
|
|
return nw_cache
|
|
|
|
def test_get_flavor_valid_instance_type(self):
|
|
flavor_bookmark = "http://localhost/fake/flavors/1"
|
|
expected = {"id": "1",
|
|
"links": [{"rel": "bookmark",
|
|
"href": flavor_bookmark}]}
|
|
result = self.view_builder._get_flavor(self.request, self.instance,
|
|
False)
|
|
self.assertEqual(result, expected)
|
|
|
|
def test_build_server(self):
|
|
expected_server = {
|
|
"server": {
|
|
"id": self.uuid,
|
|
"name": "test_server",
|
|
"links": [
|
|
{
|
|
"rel": "self",
|
|
"href": self.self_link,
|
|
},
|
|
{
|
|
"rel": "bookmark",
|
|
"href": self.bookmark_link,
|
|
},
|
|
],
|
|
}
|
|
}
|
|
|
|
output = self.view_builder.basic(self.request, self.instance)
|
|
self.assertThat(output, matchers.DictMatches(expected_server))
|
|
|
|
def test_build_server_with_project_id(self):
|
|
expected_server = {
|
|
"server": {
|
|
"id": self.uuid,
|
|
"name": "test_server",
|
|
"links": [
|
|
{
|
|
"rel": "self",
|
|
"href": self.self_link,
|
|
},
|
|
{
|
|
"rel": "bookmark",
|
|
"href": self.bookmark_link,
|
|
},
|
|
],
|
|
}
|
|
}
|
|
|
|
output = self.view_builder.basic(self.request, self.instance)
|
|
self.assertThat(output, matchers.DictMatches(expected_server))
|
|
|
|
def test_build_server_detail(self):
|
|
image_bookmark = "http://localhost/fake/images/5"
|
|
flavor_bookmark = "http://localhost/fake/flavors/1"
|
|
expected_server = {
|
|
"server": {
|
|
"id": self.uuid,
|
|
"user_id": "fake_user",
|
|
"tenant_id": "fake_project",
|
|
"updated": "2010-11-11T11:00:00Z",
|
|
"created": "2010-10-10T12:00:00Z",
|
|
"progress": 0,
|
|
"name": "test_server",
|
|
"status": "ACTIVE",
|
|
"hostId": '',
|
|
"image": {
|
|
"id": "5",
|
|
"links": [
|
|
{
|
|
"rel": "bookmark",
|
|
"href": image_bookmark,
|
|
},
|
|
],
|
|
},
|
|
"flavor": {
|
|
"id": "1",
|
|
"links": [
|
|
{
|
|
"rel": "bookmark",
|
|
"href": flavor_bookmark,
|
|
},
|
|
],
|
|
},
|
|
"addresses": {
|
|
'test1': [
|
|
{'version': 4, 'addr': '192.168.1.100',
|
|
'OS-EXT-IPS:type': 'fixed',
|
|
'OS-EXT-IPS-MAC:mac_addr': 'aa:aa:aa:aa:aa:aa'},
|
|
{'version': 6, 'addr': '2001:db8:0:1::1',
|
|
'OS-EXT-IPS:type': 'fixed',
|
|
'OS-EXT-IPS-MAC:mac_addr': 'aa:aa:aa:aa:aa:aa'},
|
|
{'version': 4, 'addr': '192.168.2.100',
|
|
'OS-EXT-IPS:type': 'fixed',
|
|
'OS-EXT-IPS-MAC:mac_addr': 'bb:bb:bb:bb:bb:bb'}
|
|
],
|
|
'test2': [
|
|
{'version': 4, 'addr': '192.168.3.100',
|
|
'OS-EXT-IPS:type': 'fixed',
|
|
'OS-EXT-IPS-MAC:mac_addr': 'cc:cc:cc:cc:cc:cc'},
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"links": [
|
|
{
|
|
"rel": "self",
|
|
"href": self.self_link,
|
|
},
|
|
{
|
|
"rel": "bookmark",
|
|
"href": self.bookmark_link,
|
|
},
|
|
],
|
|
"OS-DCF:diskConfig": "MANUAL",
|
|
"accessIPv4": '',
|
|
"accessIPv6": '',
|
|
"OS-EXT-AZ:availability_zone": "nova",
|
|
"config_drive": None,
|
|
"OS-EXT-SRV-ATTR:host": None,
|
|
"OS-EXT-SRV-ATTR:hypervisor_hostname": None,
|
|
"OS-EXT-SRV-ATTR:instance_name": "instance-00000001",
|
|
"key_name": '',
|
|
"OS-SRV-USG:launched_at": None,
|
|
"OS-SRV-USG:terminated_at": None
|
|
}
|
|
}
|
|
|
|
output = self.view_builder.show(self.request, self.instance)
|
|
self.assertThat(output, matchers.DictMatches(expected_server))
|
|
|
|
def test_build_server_detail_with_fault(self):
|
|
self.instance['vm_state'] = vm_states.ERROR
|
|
self.instance['fault'] = fake_instance.fake_fault_obj(
|
|
self.request.context, self.uuid)
|
|
|
|
image_bookmark = "http://localhost/fake/images/5"
|
|
flavor_bookmark = "http://localhost/fake/flavors/1"
|
|
expected_server = {
|
|
"server": {
|
|
"id": self.uuid,
|
|
"user_id": "fake_user",
|
|
"tenant_id": "fake_project",
|
|
"updated": "2010-11-11T11:00:00Z",
|
|
"created": "2010-10-10T12:00:00Z",
|
|
"name": "test_server",
|
|
"status": "ERROR",
|
|
"hostId": '',
|
|
"image": {
|
|
"id": "5",
|
|
"links": [
|
|
{
|
|
"rel": "bookmark",
|
|
"href": image_bookmark,
|
|
},
|
|
],
|
|
},
|
|
"flavor": {
|
|
"id": "1",
|
|
"links": [
|
|
{
|
|
"rel": "bookmark",
|
|
"href": flavor_bookmark,
|
|
},
|
|
],
|
|
},
|
|
"addresses": {
|
|
'test1': [
|
|
{'version': 4, 'addr': '192.168.1.100',
|
|
'OS-EXT-IPS:type': 'fixed',
|
|
'OS-EXT-IPS-MAC:mac_addr': 'aa:aa:aa:aa:aa:aa'},
|
|
{'version': 6, 'addr': '2001:db8:0:1::1',
|
|
'OS-EXT-IPS:type': 'fixed',
|
|
'OS-EXT-IPS-MAC:mac_addr': 'aa:aa:aa:aa:aa:aa'},
|
|
{'version': 4, 'addr': '192.168.2.100',
|
|
'OS-EXT-IPS:type': 'fixed',
|
|
'OS-EXT-IPS-MAC:mac_addr': 'bb:bb:bb:bb:bb:bb'}
|
|
],
|
|
'test2': [
|
|
{'version': 4, 'addr': '192.168.3.100',
|
|
'OS-EXT-IPS:type': 'fixed',
|
|
'OS-EXT-IPS-MAC:mac_addr': 'cc:cc:cc:cc:cc:cc'},
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"links": [
|
|
{
|
|
"rel": "self",
|
|
"href": self.self_link,
|
|
},
|
|
{
|
|
"rel": "bookmark",
|
|
"href": self.bookmark_link,
|
|
},
|
|
],
|
|
"fault": {
|
|
"code": 404,
|
|
"created": "2010-10-10T12:00:00Z",
|
|
"message": "HTTPNotFound",
|
|
"details": "Stock details for test",
|
|
},
|
|
"OS-DCF:diskConfig": "MANUAL",
|
|
"accessIPv4": '',
|
|
"accessIPv6": '',
|
|
"OS-EXT-AZ:availability_zone": "nova",
|
|
"config_drive": None,
|
|
"OS-EXT-SRV-ATTR:host": None,
|
|
"OS-EXT-SRV-ATTR:hypervisor_hostname": None,
|
|
"OS-EXT-SRV-ATTR:instance_name": "instance-00000001",
|
|
"key_name": '',
|
|
"OS-SRV-USG:launched_at": None,
|
|
"OS-SRV-USG:terminated_at": None
|
|
|
|
}
|
|
}
|
|
|
|
self.request.context = context.RequestContext('fake', 'fake')
|
|
output = self.view_builder.show(self.request, self.instance)
|
|
self.assertThat(output, matchers.DictMatches(expected_server))
|
|
|
|
def test_build_server_detail_with_fault_that_has_been_deleted(self):
|
|
self.instance['deleted'] = 1
|
|
self.instance['vm_state'] = vm_states.ERROR
|
|
fault = fake_instance.fake_fault_obj(self.request.context,
|
|
self.uuid, code=500,
|
|
message="No valid host was found")
|
|
self.instance['fault'] = fault
|
|
|
|
expected_fault = {"code": 500,
|
|
"created": "2010-10-10T12:00:00Z",
|
|
"message": "No valid host was found"}
|
|
|
|
self.request.context = context.RequestContext('fake', 'fake')
|
|
output = self.view_builder.show(self.request, self.instance)
|
|
# Regardless of vm_state deleted servers should be DELETED
|
|
self.assertEqual("DELETED", output['server']['status'])
|
|
self.assertThat(output['server']['fault'],
|
|
matchers.DictMatches(expected_fault))
|
|
|
|
@mock.patch('nova.objects.InstanceMapping.get_by_instance_uuid')
|
|
def test_build_server_detail_with_fault_no_instance_mapping(self,
|
|
mock_im):
|
|
self.instance['vm_state'] = vm_states.ERROR
|
|
|
|
mock_im.side_effect = exception.InstanceMappingNotFound(uuid='foo')
|
|
|
|
self.request.context = context.RequestContext('fake', 'fake')
|
|
self.view_builder.show(self.request, self.instance)
|
|
mock_im.assert_called_once_with(mock.ANY, self.uuid)
|
|
|
|
@mock.patch('nova.objects.InstanceMapping.get_by_instance_uuid')
|
|
def test_build_server_detail_with_fault_loaded(self, mock_im):
|
|
self.instance['vm_state'] = vm_states.ERROR
|
|
fault = fake_instance.fake_fault_obj(self.request.context,
|
|
self.uuid, code=500,
|
|
message="No valid host was found")
|
|
self.instance['fault'] = fault
|
|
|
|
self.request.context = context.RequestContext('fake', 'fake')
|
|
self.view_builder.show(self.request, self.instance)
|
|
self.assertFalse(mock_im.called)
|
|
|
|
def test_build_server_detail_with_fault_no_details_not_admin(self):
|
|
self.instance['vm_state'] = vm_states.ERROR
|
|
self.instance['fault'] = fake_instance.fake_fault_obj(
|
|
self.request.context,
|
|
self.uuid,
|
|
code=500,
|
|
message='Error')
|
|
|
|
expected_fault = {"code": 500,
|
|
"created": "2010-10-10T12:00:00Z",
|
|
"message": "Error"}
|
|
|
|
self.request.context = context.RequestContext('fake', 'fake')
|
|
output = self.view_builder.show(self.request, self.instance)
|
|
self.assertThat(output['server']['fault'],
|
|
matchers.DictMatches(expected_fault))
|
|
|
|
def test_build_server_detail_with_fault_admin(self):
|
|
self.instance['vm_state'] = vm_states.ERROR
|
|
self.instance['fault'] = fake_instance.fake_fault_obj(
|
|
self.request.context,
|
|
self.uuid,
|
|
code=500,
|
|
message='Error')
|
|
|
|
expected_fault = {"code": 500,
|
|
"created": "2010-10-10T12:00:00Z",
|
|
"message": "Error",
|
|
'details': 'Stock details for test'}
|
|
|
|
self.request.environ['nova.context'].is_admin = True
|
|
output = self.view_builder.show(self.request, self.instance)
|
|
self.assertThat(output['server']['fault'],
|
|
matchers.DictMatches(expected_fault))
|
|
|
|
def test_build_server_detail_with_fault_no_details_admin(self):
|
|
self.instance['vm_state'] = vm_states.ERROR
|
|
self.instance['fault'] = fake_instance.fake_fault_obj(
|
|
self.request.context,
|
|
self.uuid,
|
|
code=500,
|
|
message='Error',
|
|
details='')
|
|
|
|
expected_fault = {"code": 500,
|
|
"created": "2010-10-10T12:00:00Z",
|
|
"message": "Error"}
|
|
|
|
self.request.environ['nova.context'].is_admin = True
|
|
output = self.view_builder.show(self.request, self.instance)
|
|
self.assertThat(output['server']['fault'],
|
|
matchers.DictMatches(expected_fault))
|
|
|
|
def test_build_server_detail_with_fault_but_active(self):
|
|
self.instance['vm_state'] = vm_states.ACTIVE
|
|
self.instance['progress'] = 100
|
|
self.instance['fault'] = fake_instance.fake_fault_obj(
|
|
self.request.context, self.uuid)
|
|
|
|
output = self.view_builder.show(self.request, self.instance)
|
|
self.assertNotIn('fault', output['server'])
|
|
|
|
def test_build_server_detail_active_status(self):
|
|
# set the power state of the instance to running
|
|
self.instance['vm_state'] = vm_states.ACTIVE
|
|
self.instance['progress'] = 100
|
|
image_bookmark = "http://localhost/fake/images/5"
|
|
flavor_bookmark = "http://localhost/fake/flavors/1"
|
|
expected_server = {
|
|
"server": {
|
|
"id": self.uuid,
|
|
"user_id": "fake_user",
|
|
"tenant_id": "fake_project",
|
|
"updated": "2010-11-11T11:00:00Z",
|
|
"created": "2010-10-10T12:00:00Z",
|
|
"progress": 100,
|
|
"name": "test_server",
|
|
"status": "ACTIVE",
|
|
"hostId": '',
|
|
"image": {
|
|
"id": "5",
|
|
"links": [
|
|
{
|
|
"rel": "bookmark",
|
|
"href": image_bookmark,
|
|
},
|
|
],
|
|
},
|
|
"flavor": {
|
|
"id": "1",
|
|
"links": [
|
|
{
|
|
"rel": "bookmark",
|
|
"href": flavor_bookmark,
|
|
},
|
|
],
|
|
},
|
|
"addresses": {
|
|
'test1': [
|
|
{'version': 4, 'addr': '192.168.1.100',
|
|
'OS-EXT-IPS:type': 'fixed',
|
|
'OS-EXT-IPS-MAC:mac_addr': 'aa:aa:aa:aa:aa:aa'},
|
|
{'version': 6, 'addr': '2001:db8:0:1::1',
|
|
'OS-EXT-IPS:type': 'fixed',
|
|
'OS-EXT-IPS-MAC:mac_addr': 'aa:aa:aa:aa:aa:aa'},
|
|
{'version': 4, 'addr': '192.168.2.100',
|
|
'OS-EXT-IPS:type': 'fixed',
|
|
'OS-EXT-IPS-MAC:mac_addr': 'bb:bb:bb:bb:bb:bb'}
|
|
],
|
|
'test2': [
|
|
{'version': 4, 'addr': '192.168.3.100',
|
|
'OS-EXT-IPS:type': 'fixed',
|
|
'OS-EXT-IPS-MAC:mac_addr': 'cc:cc:cc:cc:cc:cc'},
|
|
]
|
|
},
|
|
"metadata": {},
|
|
"links": [
|
|
{
|
|
"rel": "self",
|
|
"href": self.self_link,
|
|
},
|
|
{
|
|
"rel": "bookmark",
|
|
"href": self.bookmark_link,
|
|
},
|
|
],
|
|
"OS-DCF:diskConfig": "MANUAL",
|
|
"accessIPv4": '',
|
|
"accessIPv6": '',
|
|
"OS-EXT-AZ:availability_zone": "nova",
|
|
"config_drive": None,
|
|
"OS-EXT-SRV-ATTR:host": None,
|
|
"OS-EXT-SRV-ATTR:hypervisor_hostname": None,
|
|
"OS-EXT-SRV-ATTR:instance_name": "instance-00000001",
|
|
"key_name": '',
|
|
"OS-SRV-USG:launched_at": None,
|
|
"OS-SRV-USG:terminated_at": None
|
|
}
|
|
}
|
|
|
|
output = self.view_builder.show(self.request, self.instance)
|
|
self.assertThat(output, matchers.DictMatches(expected_server))
|
|
|
|
def test_build_server_detail_with_metadata(self):
|
|
|
|
metadata = []
|
|
metadata.append(models.InstanceMetadata(key="Open", value="Stack"))
|
|
metadata = nova_utils.metadata_to_dict(metadata)
|
|
self.instance['metadata'] = metadata
|
|
|
|
image_bookmark = "http://localhost/fake/images/5"
|
|
flavor_bookmark = "http://localhost/fake/flavors/1"
|
|
expected_server = {
|
|
"server": {
|
|
"id": self.uuid,
|
|
"user_id": "fake_user",
|
|
"tenant_id": "fake_project",
|
|
"updated": "2010-11-11T11:00:00Z",
|
|
"created": "2010-10-10T12:00:00Z",
|
|
"progress": 0,
|
|
"name": "test_server",
|
|
"status": "ACTIVE",
|
|
"hostId": '',
|
|
"image": {
|
|
"id": "5",
|
|
"links": [
|
|
{
|
|
"rel": "bookmark",
|
|
"href": image_bookmark,
|
|
},
|
|
],
|
|
},
|
|
"flavor": {
|
|
"id": "1",
|
|
"links": [
|
|
{
|
|
"rel": "bookmark",
|
|
"href": flavor_bookmark,
|
|
},
|
|
],
|
|
},
|
|
"addresses": {
|
|
'test1': [
|
|
{'version': 4, 'addr': '192.168.1.100',
|
|
'OS-EXT-IPS:type': 'fixed',
|
|
'OS-EXT-IPS-MAC:mac_addr': 'aa:aa:aa:aa:aa:aa'},
|
|
{'version': 6, 'addr': '2001:db8:0:1::1',
|
|
'OS-EXT-IPS:type': 'fixed',
|
|
'OS-EXT-IPS-MAC:mac_addr': 'aa:aa:aa:aa:aa:aa'},
|
|
{'version': 4, 'addr': '192.168.2.100',
|
|
'OS-EXT-IPS:type': 'fixed',
|
|
'OS-EXT-IPS-MAC:mac_addr': 'bb:bb:bb:bb:bb:bb'}
|
|
],
|
|
'test2': [
|
|
{'version': 4, 'addr': '192.168.3.100',
|
|
'OS-EXT-IPS:type': 'fixed',
|
|
'OS-EXT-IPS-MAC:mac_addr': 'cc:cc:cc:cc:cc:cc'},
|
|
]
|
|
},
|
|
"metadata": {"Open": "Stack"},
|
|
"links": [
|
|
{
|
|
"rel": "self",
|
|
"href": self.self_link,
|
|
},
|
|
{
|
|
"rel": "bookmark",
|
|
"href": self.bookmark_link,
|
|
},
|
|
],
|
|
"OS-DCF:diskConfig": "MANUAL",
|
|
"accessIPv4": '',
|
|
"accessIPv6": '',
|
|
"OS-EXT-AZ:availability_zone": "nova",
|
|
"config_drive": None,
|
|
"OS-EXT-SRV-ATTR:host": None,
|
|
"OS-EXT-SRV-ATTR:hypervisor_hostname": None,
|
|
"OS-EXT-SRV-ATTR:instance_name": "instance-00000001",
|
|
"key_name": '',
|
|
"OS-SRV-USG:launched_at": None,
|
|
"OS-SRV-USG:terminated_at": None
|
|
}
|
|
}
|
|
|
|
output = self.view_builder.show(self.request, self.instance)
|
|
self.assertThat(output, matchers.DictMatches(expected_server))
|
|
|
|
|
|
class ServersAllExtensionsTestCase(test.TestCase):
|
|
"""Servers tests using default API router with all extensions enabled.
|
|
|
|
The intent here is to catch cases where extensions end up throwing
|
|
an exception because of a malformed request before the core API
|
|
gets a chance to validate the request and return a 422 response.
|
|
|
|
For example, AccessIPsController extends servers.Controller::
|
|
|
|
| @wsgi.extends
|
|
| def create(self, req, resp_obj, body):
|
|
| context = req.environ['nova.context']
|
|
| if authorize(context) and 'server' in resp_obj.obj:
|
|
| resp_obj.attach(xml=AccessIPTemplate())
|
|
| server = resp_obj.obj['server']
|
|
| self._extend_server(req, server)
|
|
|
|
we want to ensure that the extension isn't barfing on an invalid
|
|
body.
|
|
"""
|
|
|
|
def setUp(self):
|
|
super(ServersAllExtensionsTestCase, self).setUp()
|
|
self.app = compute.APIRouterV21()
|
|
|
|
@mock.patch.object(compute_api.API, 'create',
|
|
side_effect=test.TestingException(
|
|
"Should not reach the compute API."))
|
|
def test_create_missing_server(self, mock_create):
|
|
# Test create with malformed body.
|
|
req = fakes.HTTPRequestV21.blank('/fake/servers')
|
|
req.method = 'POST'
|
|
req.content_type = 'application/json'
|
|
body = {'foo': {'a': 'b'}}
|
|
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
res = req.get_response(self.app)
|
|
self.assertEqual(400, res.status_int)
|
|
|
|
def test_update_missing_server(self):
|
|
# Test update with malformed body.
|
|
|
|
req = fakes.HTTPRequestV21.blank('/fake/servers/1')
|
|
req.method = 'PUT'
|
|
req.content_type = 'application/json'
|
|
body = {'foo': {'a': 'b'}}
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
with mock.patch('nova.objects.Instance.save') as mock_save:
|
|
res = req.get_response(self.app)
|
|
self.assertFalse(mock_save.called)
|
|
self.assertEqual(400, res.status_int)
|
|
|
|
|
|
class ServersInvalidRequestTestCase(test.TestCase):
|
|
"""Tests of places we throw 400 Bad Request from."""
|
|
|
|
def setUp(self):
|
|
super(ServersInvalidRequestTestCase, self).setUp()
|
|
self.controller = servers.ServersController()
|
|
|
|
def _invalid_server_create(self, body):
|
|
req = fakes.HTTPRequestV21.blank('/fake/servers')
|
|
req.method = 'POST'
|
|
|
|
self.assertRaises(exception.ValidationError,
|
|
self.controller.create, req, body=body)
|
|
|
|
def test_create_server_no_body(self):
|
|
self._invalid_server_create(body=None)
|
|
|
|
def test_create_server_missing_server(self):
|
|
body = {'foo': {'a': 'b'}}
|
|
self._invalid_server_create(body=body)
|
|
|
|
def test_create_server_malformed_entity(self):
|
|
body = {'server': 'string'}
|
|
self._invalid_server_create(body=body)
|
|
|
|
def _unprocessable_server_update(self, body):
|
|
req = fakes.HTTPRequestV21.blank('/fake/servers/%s' % FAKE_UUID)
|
|
req.method = 'PUT'
|
|
|
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
|
self.controller.update, req, FAKE_UUID, body=body)
|
|
|
|
def test_update_server_no_body(self):
|
|
self._invalid_server_create(body=None)
|
|
|
|
def test_update_server_missing_server(self):
|
|
body = {'foo': {'a': 'b'}}
|
|
self._invalid_server_create(body=body)
|
|
|
|
def test_create_update_malformed_entity(self):
|
|
body = {'server': 'string'}
|
|
self._invalid_server_create(body=body)
|
|
|
|
|
|
# TODO(alex_xu): There isn't specified file for ips extension. Most of
|
|
# unittest related to ips extension is in this file. So put the ips policy
|
|
# enforcement tests at here until there is specified file for ips extension.
|
|
class IPsPolicyEnforcementV21(test.NoDBTestCase):
|
|
|
|
def setUp(self):
|
|
super(IPsPolicyEnforcementV21, self).setUp()
|
|
self.controller = ips.IPsController()
|
|
self.req = fakes.HTTPRequest.blank("/v2/fake")
|
|
|
|
def test_index_policy_failed(self):
|
|
rule_name = "os_compute_api:ips:index"
|
|
self.policy.set_rules({rule_name: "project:non_fake"})
|
|
exc = self.assertRaises(
|
|
exception.PolicyNotAuthorized,
|
|
self.controller.index, self.req, fakes.FAKE_UUID)
|
|
self.assertEqual(
|
|
"Policy doesn't allow %s to be performed." % rule_name,
|
|
exc.format_message())
|
|
|
|
def test_show_policy_failed(self):
|
|
rule_name = "os_compute_api:ips:show"
|
|
self.policy.set_rules({rule_name: "project:non_fake"})
|
|
exc = self.assertRaises(
|
|
exception.PolicyNotAuthorized,
|
|
self.controller.show, self.req, fakes.FAKE_UUID, fakes.FAKE_UUID)
|
|
self.assertEqual(
|
|
"Policy doesn't allow %s to be performed." % rule_name,
|
|
exc.format_message())
|
|
|
|
|
|
class ServersPolicyEnforcementV21(test.NoDBTestCase):
|
|
|
|
def setUp(self):
|
|
super(ServersPolicyEnforcementV21, self).setUp()
|
|
self.useFixture(nova_fixtures.AllServicesCurrent())
|
|
self.controller = servers.ServersController()
|
|
self.req = fakes.HTTPRequest.blank('')
|
|
self.image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
|
|
|
def _common_policy_check(self, rules, rule_name, func, *arg, **kwarg):
|
|
self.policy.set_rules(rules)
|
|
exc = self.assertRaises(
|
|
exception.PolicyNotAuthorized, func, *arg, **kwarg)
|
|
self.assertEqual(
|
|
"Policy doesn't allow %s to be performed." % rule_name,
|
|
exc.format_message())
|
|
|
|
@mock.patch.object(servers.ServersController, '_get_instance')
|
|
def test_start_policy_failed(self, _get_instance_mock):
|
|
_get_instance_mock.return_value = None
|
|
rule_name = "os_compute_api:servers:start"
|
|
rule = {rule_name: "project:non_fake"}
|
|
self._common_policy_check(
|
|
rule, rule_name, self.controller._start_server,
|
|
self.req, FAKE_UUID, body={})
|
|
|
|
@mock.patch.object(servers.ServersController, '_get_instance')
|
|
def test_trigger_crash_dump_policy_failed_with_other_project(
|
|
self, _get_instance_mock):
|
|
_get_instance_mock.return_value = fake_instance.fake_instance_obj(
|
|
self.req.environ['nova.context'])
|
|
rule_name = "os_compute_api:servers:trigger_crash_dump"
|
|
rule = {rule_name: "project_id:%(project_id)s"}
|
|
self.req.api_version_request =\
|
|
api_version_request.APIVersionRequest('2.17')
|
|
# Change the project_id in request context.
|
|
self.req.environ['nova.context'].project_id = 'other-project'
|
|
self._common_policy_check(
|
|
rule, rule_name, self.controller._action_trigger_crash_dump,
|
|
self.req, FAKE_UUID, body={'trigger_crash_dump': None})
|
|
|
|
@mock.patch('nova.compute.api.API.trigger_crash_dump')
|
|
@mock.patch.object(servers.ServersController, '_get_instance')
|
|
def test_trigger_crash_dump_overridden_policy_pass_with_same_project(
|
|
self, _get_instance_mock, trigger_crash_dump_mock):
|
|
instance = fake_instance.fake_instance_obj(
|
|
self.req.environ['nova.context'],
|
|
project_id=self.req.environ['nova.context'].project_id)
|
|
_get_instance_mock.return_value = instance
|
|
rule_name = "os_compute_api:servers:trigger_crash_dump"
|
|
self.policy.set_rules({rule_name: "project_id:%(project_id)s"})
|
|
self.req.api_version_request = (
|
|
api_version_request.APIVersionRequest('2.17'))
|
|
self.controller._action_trigger_crash_dump(
|
|
self.req, fakes.FAKE_UUID, body={'trigger_crash_dump': None})
|
|
trigger_crash_dump_mock.assert_called_once_with(
|
|
self.req.environ['nova.context'], instance)
|
|
|
|
@mock.patch.object(servers.ServersController, '_get_instance')
|
|
def test_trigger_crash_dump_overridden_policy_failed_with_other_user(
|
|
self, _get_instance_mock):
|
|
_get_instance_mock.return_value = (
|
|
fake_instance.fake_instance_obj(self.req.environ['nova.context']))
|
|
rule_name = "os_compute_api:servers:trigger_crash_dump"
|
|
self.policy.set_rules({rule_name: "user_id:%(user_id)s"})
|
|
# Change the user_id in request context.
|
|
self.req.environ['nova.context'].user_id = 'other-user'
|
|
self.req.api_version_request = (
|
|
api_version_request.APIVersionRequest('2.17'))
|
|
exc = self.assertRaises(exception.PolicyNotAuthorized,
|
|
self.controller._action_trigger_crash_dump,
|
|
self.req,
|
|
fakes.FAKE_UUID,
|
|
body={'trigger_crash_dump': None})
|
|
self.assertEqual(
|
|
"Policy doesn't allow %s to be performed." % rule_name,
|
|
exc.format_message())
|
|
|
|
@mock.patch('nova.compute.api.API.trigger_crash_dump')
|
|
@mock.patch.object(servers.ServersController, '_get_instance')
|
|
def test_trigger_crash_dump_overridden_policy_pass_with_same_user(
|
|
self, _get_instance_mock, trigger_crash_dump_mock):
|
|
instance = fake_instance.fake_instance_obj(
|
|
self.req.environ['nova.context'],
|
|
user_id=self.req.environ['nova.context'].user_id)
|
|
_get_instance_mock.return_value = instance
|
|
rule_name = "os_compute_api:servers:trigger_crash_dump"
|
|
self.policy.set_rules({rule_name: "user_id:%(user_id)s"})
|
|
self.req.api_version_request = (
|
|
api_version_request.APIVersionRequest('2.17'))
|
|
self.controller._action_trigger_crash_dump(
|
|
self.req, fakes.FAKE_UUID, body={'trigger_crash_dump': None})
|
|
trigger_crash_dump_mock.assert_called_once_with(
|
|
self.req.environ['nova.context'], instance)
|
|
|
|
def test_index_policy_failed(self):
|
|
rule_name = "os_compute_api:servers:index"
|
|
rule = {rule_name: "project:non_fake"}
|
|
self._common_policy_check(
|
|
rule, rule_name, self.controller.index, self.req)
|
|
|
|
def test_detail_policy_failed(self):
|
|
rule_name = "os_compute_api:servers:detail"
|
|
rule = {rule_name: "project:non_fake"}
|
|
self._common_policy_check(
|
|
rule, rule_name, self.controller.detail, self.req)
|
|
|
|
def test_detail_get_tenants_policy_failed(self):
|
|
req = fakes.HTTPRequest.blank('')
|
|
req.GET["all_tenants"] = "True"
|
|
rule_name = "os_compute_api:servers:detail:get_all_tenants"
|
|
rule = {rule_name: "project:non_fake"}
|
|
self._common_policy_check(
|
|
rule, rule_name, self.controller._get_servers, req, True)
|
|
|
|
def test_index_get_tenants_policy_failed(self):
|
|
req = fakes.HTTPRequest.blank('')
|
|
req.GET["all_tenants"] = "True"
|
|
rule_name = "os_compute_api:servers:index:get_all_tenants"
|
|
rule = {rule_name: "project:non_fake"}
|
|
self._common_policy_check(
|
|
rule, rule_name, self.controller._get_servers, req, False)
|
|
|
|
@mock.patch.object(common, 'get_instance')
|
|
def test_show_policy_failed(self, get_instance_mock):
|
|
get_instance_mock.return_value = None
|
|
rule_name = "os_compute_api:servers:show"
|
|
rule = {rule_name: "project:non_fake"}
|
|
self._common_policy_check(
|
|
rule, rule_name, self.controller.show, self.req, FAKE_UUID)
|
|
|
|
@mock.patch.object(common, 'get_instance')
|
|
def test_delete_policy_failed_with_other_project(self, get_instance_mock):
|
|
get_instance_mock.return_value = fake_instance.fake_instance_obj(
|
|
self.req.environ['nova.context'])
|
|
rule_name = "os_compute_api:servers:delete"
|
|
rule = {rule_name: "project_id:%(project_id)s"}
|
|
# Change the project_id in request context.
|
|
self.req.environ['nova.context'].project_id = 'other-project'
|
|
self._common_policy_check(
|
|
rule, rule_name, self.controller.delete, self.req, FAKE_UUID)
|
|
|
|
@mock.patch('nova.compute.api.API.soft_delete')
|
|
@mock.patch('nova.api.openstack.common.get_instance')
|
|
def test_delete_overridden_policy_pass_with_same_project(self,
|
|
get_instance_mock,
|
|
soft_delete_mock):
|
|
self.flags(reclaim_instance_interval=3600)
|
|
instance = fake_instance.fake_instance_obj(
|
|
self.req.environ['nova.context'],
|
|
project_id=self.req.environ['nova.context'].project_id)
|
|
get_instance_mock.return_value = instance
|
|
rule_name = "os_compute_api:servers:delete"
|
|
self.policy.set_rules({rule_name: "project_id:%(project_id)s"})
|
|
self.controller.delete(self.req, fakes.FAKE_UUID)
|
|
soft_delete_mock.assert_called_once_with(
|
|
self.req.environ['nova.context'], instance)
|
|
|
|
@mock.patch('nova.api.openstack.common.get_instance')
|
|
def test_delete_overridden_policy_failed_with_other_user_in_same_project(
|
|
self, get_instance_mock):
|
|
get_instance_mock.return_value = (
|
|
fake_instance.fake_instance_obj(self.req.environ['nova.context']))
|
|
rule_name = "os_compute_api:servers:delete"
|
|
rule = {rule_name: "user_id:%(user_id)s"}
|
|
# Change the user_id in request context.
|
|
self.req.environ['nova.context'].user_id = 'other-user'
|
|
self._common_policy_check(
|
|
rule, rule_name, self.controller.delete, self.req, FAKE_UUID)
|
|
|
|
@mock.patch('nova.compute.api.API.soft_delete')
|
|
@mock.patch('nova.api.openstack.common.get_instance')
|
|
def test_delete_overridden_policy_pass_with_same_user(self,
|
|
get_instance_mock,
|
|
soft_delete_mock):
|
|
self.flags(reclaim_instance_interval=3600)
|
|
instance = fake_instance.fake_instance_obj(
|
|
self.req.environ['nova.context'],
|
|
user_id=self.req.environ['nova.context'].user_id)
|
|
get_instance_mock.return_value = instance
|
|
rule_name = "os_compute_api:servers:delete"
|
|
self.policy.set_rules({rule_name: "user_id:%(user_id)s"})
|
|
self.controller.delete(self.req, fakes.FAKE_UUID)
|
|
soft_delete_mock.assert_called_once_with(
|
|
self.req.environ['nova.context'], instance)
|
|
|
|
@mock.patch.object(common, 'get_instance')
|
|
def test_update_policy_failed_with_other_project(self, get_instance_mock):
|
|
get_instance_mock.return_value = fake_instance.fake_instance_obj(
|
|
self.req.environ['nova.context'])
|
|
rule_name = "os_compute_api:servers:update"
|
|
rule = {rule_name: "project_id:%(project_id)s"}
|
|
body = {'server': {'name': 'server_test'}}
|
|
# Change the project_id in request context.
|
|
self.req.environ['nova.context'].project_id = 'other-project'
|
|
self._common_policy_check(
|
|
rule, rule_name, self.controller.update, self.req,
|
|
FAKE_UUID, body=body)
|
|
|
|
@mock.patch('nova.api.openstack.compute.views.servers.ViewBuilder.show')
|
|
@mock.patch.object(compute_api.API, 'update_instance')
|
|
@mock.patch.object(common, 'get_instance')
|
|
def test_update_overridden_policy_pass_with_same_project(
|
|
self, get_instance_mock, update_instance_mock, view_show_mock):
|
|
instance = fake_instance.fake_instance_obj(
|
|
self.req.environ['nova.context'],
|
|
project_id=self.req.environ['nova.context'].project_id)
|
|
get_instance_mock.return_value = instance
|
|
rule_name = "os_compute_api:servers:update"
|
|
self.policy.set_rules({rule_name: "project_id:%(project_id)s"})
|
|
body = {'server': {'name': 'server_test'}}
|
|
self.controller.update(self.req, fakes.FAKE_UUID, body=body)
|
|
|
|
@mock.patch.object(common, 'get_instance')
|
|
def test_update_overridden_policy_failed_with_other_user_in_same_project(
|
|
self, get_instance_mock):
|
|
get_instance_mock.return_value = (
|
|
fake_instance.fake_instance_obj(self.req.environ['nova.context']))
|
|
rule_name = "os_compute_api:servers:update"
|
|
rule = {rule_name: "user_id:%(user_id)s"}
|
|
# Change the user_id in request context.
|
|
self.req.environ['nova.context'].user_id = 'other-user'
|
|
body = {'server': {'name': 'server_test'}}
|
|
self._common_policy_check(
|
|
rule, rule_name, self.controller.update, self.req,
|
|
FAKE_UUID, body=body)
|
|
|
|
@mock.patch('nova.api.openstack.compute.views.servers.ViewBuilder.show')
|
|
@mock.patch.object(compute_api.API, 'update_instance')
|
|
@mock.patch.object(common, 'get_instance')
|
|
def test_update_overridden_policy_pass_with_same_user(self,
|
|
get_instance_mock,
|
|
update_instance_mock,
|
|
view_show_mock):
|
|
instance = fake_instance.fake_instance_obj(
|
|
self.req.environ['nova.context'],
|
|
user_id=self.req.environ['nova.context'].user_id)
|
|
get_instance_mock.return_value = instance
|
|
rule_name = "os_compute_api:servers:update"
|
|
self.policy.set_rules({rule_name: "user_id:%(user_id)s"})
|
|
body = {'server': {'name': 'server_test'}}
|
|
self.controller.update(self.req, fakes.FAKE_UUID, body=body)
|
|
|
|
def test_confirm_resize_policy_failed(self):
|
|
rule_name = "os_compute_api:servers:confirm_resize"
|
|
rule = {rule_name: "project:non_fake"}
|
|
body = {'server': {'name': 'server_test'}}
|
|
self._common_policy_check(
|
|
rule, rule_name, self.controller._action_confirm_resize,
|
|
self.req, FAKE_UUID, body=body)
|
|
|
|
def test_revert_resize_policy_failed(self):
|
|
rule_name = "os_compute_api:servers:revert_resize"
|
|
rule = {rule_name: "project:non_fake"}
|
|
body = {'server': {'name': 'server_test'}}
|
|
self._common_policy_check(
|
|
rule, rule_name, self.controller._action_revert_resize,
|
|
self.req, FAKE_UUID, body=body)
|
|
|
|
def test_reboot_policy_failed(self):
|
|
rule_name = "os_compute_api:servers:reboot"
|
|
rule = {rule_name: "project:non_fake"}
|
|
body = {'reboot': {'type': 'HARD'}}
|
|
self._common_policy_check(
|
|
rule, rule_name, self.controller._action_reboot,
|
|
self.req, FAKE_UUID, body=body)
|
|
|
|
@mock.patch('nova.api.openstack.common.get_instance')
|
|
def test_resize_policy_failed_with_other_project(self, get_instance_mock):
|
|
get_instance_mock.return_value = (
|
|
fake_instance.fake_instance_obj(self.req.environ['nova.context']))
|
|
rule_name = "os_compute_api:servers:resize"
|
|
rule = {rule_name: "project_id:%(project_id)s"}
|
|
body = {'resize': {'flavorRef': '1'}}
|
|
# Change the project_id in request context.
|
|
self.req.environ['nova.context'].project_id = 'other-project'
|
|
self._common_policy_check(
|
|
rule, rule_name, self.controller._action_resize, self.req,
|
|
FAKE_UUID, body=body)
|
|
|
|
@mock.patch('nova.compute.api.API.resize')
|
|
@mock.patch('nova.api.openstack.common.get_instance')
|
|
def test_resize_overridden_policy_pass_with_same_project(self,
|
|
get_instance_mock,
|
|
resize_mock):
|
|
instance = fake_instance.fake_instance_obj(
|
|
self.req.environ['nova.context'],
|
|
project_id=self.req.environ['nova.context'].project_id)
|
|
get_instance_mock.return_value = instance
|
|
rule_name = "os_compute_api:servers:resize"
|
|
self.policy.set_rules({rule_name: "project_id:%(project_id)s"})
|
|
body = {'resize': {'flavorRef': '1'}}
|
|
self.controller._action_resize(self.req, fakes.FAKE_UUID, body=body)
|
|
resize_mock.assert_called_once_with(self.req.environ['nova.context'],
|
|
instance, '1')
|
|
|
|
@mock.patch('nova.api.openstack.common.get_instance')
|
|
def test_resize_overridden_policy_failed_with_other_user_in_same_project(
|
|
self, get_instance_mock):
|
|
get_instance_mock.return_value = (
|
|
fake_instance.fake_instance_obj(self.req.environ['nova.context']))
|
|
rule_name = "os_compute_api:servers:resize"
|
|
rule = {rule_name: "user_id:%(user_id)s"}
|
|
# Change the user_id in request context.
|
|
self.req.environ['nova.context'].user_id = 'other-user'
|
|
body = {'resize': {'flavorRef': '1'}}
|
|
self._common_policy_check(
|
|
rule, rule_name, self.controller._action_resize, self.req,
|
|
FAKE_UUID, body=body)
|
|
|
|
@mock.patch('nova.compute.api.API.resize')
|
|
@mock.patch('nova.api.openstack.common.get_instance')
|
|
def test_resize_overridden_policy_pass_with_same_user(self,
|
|
get_instance_mock,
|
|
resize_mock):
|
|
instance = fake_instance.fake_instance_obj(
|
|
self.req.environ['nova.context'],
|
|
user_id=self.req.environ['nova.context'].user_id)
|
|
get_instance_mock.return_value = instance
|
|
rule_name = "os_compute_api:servers:resize"
|
|
self.policy.set_rules({rule_name: "user_id:%(user_id)s"})
|
|
body = {'resize': {'flavorRef': '1'}}
|
|
self.controller._action_resize(self.req, fakes.FAKE_UUID, body=body)
|
|
resize_mock.assert_called_once_with(self.req.environ['nova.context'],
|
|
instance, '1')
|
|
|
|
@mock.patch('nova.api.openstack.common.get_instance')
|
|
def test_rebuild_policy_failed_with_other_project(self, get_instance_mock):
|
|
get_instance_mock.return_value = fake_instance.fake_instance_obj(
|
|
self.req.environ['nova.context'],
|
|
project_id=self.req.environ['nova.context'].project_id)
|
|
rule_name = "os_compute_api:servers:rebuild"
|
|
rule = {rule_name: "project_id:%(project_id)s"}
|
|
body = {'rebuild': {'imageRef': self.image_uuid}}
|
|
# Change the project_id in request context.
|
|
self.req.environ['nova.context'].project_id = 'other-project'
|
|
self._common_policy_check(
|
|
rule, rule_name, self.controller._action_rebuild,
|
|
self.req, FAKE_UUID, body=body)
|
|
|
|
@mock.patch('nova.api.openstack.common.get_instance')
|
|
def test_rebuild_overridden_policy_failed_with_other_user_in_same_project(
|
|
self, get_instance_mock):
|
|
get_instance_mock.return_value = (
|
|
fake_instance.fake_instance_obj(self.req.environ['nova.context']))
|
|
rule_name = "os_compute_api:servers:rebuild"
|
|
rule = {rule_name: "user_id:%(user_id)s"}
|
|
body = {'rebuild': {'imageRef': self.image_uuid}}
|
|
# Change the user_id in request context.
|
|
self.req.environ['nova.context'].user_id = 'other-user'
|
|
self._common_policy_check(
|
|
rule, rule_name, self.controller._action_rebuild,
|
|
self.req, FAKE_UUID, body=body)
|
|
|
|
@mock.patch('nova.api.openstack.compute.views.servers.ViewBuilder.show')
|
|
@mock.patch('nova.compute.api.API.rebuild')
|
|
@mock.patch('nova.api.openstack.common.get_instance')
|
|
def test_rebuild_overridden_policy_pass_with_same_user(self,
|
|
get_instance_mock,
|
|
rebuild_mock,
|
|
view_show_mock):
|
|
instance = fake_instance.fake_instance_obj(
|
|
self.req.environ['nova.context'],
|
|
user_id=self.req.environ['nova.context'].user_id)
|
|
get_instance_mock.return_value = instance
|
|
rule_name = "os_compute_api:servers:rebuild"
|
|
self.policy.set_rules({rule_name: "user_id:%(user_id)s"})
|
|
body = {'rebuild': {'imageRef': self.image_uuid,
|
|
'adminPass': 'dumpy_password'}}
|
|
self.controller._action_rebuild(self.req, fakes.FAKE_UUID, body=body)
|
|
rebuild_mock.assert_called_once_with(self.req.environ['nova.context'],
|
|
instance,
|
|
self.image_uuid,
|
|
'dumpy_password')
|
|
|
|
def test_create_image_policy_failed(self):
|
|
rule_name = "os_compute_api:servers:create_image"
|
|
rule = {rule_name: "project:non_fake"}
|
|
body = {
|
|
'createImage': {
|
|
'name': 'Snapshot 1',
|
|
},
|
|
}
|
|
self._common_policy_check(
|
|
rule, rule_name, self.controller._action_create_image,
|
|
self.req, FAKE_UUID, body=body)
|
|
|
|
@mock.patch('nova.compute.utils.is_volume_backed_instance',
|
|
return_value=True)
|
|
@mock.patch.object(objects.BlockDeviceMappingList, 'get_by_instance_uuid')
|
|
@mock.patch.object(servers.ServersController, '_get_server')
|
|
def test_create_vol_backed_img_snapshotting_policy_blocks_project(self,
|
|
mock_get_server,
|
|
mock_get_uuidi,
|
|
mock_is_vol_back):
|
|
"""Don't permit a snapshot of a volume backed instance if configured
|
|
not to based on project
|
|
"""
|
|
rule_name = "os_compute_api:servers:create_image:allow_volume_backed"
|
|
rules = {
|
|
rule_name: "project:non_fake",
|
|
"os_compute_api:servers:create_image": "",
|
|
}
|
|
body = {
|
|
'createImage': {
|
|
'name': 'Snapshot 1',
|
|
},
|
|
}
|
|
self._common_policy_check(
|
|
rules, rule_name, self.controller._action_create_image,
|
|
self.req, FAKE_UUID, body=body)
|
|
|
|
@mock.patch('nova.compute.utils.is_volume_backed_instance',
|
|
return_value=True)
|
|
@mock.patch.object(objects.BlockDeviceMappingList, 'get_by_instance_uuid')
|
|
@mock.patch.object(servers.ServersController, '_get_server')
|
|
def test_create_vol_backed_img_snapshotting_policy_blocks_role(self,
|
|
mock_get_server,
|
|
mock_get_uuidi,
|
|
mock_is_vol_back):
|
|
"""Don't permit a snapshot of a volume backed instance if configured
|
|
not to based on role
|
|
"""
|
|
rule_name = "os_compute_api:servers:create_image:allow_volume_backed"
|
|
rules = {
|
|
rule_name: "role:non_fake",
|
|
"os_compute_api:servers:create_image": "",
|
|
}
|
|
body = {
|
|
'createImage': {
|
|
'name': 'Snapshot 1',
|
|
},
|
|
}
|
|
self._common_policy_check(
|
|
rules, rule_name, self.controller._action_create_image,
|
|
self.req, FAKE_UUID, body=body)
|
|
|
|
def _create_policy_check(self, rules, rule_name):
|
|
flavor_ref = 'http://localhost/123/flavors/3'
|
|
body = {
|
|
'server': {
|
|
'name': 'server_test',
|
|
'imageRef': self.image_uuid,
|
|
'flavorRef': flavor_ref,
|
|
'availability_zone': "zone1:host1:node1",
|
|
'block_device_mapping': [{'device_name': "/dev/sda1"}],
|
|
'networks': [{'uuid': 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'}],
|
|
'metadata': {
|
|
'hello': 'world',
|
|
'open': 'stack',
|
|
},
|
|
},
|
|
}
|
|
self._common_policy_check(
|
|
rules, rule_name, self.controller.create, self.req, body=body)
|
|
|
|
def test_create_policy_failed(self):
|
|
rule_name = "os_compute_api:servers:create"
|
|
rules = {rule_name: "project:non_fake"}
|
|
self._create_policy_check(rules, rule_name)
|
|
|
|
def test_create_forced_host_policy_failed(self):
|
|
rule_name = "os_compute_api:servers:create:forced_host"
|
|
rule = {"os_compute_api:servers:create": "@",
|
|
rule_name: "project:non_fake"}
|
|
self._create_policy_check(rule, rule_name)
|
|
|
|
def test_create_attach_volume_policy_failed(self):
|
|
rule_name = "os_compute_api:servers:create:attach_volume"
|
|
rules = {"os_compute_api:servers:create": "@",
|
|
"os_compute_api:servers:create:forced_host": "@",
|
|
rule_name: "project:non_fake"}
|
|
self._create_policy_check(rules, rule_name)
|
|
|
|
def test_create_attach_attach_network_policy_failed(self):
|
|
rule_name = "os_compute_api:servers:create:attach_network"
|
|
rules = {"os_compute_api:servers:create": "@",
|
|
"os_compute_api:servers:create:forced_host": "@",
|
|
"os_compute_api:servers:create:attach_volume": "@",
|
|
rule_name: "project:non_fake"}
|
|
self._create_policy_check(rules, rule_name)
|
|
|
|
|
|
class ServersActionsJsonTestV239(test.NoDBTestCase):
|
|
|
|
def setUp(self):
|
|
super(ServersActionsJsonTestV239, self).setUp()
|
|
self.controller = servers.ServersController()
|
|
self.req = fakes.HTTPRequest.blank('', version='2.39')
|
|
|
|
@mock.patch.object(common, 'check_img_metadata_properties_quota')
|
|
@mock.patch.object(common, 'get_instance')
|
|
def test_server_create_image_no_quota_checks(self, mock_get_instance,
|
|
mock_check_quotas):
|
|
# 'mock_get_instance' helps to skip the whole logic of the action,
|
|
# but to make the test
|
|
mock_get_instance.side_effect = webob.exc.HTTPNotFound
|
|
body = {
|
|
'createImage': {
|
|
'name': 'Snapshot 1',
|
|
},
|
|
}
|
|
self.assertRaises(webob.exc.HTTPNotFound,
|
|
self.controller._action_create_image, self.req,
|
|
FAKE_UUID, body=body)
|
|
# starting from version 2.39 no quota checks on Nova side are performed
|
|
# for 'createImage' action after removing 'image-metadata' proxy API
|
|
mock_check_quotas.assert_not_called()
|