heat/contrib/rackspace/rackspace/tests/test_rackspace_cloud_server.py

663 lines
27 KiB
Python

#
# 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 mock
from oslo_config import cfg
from oslo_utils import uuidutils
import six
from heat.common import exception
from heat.common import template_format
from heat.engine.clients.os import glance
from heat.engine.clients.os import neutron
from heat.engine.clients.os import nova
from heat.engine import environment
from heat.engine import resource
from heat.engine import rsrc_defn
from heat.engine import scheduler
from heat.engine import stack as parser
from heat.engine import template
from heat.tests import common
from heat.tests.openstack.nova import fakes
from heat.tests import utils
from ..resources import cloud_server # noqa
wp_template = '''
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "WordPress",
"Parameters" : {
"key_name" : {
"Description" : "key_name",
"Type" : "String",
"Default" : "test"
}
},
"Resources" : {
"WebServer": {
"Type": "OS::Nova::Server",
"Properties": {
"image" : "CentOS 5.2",
"flavor" : "256 MB Server",
"key_name" : "test",
"user_data" : "wordpress"
}
}
}
}
'''
cfg.CONF.import_opt('region_name_for_services', 'heat.common.config')
class CloudServersTest(common.HeatTestCase):
def setUp(self):
super(CloudServersTest, self).setUp()
cfg.CONF.set_override('region_name_for_services', 'RegionOne')
self.ctx = utils.dummy_context()
self.fc = fakes.FakeClient()
mock_nova_create = mock.Mock()
self.ctx.clients.client_plugin(
'nova')._create = mock_nova_create
mock_nova_create.return_value = self.fc
# Test environment may not have pyrax client library installed and if
# pyrax is not installed resource class would not be registered.
# So register resource provider class explicitly for unit testing.
resource._register_class("OS::Nova::Server",
cloud_server.CloudServer)
def _setup_test_stack(self, stack_name):
t = template_format.parse(wp_template)
templ = template.Template(
t, env=environment.Environment({'key_name': 'test'}))
self.stack = parser.Stack(self.ctx, stack_name, templ,
stack_id=uuidutils.generate_uuid())
return (templ, self.stack)
def _setup_test_server(self, return_server, name, image_id=None,
override_name=False, stub_create=True):
stack_name = '%s_s' % name
(tmpl, stack) = self._setup_test_stack(stack_name)
tmpl.t['Resources']['WebServer']['Properties'][
'image'] = image_id or 'CentOS 5.2'
tmpl.t['Resources']['WebServer']['Properties'][
'flavor'] = '256 MB Server'
self.patchobject(neutron.NeutronClientPlugin,
'find_resourceid_by_name_or_id',
return_value='aaaaaa')
self.patchobject(nova.NovaClientPlugin, 'find_flavor_by_name_or_id',
return_value=1)
self.patchobject(glance.GlanceClientPlugin, 'find_image_by_name_or_id',
return_value=1)
server_name = '%s' % name
if override_name:
tmpl.t['Resources']['WebServer']['Properties'][
'name'] = server_name
resource_defns = tmpl.resource_definitions(stack)
server = cloud_server.CloudServer(server_name,
resource_defns['WebServer'],
stack)
self.patchobject(nova.NovaClientPlugin, '_create',
return_value=self.fc)
self.patchobject(server, 'store_external_ports')
if stub_create:
self.patchobject(self.fc.servers, 'create',
return_value=return_server)
# mock check_create_complete innards
self.patchobject(self.fc.servers, 'get',
return_value=return_server)
return server
def _create_test_server(self, return_server, name, override_name=False,
stub_create=True):
server = self._setup_test_server(return_server, name,
stub_create=stub_create)
scheduler.TaskRunner(server.create)()
return server
def _mock_metadata_os_distro(self):
image_data = mock.Mock(metadata={'os_distro': 'centos'})
self.fc.images.get = mock.Mock(return_value=image_data)
def test_rackconnect_deployed(self):
return_server = self.fc.servers.list()[1]
return_server.metadata = {
'rackconnect_automation_status': 'DEPLOYED',
'rax_service_level_automation': 'Complete',
}
server = self._setup_test_server(return_server,
'test_rackconnect_deployed')
server.context.roles = ['rack_connect']
scheduler.TaskRunner(server.create)()
self.assertEqual('CREATE', server.action)
self.assertEqual('COMPLETE', server.status)
def test_rackconnect_failed(self):
return_server = self.fc.servers.list()[1]
return_server.metadata = {
'rackconnect_automation_status': 'FAILED',
'rax_service_level_automation': 'Complete',
}
server = self._setup_test_server(return_server,
'test_rackconnect_failed')
server.context.roles = ['rack_connect']
create = scheduler.TaskRunner(server.create)
exc = self.assertRaises(exception.ResourceFailure, create)
self.assertEqual('Error: resources.test_rackconnect_failed: '
'RackConnect automation FAILED',
six.text_type(exc))
def test_rackconnect_unprocessable(self):
return_server = self.fc.servers.list()[1]
return_server.metadata = {
'rackconnect_automation_status': 'UNPROCESSABLE',
'rackconnect_unprocessable_reason': 'Fake reason',
'rax_service_level_automation': 'Complete',
}
server = self._setup_test_server(return_server,
'test_rackconnect_unprocessable')
server.context.roles = ['rack_connect']
scheduler.TaskRunner(server.create)()
self.assertEqual('CREATE', server.action)
self.assertEqual('COMPLETE', server.status)
def test_rackconnect_unknown(self):
return_server = self.fc.servers.list()[1]
return_server.metadata = {
'rackconnect_automation_status': 'FOO',
'rax_service_level_automation': 'Complete',
}
server = self._setup_test_server(return_server,
'test_rackconnect_unknown')
server.context.roles = ['rack_connect']
create = scheduler.TaskRunner(server.create)
exc = self.assertRaises(exception.ResourceFailure, create)
self.assertEqual('Error: resources.test_rackconnect_unknown: '
'Unknown RackConnect automation status: FOO',
six.text_type(exc))
def test_rackconnect_deploying(self):
return_server = self.fc.servers.list()[0]
server = self._setup_test_server(return_server,
'srv_sts_bld')
server.resource_id = 1234
server.context.roles = ['rack_connect']
check_iterations = [0]
# Bind fake get method which check_create_complete will call
def activate_status(server):
check_iterations[0] += 1
if check_iterations[0] == 1:
return_server.metadata.update({
'rackconnect_automation_status': 'DEPLOYING',
'rax_service_level_automation': 'Complete',
})
if check_iterations[0] == 2:
return_server.status = 'ACTIVE'
if check_iterations[0] > 3:
return_server.metadata.update({
'rackconnect_automation_status': 'DEPLOYED',
})
return return_server
self.patchobject(self.fc.servers, 'get',
side_effect=activate_status)
scheduler.TaskRunner(server.create)()
self.assertEqual((server.CREATE, server.COMPLETE), server.state)
def test_rackconnect_no_status(self):
return_server = self.fc.servers.list()[0]
server = self._setup_test_server(return_server,
'srv_sts_bld')
server.resource_id = 1234
server.context.roles = ['rack_connect']
check_iterations = [0]
# Bind fake get method which check_create_complete will call
def activate_status(server):
check_iterations[0] += 1
if check_iterations[0] == 1:
return_server.status = 'ACTIVE'
if check_iterations[0] > 2:
return_server.metadata.update({
'rackconnect_automation_status': 'DEPLOYED',
'rax_service_level_automation': 'Complete'})
return return_server
self.patchobject(self.fc.servers, 'get',
side_effect=activate_status)
scheduler.TaskRunner(server.create)()
self.assertEqual((server.CREATE, server.COMPLETE), server.state)
def test_rax_automation_lifecycle(self):
return_server = self.fc.servers.list()[0]
server = self._setup_test_server(return_server,
'srv_sts_bld')
server.resource_id = 1234
server.context.roles = ['rack_connect']
server.metadata = {}
check_iterations = [0]
# Bind fake get method which check_create_complete will call
def activate_status(server):
check_iterations[0] += 1
if check_iterations[0] == 1:
return_server.status = 'ACTIVE'
if check_iterations[0] == 2:
return_server.metadata = {
'rackconnect_automation_status': 'DEPLOYED'}
if check_iterations[0] == 3:
return_server.metadata = {
'rackconnect_automation_status': 'DEPLOYED',
'rax_service_level_automation': 'In Progress'}
if check_iterations[0] > 3:
return_server.metadata = {
'rackconnect_automation_status': 'DEPLOYED',
'rax_service_level_automation': 'Complete'}
return return_server
self.patchobject(self.fc.servers, 'get',
side_effect=activate_status)
scheduler.TaskRunner(server.create)()
self.assertEqual((server.CREATE, server.COMPLETE), server.state)
def test_add_port_for_addresses(self):
return_server = self.fc.servers.list()[1]
return_server.metadata = {'rax_service_level_automation': 'Complete'}
stack_name = 'test_stack'
(tmpl, stack) = self._setup_test_stack(stack_name)
resource_defns = tmpl.resource_definitions(stack)
self.patchobject(nova.NovaClientPlugin, 'find_flavor_by_name_or_id',
return_value=1)
self.patchobject(glance.GlanceClientPlugin, 'find_image_by_name_or_id',
return_value=1)
server = cloud_server.CloudServer('WebServer',
resource_defns['WebServer'], stack)
self.patchobject(server, 'store_external_ports')
class Interface(object):
def __init__(self, id, addresses):
self.identifier = id
self.addresses = addresses
@property
def id(self):
return self.identifier
@property
def ip_addresses(self):
return self.addresses
interfaces = [
{
"id": "port-uuid-1",
"ip_addresses": [
{
"address": "4.5.6.7",
"network_id": "00xx000-0xx0-0xx0-0xx0-00xxx000",
"network_label": "public"
},
{
"address": "2001:4802:7805:104:be76:4eff:fe20:2063",
"network_id": "00xx000-0xx0-0xx0-0xx0-00xxx000",
"network_label": "public"
}
],
"mac_address": "fa:16:3e:8c:22:aa"
},
{
"id": "port-uuid-2",
"ip_addresses": [
{
"address": "5.6.9.8",
"network_id": "11xx1-1xx1-xx11-1xx1-11xxxx11",
"network_label": "public"
}
],
"mac_address": "fa:16:3e:8c:44:cc"
},
{
"id": "port-uuid-3",
"ip_addresses": [
{
"address": "10.13.12.13",
"network_id": "1xx1-1xx1-xx11-1xx1-11xxxx11",
"network_label": "private"
}
],
"mac_address": "fa:16:3e:8c:44:dd"
}
]
ifaces = [Interface(i['id'], i['ip_addresses']) for i in interfaces]
expected = {
'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa':
[{'OS-EXT-IPS-MAC:mac_addr': 'fa:16:3e:8c:22:aa',
'addr': '4.5.6.7',
'port': 'port-uuid-1',
'version': 4},
{'OS-EXT-IPS-MAC:mac_addr': 'fa:16:3e:8c:33:bb',
'addr': '5.6.9.8',
'port': 'port-uuid-2',
'version': 4}],
'private': [{'OS-EXT-IPS-MAC:mac_addr': 'fa:16:3e:8c:44:cc',
'addr': '10.13.12.13',
'port': 'port-uuid-3',
'version': 4}],
'public': [{'OS-EXT-IPS-MAC:mac_addr': 'fa:16:3e:8c:22:aa',
'addr': '4.5.6.7',
'port': 'port-uuid-1',
'version': 4},
{'OS-EXT-IPS-MAC:mac_addr': 'fa:16:3e:8c:33:bb',
'addr': '5.6.9.8',
'port': 'port-uuid-2',
'version': 4}]}
server.client = mock.Mock()
mock_client = mock.Mock()
server.client.return_value = mock_client
mock_ext = mock_client.os_virtual_interfacesv2_python_novaclient_ext
mock_ext.list.return_value = ifaces
resp = server._add_port_for_address(return_server)
self.assertEqual(expected, resp)
def test_rax_automation_build_error(self):
return_server = self.fc.servers.list()[1]
return_server.metadata = {'rax_service_level_automation':
'Build Error'}
server = self._setup_test_server(return_server,
'test_managed_cloud_build_error')
create = scheduler.TaskRunner(server.create)
exc = self.assertRaises(exception.ResourceFailure, create)
self.assertEqual('Error: resources.test_managed_cloud_build_error: '
'Rackspace Cloud automation failed',
six.text_type(exc))
def test_rax_automation_unknown(self):
return_server = self.fc.servers.list()[1]
return_server.metadata = {'rax_service_level_automation': 'FOO'}
server = self._setup_test_server(return_server,
'test_managed_cloud_unknown')
create = scheduler.TaskRunner(server.create)
exc = self.assertRaises(exception.ResourceFailure, create)
self.assertEqual('Error: resources.test_managed_cloud_unknown: '
'Unknown Rackspace Cloud automation status: FOO',
six.text_type(exc))
def _test_server_config_drive(self, user_data, config_drive, result,
ud_format='RAW'):
return_server = self.fc.servers.list()[1]
return_server.metadata = {'rax_service_level_automation': 'Complete'}
stack_name = 'no_user_data'
self.patchobject(nova.NovaClientPlugin, 'find_flavor_by_name_or_id',
return_value=1)
self.patchobject(glance.GlanceClientPlugin, 'find_image_by_name_or_id',
return_value=1)
(tmpl, stack) = self._setup_test_stack(stack_name)
properties = tmpl.t['Resources']['WebServer']['Properties']
properties['user_data'] = user_data
properties['config_drive'] = config_drive
properties['user_data_format'] = ud_format
properties['software_config_transport'] = "POLL_TEMP_URL"
resource_defns = tmpl.resource_definitions(stack)
server = cloud_server.CloudServer('WebServer',
resource_defns['WebServer'], stack)
server.metadata = {'rax_service_level_automation': 'Complete'}
self.patchobject(server, 'store_external_ports')
self.patchobject(server, "_populate_deployments_metadata")
mock_servers_create = mock.Mock(return_value=return_server)
self.fc.servers.create = mock_servers_create
self.patchobject(self.fc.servers, 'get',
return_value=return_server)
scheduler.TaskRunner(server.create)()
mock_servers_create.assert_called_with(
image=mock.ANY,
flavor=mock.ANY,
key_name=mock.ANY,
name=mock.ANY,
security_groups=mock.ANY,
userdata=mock.ANY,
scheduler_hints=mock.ANY,
meta=mock.ANY,
nics=mock.ANY,
availability_zone=mock.ANY,
block_device_mapping=mock.ANY,
block_device_mapping_v2=mock.ANY,
config_drive=result,
disk_config=mock.ANY,
reservation_id=mock.ANY,
files=mock.ANY,
admin_pass=mock.ANY)
def test_server_user_data_no_config_drive(self):
self._test_server_config_drive("my script", False, True)
def test_server_user_data_config_drive(self):
self._test_server_config_drive("my script", True, True)
def test_server_no_user_data_config_drive(self):
self._test_server_config_drive(None, True, True)
def test_server_no_user_data_no_config_drive(self):
self._test_server_config_drive(None, False, False)
def test_server_no_user_data_software_config(self):
self._test_server_config_drive(None, False, True,
ud_format="SOFTWARE_CONFIG")
@mock.patch.object(resource.Resource, "client_plugin")
@mock.patch.object(resource.Resource, "client")
class CloudServersValidationTests(common.HeatTestCase):
def setUp(self):
super(CloudServersValidationTests, self).setUp()
resource._register_class("OS::Nova::Server", cloud_server.CloudServer)
properties_server = {
"image": "CentOS 5.2",
"flavor": "256 MB Server",
"key_name": "test",
"user_data": "wordpress",
}
self.mockstack = mock.Mock()
self.mockstack.has_cache_data.return_value = False
self.mockstack.db_resource_get.return_value = None
self.rsrcdef = rsrc_defn.ResourceDefinition(
"test", cloud_server.CloudServer, properties=properties_server)
def test_validate_no_image(self, mock_client, mock_plugin):
properties_server = {
"flavor": "256 MB Server",
"key_name": "test",
"user_data": "wordpress",
}
rsrcdef = rsrc_defn.ResourceDefinition(
"test", cloud_server.CloudServer, properties=properties_server)
mock_plugin().find_flavor_by_name_or_id.return_value = 1
server = cloud_server.CloudServer("test", rsrcdef, self.mockstack)
mock_boot_vol = self.patchobject(
server, '_validate_block_device_mapping')
mock_boot_vol.return_value = True
self.assertIsNone(server.validate())
def test_validate_no_image_bfv(self, mock_client, mock_plugin):
properties_server = {
"flavor": "256 MB Server",
"key_name": "test",
"user_data": "wordpress",
}
rsrcdef = rsrc_defn.ResourceDefinition(
"test", cloud_server.CloudServer, properties=properties_server)
mock_plugin().find_flavor_by_name_or_id.return_value = 1
server = cloud_server.CloudServer("test", rsrcdef, self.mockstack)
mock_boot_vol = self.patchobject(
server, '_validate_block_device_mapping')
mock_boot_vol.return_value = True
mock_flavor = mock.Mock(ram=4)
mock_flavor.to_dict.return_value = {
'OS-FLV-WITH-EXT-SPECS:extra_specs': {
'class': 'standard1',
},
}
mock_plugin().get_flavor.return_value = mock_flavor
error = self.assertRaises(
exception.StackValidationFailed, server.validate)
self.assertEqual(
'Flavor 256 MB Server cannot be booted from volume.',
six.text_type(error))
def test_validate_bfv_volume_only(self, mock_client, mock_plugin):
mock_plugin().find_flavor_by_name_or_id.return_value = 1
mock_plugin().find_image_by_name_or_id.return_value = 1
server = cloud_server.CloudServer("test", self.rsrcdef, self.mockstack)
mock_flavor = mock.Mock(ram=4, disk=4)
mock_flavor.to_dict.return_value = {
'OS-FLV-WITH-EXT-SPECS:extra_specs': {
'class': 'memory1',
},
}
mock_image = mock.Mock(status='ACTIVE', min_ram=2, min_disk=1)
mock_image.get.return_value = "memory1"
mock_image.__iter__ = mock.Mock(return_value=iter([]))
mock_plugin().get_flavor.return_value = mock_flavor
mock_plugin().get_image.return_value = mock_image
error = self.assertRaises(
exception.StackValidationFailed, server.validate)
self.assertEqual(
'Flavor 256 MB Server must be booted from volume, '
'but image CentOS 5.2 was also specified.',
six.text_type(error))
def test_validate_image_flavor_excluded_class(self, mock_client,
mock_plugin):
mock_plugin().find_flavor_by_name_or_id.return_value = 1
mock_plugin().find_image_by_name_or_id.return_value = 1
server = cloud_server.CloudServer("test", self.rsrcdef, self.mockstack)
mock_image = mock.Mock(status='ACTIVE', min_ram=2, min_disk=1)
mock_image.get.return_value = "!standard1, *"
mock_image.__iter__ = mock.Mock(return_value=iter([]))
mock_flavor = mock.Mock(ram=4, disk=4)
mock_flavor.to_dict.return_value = {
'OS-FLV-WITH-EXT-SPECS:extra_specs': {
'class': 'standard1',
},
}
mock_plugin().get_flavor.return_value = mock_flavor
mock_plugin().get_image.return_value = mock_image
error = self.assertRaises(
exception.StackValidationFailed, server.validate)
self.assertEqual(
'Flavor 256 MB Server cannot be used with image CentOS 5.2.',
six.text_type(error))
def test_validate_image_flavor_ok(self, mock_client, mock_plugin):
mock_plugin().find_flavor_by_name_or_id.return_value = 1
mock_plugin().find_image_by_name_or_id.return_value = 1
server = cloud_server.CloudServer("test", self.rsrcdef, self.mockstack)
mock_image = mock.Mock(size=1, status='ACTIVE', min_ram=2, min_disk=2)
mock_image.get.return_value = "standard1"
mock_image.__iter__ = mock.Mock(return_value=iter([]))
mock_flavor = mock.Mock(ram=4, disk=4)
mock_flavor.to_dict.return_value = {
'OS-FLV-WITH-EXT-SPECS:extra_specs': {
'class': 'standard1',
'disk_io_index': 1,
},
}
mock_plugin().get_flavor.return_value = mock_flavor
mock_plugin().get_image.return_value = mock_image
self.assertIsNone(server.validate())
def test_validate_image_flavor_empty_metadata(self, mock_client,
mock_plugin):
server = cloud_server.CloudServer("test", self.rsrcdef, self.mockstack)
mock_image = mock.Mock(size=1, status='ACTIVE', min_ram=2, min_disk=2)
mock_image.get.return_value = ""
mock_image.__iter__ = mock.Mock(return_value=iter([]))
mock_flavor = mock.Mock(ram=4, disk=4)
mock_flavor.to_dict.return_value = {
'OS-FLV-WITH-EXT-SPECS:extra_specs': {
'flavor_classes': '',
},
}
mock_plugin().get_flavor.return_value = mock_flavor
mock_plugin().get_image.return_value = mock_image
self.assertIsNone(server.validate())
def test_validate_image_flavor_no_metadata(self, mock_client, mock_plugin):
server = cloud_server.CloudServer("test", self.rsrcdef, self.mockstack)
mock_image = mock.Mock(size=1, status='ACTIVE', min_ram=2, min_disk=2)
mock_image.get.return_value = None
mock_image.__iter__ = mock.Mock(return_value=iter([]))
mock_flavor = mock.Mock(ram=4, disk=4)
mock_flavor.to_dict.return_value = {}
mock_plugin().get_flavor.return_value = mock_flavor
mock_plugin().get_image.return_value = mock_image
self.assertIsNone(server.validate())
def test_validate_image_flavor_not_base(self, mock_client, mock_plugin):
server = cloud_server.CloudServer("test", self.rsrcdef, self.mockstack)
mock_image = mock.Mock(size=1, status='ACTIVE', min_ram=2, min_disk=2)
mock_image.get.return_value = None
mock_image.__iter__ = mock.Mock(return_value=iter(
['base_image_ref']))
mock_image.__getitem__ = mock.Mock(return_value='1234')
mock_base_image = mock.Mock(size=1, status='ACTIVE', min_ram=2,
min_disk=2)
mock_base_image.get.return_value = None
mock_base_image.__iter__ = mock.Mock(return_value=iter([]))
mock_flavor = mock.Mock(ram=4, disk=4)
mock_flavor.to_dict.return_value = {}
mock_plugin().get_flavor.return_value = mock_flavor
mock_plugin().get_image.side_effect = [mock_image, mock_base_image]
self.assertIsNone(server.validate())