Register CloudServer as OS::Nova::Server
Register CloudServer as OS::Nova::Server so it waits on the RackConnect and Managed Cloud automations. Remove the extra attributes and properties from CloudServer so it is compatible with OS::Nova::Server. Change-Id: Idded2458291d8321f0ff404be360322d984595e6
This commit is contained in:
parent
095b641958
commit
158e27c538
|
@ -11,17 +11,12 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import copy
|
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
from heat.common import exception
|
from heat.common import exception
|
||||||
from heat.common.i18n import _
|
from heat.common.i18n import _
|
||||||
from heat.common.i18n import _LW
|
from heat.common.i18n import _LW
|
||||||
from heat.engine import attributes
|
|
||||||
from heat.engine import properties
|
|
||||||
from heat.engine.resources import server
|
from heat.engine.resources import server
|
||||||
from heat.engine import support
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import pyrax # noqa
|
import pyrax # noqa
|
||||||
|
@ -35,11 +30,6 @@ LOG = logging.getLogger(__name__)
|
||||||
class CloudServer(server.Server):
|
class CloudServer(server.Server):
|
||||||
"""Resource for Rackspace Cloud Servers."""
|
"""Resource for Rackspace Cloud Servers."""
|
||||||
|
|
||||||
support_status = support.SupportStatus(
|
|
||||||
support.DEPRECATED,
|
|
||||||
_('Use OS::Nova::Server instead.'),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Managed Cloud automation statuses
|
# Managed Cloud automation statuses
|
||||||
MC_STATUS_IN_PROGRESS = 'In Progress'
|
MC_STATUS_IN_PROGRESS = 'In Progress'
|
||||||
MC_STATUS_COMPLETE = 'Complete'
|
MC_STATUS_COMPLETE = 'Complete'
|
||||||
|
@ -51,78 +41,11 @@ class CloudServer(server.Server):
|
||||||
RC_STATUS_FAILED = 'FAILED'
|
RC_STATUS_FAILED = 'FAILED'
|
||||||
RC_STATUS_UNPROCESSABLE = 'UNPROCESSABLE'
|
RC_STATUS_UNPROCESSABLE = 'UNPROCESSABLE'
|
||||||
|
|
||||||
# Admin Pass Properties
|
|
||||||
SAVE_ADMIN_PASS = 'save_admin_pass'
|
|
||||||
|
|
||||||
properties_schema = copy.deepcopy(server.Server.properties_schema)
|
|
||||||
properties_schema.update(
|
|
||||||
{
|
|
||||||
SAVE_ADMIN_PASS: properties.Schema(
|
|
||||||
properties.Schema.BOOLEAN,
|
|
||||||
_('True if the system should remember the admin password; '
|
|
||||||
'False otherwise.'),
|
|
||||||
default=False
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
NEW_ATTRIBUTES = (
|
|
||||||
DISTRO, PRIVATE_IP_V4, ADMIN_PASS_ATTR,
|
|
||||||
) = (
|
|
||||||
'distro', 'privateIPv4', 'admin_pass',
|
|
||||||
)
|
|
||||||
|
|
||||||
ATTRIBUTES = copy.deepcopy(server.Server.ATTRIBUTES)
|
|
||||||
ATTRIBUTES += NEW_ATTRIBUTES
|
|
||||||
|
|
||||||
attributes_schema = copy.deepcopy(server.Server.attributes_schema)
|
|
||||||
attributes_schema.update(
|
|
||||||
{
|
|
||||||
DISTRO: attributes.Schema(
|
|
||||||
_('The Linux distribution on the server.')
|
|
||||||
),
|
|
||||||
PRIVATE_IP_V4: attributes.Schema(
|
|
||||||
_('The private IPv4 address of the server.')
|
|
||||||
),
|
|
||||||
ADMIN_PASS_ATTR: attributes.Schema(
|
|
||||||
_('The administrator password for the server.'),
|
|
||||||
cache_mode=attributes.Schema.CACHE_NONE
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, name, json_snippet, stack):
|
def __init__(self, name, json_snippet, stack):
|
||||||
super(CloudServer, self).__init__(name, json_snippet, stack)
|
super(CloudServer, self).__init__(name, json_snippet, stack)
|
||||||
self._server = None
|
|
||||||
self._distro = None
|
|
||||||
self._image = None
|
|
||||||
self._managed_cloud_started_event_sent = False
|
self._managed_cloud_started_event_sent = False
|
||||||
self._rack_connect_started_event_sent = False
|
self._rack_connect_started_event_sent = False
|
||||||
|
|
||||||
@property
|
|
||||||
def server(self):
|
|
||||||
"""Return the Cloud Server object."""
|
|
||||||
if self._server is None:
|
|
||||||
self._server = self.nova().servers.get(self.resource_id)
|
|
||||||
return self._server
|
|
||||||
|
|
||||||
@property
|
|
||||||
def distro(self):
|
|
||||||
"""Return the Linux distribution for this server."""
|
|
||||||
image = self.properties.get(self.IMAGE)
|
|
||||||
if self._distro is None and image:
|
|
||||||
image_data = self.nova().images.get(self.image)
|
|
||||||
self._distro = image_data.metadata['os_distro']
|
|
||||||
return self._distro
|
|
||||||
|
|
||||||
@property
|
|
||||||
def image(self):
|
|
||||||
"""Return the server's image ID."""
|
|
||||||
image = self.properties.get(self.IMAGE)
|
|
||||||
if image and self._image is None:
|
|
||||||
self._image = self.client_plugin('glance').get_image_id(image)
|
|
||||||
return self._image
|
|
||||||
|
|
||||||
def _config_drive(self):
|
def _config_drive(self):
|
||||||
user_data = self.properties.get(self.USER_DATA)
|
user_data = self.properties.get(self.USER_DATA)
|
||||||
config_drive = self.properties.get(self.CONFIG_DRIVE)
|
config_drive = self.properties.get(self.CONFIG_DRIVE)
|
||||||
|
@ -219,51 +142,9 @@ class CloudServer(server.Server):
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _resolve_attribute(self, name):
|
|
||||||
if name == self.DISTRO:
|
|
||||||
return self.distro
|
|
||||||
if name == self.PRIVATE_IP_V4:
|
|
||||||
return self.client_plugin().get_ip(self.server, 'private', 4)
|
|
||||||
if name == self.ADMIN_PASS_ATTR:
|
|
||||||
return self.data().get(self.ADMIN_PASS_ATTR, '')
|
|
||||||
return super(CloudServer, self)._resolve_attribute(name)
|
|
||||||
|
|
||||||
def handle_create(self):
|
|
||||||
server = super(CloudServer, self).handle_create()
|
|
||||||
|
|
||||||
# Server will not have an adminPass attribute if Nova's
|
|
||||||
# "enable_instance_password" config option is turned off
|
|
||||||
if (self.properties.get(self.SAVE_ADMIN_PASS) and
|
|
||||||
hasattr(server, 'adminPass') and server.adminPass):
|
|
||||||
self.data_set(self.ADMIN_PASS,
|
|
||||||
server.adminPass,
|
|
||||||
redact=True)
|
|
||||||
|
|
||||||
return server
|
|
||||||
|
|
||||||
def handle_check(self):
|
|
||||||
server = self._check_server_status()
|
|
||||||
checks = []
|
|
||||||
|
|
||||||
if 'rack_connect' in self.context.roles:
|
|
||||||
rc_status = self._check_rack_connect_complete(server)
|
|
||||||
checks.append(
|
|
||||||
{'attr': 'rackconnect complete', 'expected': True,
|
|
||||||
'current': rc_status}
|
|
||||||
)
|
|
||||||
|
|
||||||
if 'rax_managed' in self.context.roles:
|
|
||||||
mc_status = self._check_managed_cloud_complete(server)
|
|
||||||
checks.append(
|
|
||||||
{'attr': 'managed_cloud complete', 'expected': True,
|
|
||||||
'current': mc_status}
|
|
||||||
)
|
|
||||||
|
|
||||||
self._verify_check_conditions(checks)
|
|
||||||
|
|
||||||
|
|
||||||
def resource_mapping():
|
def resource_mapping():
|
||||||
return {'Rackspace::Cloud::Server': CloudServer}
|
return {'OS::Nova::Server': CloudServer}
|
||||||
|
|
||||||
|
|
||||||
def available_resource_mapping():
|
def available_resource_mapping():
|
||||||
|
|
|
@ -44,7 +44,7 @@ wp_template = '''
|
||||||
},
|
},
|
||||||
"Resources" : {
|
"Resources" : {
|
||||||
"WebServer": {
|
"WebServer": {
|
||||||
"Type": "Rackspace::Cloud::Server",
|
"Type": "OS::Nova::Server",
|
||||||
"Properties": {
|
"Properties": {
|
||||||
"image" : "CentOS 5.2",
|
"image" : "CentOS 5.2",
|
||||||
"flavor" : "256 MB Server",
|
"flavor" : "256 MB Server",
|
||||||
|
@ -75,7 +75,7 @@ class CloudServersTest(common.HeatTestCase):
|
||||||
# Test environment may not have pyrax client library installed and if
|
# Test environment may not have pyrax client library installed and if
|
||||||
# pyrax is not installed resource class would not be registered.
|
# pyrax is not installed resource class would not be registered.
|
||||||
# So register resource provider class explicitly for unit testing.
|
# So register resource provider class explicitly for unit testing.
|
||||||
resource._register_class("Rackspace::Cloud::Server",
|
resource._register_class("OS::Nova::Server",
|
||||||
cloud_server.CloudServer)
|
cloud_server.CloudServer)
|
||||||
|
|
||||||
def _mock_get_image_id_success(self, imageId):
|
def _mock_get_image_id_success(self, imageId):
|
||||||
|
@ -325,156 +325,6 @@ class CloudServersTest(common.HeatTestCase):
|
||||||
self.assertEqual('Error: Unknown Managed Cloud automation status: FOO',
|
self.assertEqual('Error: Unknown Managed Cloud automation status: FOO',
|
||||||
six.text_type(exc))
|
six.text_type(exc))
|
||||||
|
|
||||||
def _prepare_server_check(self):
|
|
||||||
templ, stack = self._setup_test_stack('server_check')
|
|
||||||
server = self.fc.servers.list()[1]
|
|
||||||
res = stack['WebServer']
|
|
||||||
res.nova = mock.Mock()
|
|
||||||
res.nova().servers.get = mock.Mock(return_value=server)
|
|
||||||
return res
|
|
||||||
|
|
||||||
def test_check_rackconnect(self):
|
|
||||||
res = self._prepare_server_check()
|
|
||||||
res._check_rack_connect_complete = mock.Mock(return_value=True)
|
|
||||||
self.ctx.roles = ['rack_connect']
|
|
||||||
|
|
||||||
scheduler.TaskRunner(res.check)()
|
|
||||||
self.assertEqual((res.CHECK, res.COMPLETE), res.state)
|
|
||||||
|
|
||||||
def test_check_rackconnect_failure(self):
|
|
||||||
self.ctx.roles = ['rack_connect']
|
|
||||||
res = self._prepare_server_check()
|
|
||||||
res._check_active = mock.Mock(return_value=True)
|
|
||||||
res._check_rack_connect_complete = mock.Mock(return_value=False)
|
|
||||||
|
|
||||||
exc = self.assertRaises(exception.ResourceFailure,
|
|
||||||
scheduler.TaskRunner(res.check))
|
|
||||||
self.assertIn('False', six.text_type(exc))
|
|
||||||
self.assertEqual((res.CHECK, res.FAILED), res.state)
|
|
||||||
|
|
||||||
def test_check_managed_cloud(self):
|
|
||||||
res = self._prepare_server_check()
|
|
||||||
res._check_managed_cloud_complete = mock.Mock(return_value=True)
|
|
||||||
self.ctx.roles = ['rax_managed']
|
|
||||||
|
|
||||||
scheduler.TaskRunner(res.check)()
|
|
||||||
self.assertEqual((res.CHECK, res.COMPLETE), res.state)
|
|
||||||
|
|
||||||
def test_check_managed_cloud_failure(self):
|
|
||||||
res = self._prepare_server_check()
|
|
||||||
res._check_managed_cloud_complete = mock.Mock(return_value=False)
|
|
||||||
self.ctx.roles = ['rax_managed']
|
|
||||||
|
|
||||||
exc = self.assertRaises(exception.ResourceFailure,
|
|
||||||
scheduler.TaskRunner(res.check))
|
|
||||||
self.assertIn('False', six.text_type(exc))
|
|
||||||
self.assertEqual((res.CHECK, res.FAILED), res.state)
|
|
||||||
|
|
||||||
@mock.patch.object(resource.Resource, 'data_set')
|
|
||||||
def test_create_store_admin_pass_resource_data(self,
|
|
||||||
mock_data_set):
|
|
||||||
self._mock_metadata_os_distro()
|
|
||||||
return_server = self.fc.servers.list()[1]
|
|
||||||
return_server.adminPass = 'autogenerated'
|
|
||||||
stack_name = 'admin_pass_s'
|
|
||||||
(t, stack) = self._setup_test_stack(stack_name)
|
|
||||||
|
|
||||||
t.t['Resources']['WebServer']['Properties']['save_admin_pass'] = True
|
|
||||||
resource_defns = t.resource_definitions(stack)
|
|
||||||
server = cloud_server.CloudServer('WebServer',
|
|
||||||
resource_defns['WebServer'], stack)
|
|
||||||
|
|
||||||
self.fc.servers.create = mock.Mock(return_value=return_server)
|
|
||||||
self._mock_get_image_id_success('image_id')
|
|
||||||
scheduler.TaskRunner(server.create)()
|
|
||||||
expected_call = mock.call(server.ADMIN_PASS,
|
|
||||||
'autogenerated', redact=True)
|
|
||||||
self.assertIn(expected_call, mock_data_set.call_args_list)
|
|
||||||
|
|
||||||
@mock.patch.object(resource.Resource, 'data_set')
|
|
||||||
def test_create_save_admin_pass_is_false(self, mock_data_set):
|
|
||||||
self._mock_metadata_os_distro()
|
|
||||||
return_server = self.fc.servers.list()[1]
|
|
||||||
return_server.adminPass = 'autogenerated'
|
|
||||||
stack_name = 'admin_pass_s'
|
|
||||||
(t, stack) = self._setup_test_stack(stack_name)
|
|
||||||
|
|
||||||
t.t['Resources']['WebServer']['Properties']['save_admin_pass'] = False
|
|
||||||
resource_defns = t.resource_definitions(stack)
|
|
||||||
server = cloud_server.CloudServer('WebServer',
|
|
||||||
resource_defns['WebServer'], stack)
|
|
||||||
|
|
||||||
self.fc.servers.create = mock.Mock(return_value=return_server)
|
|
||||||
self._mock_get_image_id_success('image_id')
|
|
||||||
scheduler.TaskRunner(server.create)()
|
|
||||||
expected_call = mock.call(mock.ANY, server.ADMIN_PASS,
|
|
||||||
mock.ANY, mock.ANY)
|
|
||||||
self.assertNotIn(expected_call, mock_data_set.call_args_list)
|
|
||||||
|
|
||||||
@mock.patch.object(resource.Resource, 'data_set')
|
|
||||||
def test_create_save_admin_pass_defaults_to_false(self,
|
|
||||||
mock_data_set):
|
|
||||||
self._mock_metadata_os_distro()
|
|
||||||
return_server = self.fc.servers.list()[1]
|
|
||||||
return_server.adminPass = 'autogenerated'
|
|
||||||
stack_name = 'admin_pass_s'
|
|
||||||
(t, stack) = self._setup_test_stack(stack_name)
|
|
||||||
|
|
||||||
t.t['Resources']['WebServer']['Properties']['save_admin_pass'] = None
|
|
||||||
resource_defns = t.resource_definitions(stack)
|
|
||||||
server = cloud_server.CloudServer('WebServer',
|
|
||||||
resource_defns['WebServer'], stack)
|
|
||||||
|
|
||||||
self.fc.servers.create = mock.Mock(return_value=return_server)
|
|
||||||
self._mock_get_image_id_success('image_id')
|
|
||||||
|
|
||||||
scheduler.TaskRunner(server.create)()
|
|
||||||
expected_call = mock.call(mock.ANY, server.ADMIN_PASS,
|
|
||||||
mock.ANY, mock.ANY)
|
|
||||||
self.assertNotIn(expected_call, mock_data_set.call_args_list)
|
|
||||||
|
|
||||||
@mock.patch.object(resource.Resource, 'data_set')
|
|
||||||
def test_create_without_adminPass_attribute(self,
|
|
||||||
mock_data_set):
|
|
||||||
self._mock_metadata_os_distro()
|
|
||||||
return_server = self.fc.servers.list()[1]
|
|
||||||
stack_name = 'admin_pass_s'
|
|
||||||
(tmpl, stack) = self._setup_test_stack(stack_name)
|
|
||||||
|
|
||||||
resource_defns = tmpl.resource_definitions(stack)
|
|
||||||
server = cloud_server.CloudServer('WebServer',
|
|
||||||
resource_defns['WebServer'], stack)
|
|
||||||
|
|
||||||
self.fc.servers.create = mock.Mock(return_value=return_server)
|
|
||||||
self._mock_get_image_id_success('image_id')
|
|
||||||
|
|
||||||
scheduler.TaskRunner(server.create)()
|
|
||||||
expected_call = mock.call(mock.ANY, server.ADMIN_PASS,
|
|
||||||
mock.ANY, redact=mock.ANY)
|
|
||||||
self.assertNotIn(expected_call, mock_data_set.call_args_list)
|
|
||||||
|
|
||||||
@mock.patch.object(resource.Resource, 'data')
|
|
||||||
def test_server_handles_server_without_password(self, mock_data_get):
|
|
||||||
mock_data_get.return_value = {}
|
|
||||||
stack_name = 'admin_pass_s'
|
|
||||||
(tmpl, stack) = self._setup_test_stack(stack_name)
|
|
||||||
|
|
||||||
resource_defns = tmpl.resource_definitions(stack)
|
|
||||||
server = cloud_server.CloudServer('WebServer',
|
|
||||||
resource_defns['WebServer'], stack)
|
|
||||||
self.assertEqual('', server.FnGetAtt('admin_pass'))
|
|
||||||
|
|
||||||
@mock.patch.object(resource.Resource, 'data')
|
|
||||||
def test_server_has_admin_pass_attribute_available(self, mock_data_get):
|
|
||||||
mock_data_get.return_value = {'admin_pass': 'foo'}
|
|
||||||
stack_name = 'admin_pass_s'
|
|
||||||
(tmpl, stack) = self._setup_test_stack(stack_name)
|
|
||||||
|
|
||||||
resource_defns = tmpl.resource_definitions(stack)
|
|
||||||
server = cloud_server.CloudServer('WebServer',
|
|
||||||
resource_defns['WebServer'], stack)
|
|
||||||
self.assertEqual('foo', server.FnGetAtt('admin_pass'))
|
|
||||||
|
|
||||||
def _test_server_config_drive(self, user_data, config_drive, result):
|
def _test_server_config_drive(self, user_data, config_drive, result):
|
||||||
return_server = self.fc.servers.list()[1]
|
return_server = self.fc.servers.list()[1]
|
||||||
stack_name = 'no_user_data'
|
stack_name = 'no_user_data'
|
||||||
|
|
Loading…
Reference in New Issue