Don't use SSH in Rackspace::Cloud::Server
Rackspace Cloud Servers now supports config drive and the Rackspace images have cloud-init installed by default, so we no longer have to SSH to the Cloud Server to install dependencies and run the user-data script. Closes-Bug: #1298050 Change-Id: I5cb6a93c5b34b0a8cfe7480211da70d65638de54
This commit is contained in:
parent
d477d3566b
commit
285f2cbc28
@ -12,11 +12,6 @@
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
import socket
|
||||
import tempfile
|
||||
|
||||
from Crypto.PublicKey import RSA
|
||||
import paramiko
|
||||
|
||||
from heat.common import exception
|
||||
from heat.engine import properties
|
||||
@ -37,72 +32,6 @@ logger = logging.getLogger(__name__)
|
||||
class CloudServer(server.Server):
|
||||
"""Resource for Rackspace Cloud Servers."""
|
||||
|
||||
SCRIPT_INSTALL_REQUIREMENTS = {
|
||||
'ubuntu': """
|
||||
apt-get update
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get install -y -o Dpkg::Options::="--force-confdef" -o \
|
||||
Dpkg::Options::="--force-confold" python-boto python-pip gcc python-dev
|
||||
pip install heat-cfntools
|
||||
cfn-create-aws-symlinks --source /usr/local/bin
|
||||
""",
|
||||
'fedora': """
|
||||
yum install -y python-boto python-pip gcc python-devel
|
||||
pip-python install heat-cfntools
|
||||
cfn-create-aws-symlinks
|
||||
""",
|
||||
'centos': """
|
||||
if ! (yum repolist 2> /dev/null | egrep -q "^[\!\*]?epel ");
|
||||
then
|
||||
rpm -ivh http://mirror.rackspace.com/epel/6/i386/epel-release-6-8.noarch.rpm
|
||||
fi
|
||||
yum install -y python-boto python-pip gcc python-devel python-argparse
|
||||
pip-python install heat-cfntools
|
||||
""",
|
||||
'rhel': """
|
||||
if ! (yum repolist 2> /dev/null | egrep -q "^[\!\*]?epel ");
|
||||
then
|
||||
rpm -ivh http://mirror.rackspace.com/epel/6/i386/epel-release-6-8.noarch.rpm
|
||||
fi
|
||||
# The RPM DB stays locked for a few secs
|
||||
while fuser /var/lib/rpm/*; do sleep 1; done
|
||||
yum install -y python-boto python-pip gcc python-devel python-argparse
|
||||
pip-python install heat-cfntools
|
||||
cfn-create-aws-symlinks
|
||||
""",
|
||||
'debian': """
|
||||
echo "deb http://mirror.rackspace.com/debian wheezy-backports main" >> \
|
||||
/etc/apt/sources.list
|
||||
apt-get update
|
||||
apt-get -t wheezy-backports install -y cloud-init
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get install -y -o Dpkg::Options::="--force-confdef" -o \
|
||||
Dpkg::Options::="--force-confold" python-pip gcc python-dev
|
||||
pip install heat-cfntools
|
||||
"""}
|
||||
|
||||
SCRIPT_CREATE_DATA_SOURCE = """
|
||||
sed -i 's/ConfigDrive/NoCloud/' /etc/cloud/cloud.cfg.d/*
|
||||
rm -rf /var/lib/cloud
|
||||
mkdir -p /var/lib/cloud/seed/nocloud-net
|
||||
mv /tmp/userdata /var/lib/cloud/seed/nocloud-net/user-data
|
||||
touch /var/lib/cloud/seed/nocloud-net/meta-data
|
||||
chmod 600 /var/lib/cloud/seed/nocloud-net/*
|
||||
"""
|
||||
|
||||
SCRIPT_RUN_CLOUD_INIT = """
|
||||
cloud-init start || cloud-init init
|
||||
"""
|
||||
|
||||
SCRIPT_RUN_CFN_USERDATA = """
|
||||
bash -x /var/lib/cloud/data/cfn-userdata > /root/cfn-userdata.log 2>&1 ||
|
||||
exit 42
|
||||
"""
|
||||
|
||||
SCRIPT_ERROR_MSG = _("The %(path)s script exited with a non-zero exit "
|
||||
"status. To see the error message, log into the "
|
||||
"server at %(ip)s and view %(log)s")
|
||||
|
||||
# Managed Cloud automation statuses
|
||||
MC_STATUS_IN_PROGRESS = 'In Progress'
|
||||
MC_STATUS_COMPLETE = 'Complete'
|
||||
@ -139,11 +68,9 @@ bash -x /var/lib/cloud/data/cfn-userdata > /root/cfn-userdata.log 2>&1 ||
|
||||
|
||||
def __init__(self, name, json_snippet, stack):
|
||||
super(CloudServer, self).__init__(name, json_snippet, stack)
|
||||
self.stack = stack
|
||||
self._server = None
|
||||
self._distro = None
|
||||
self._image = None
|
||||
self._retry_iterations = 0
|
||||
self._managed_cloud_started_event_sent = False
|
||||
self._rack_connect_started_event_sent = False
|
||||
|
||||
@ -163,26 +90,6 @@ bash -x /var/lib/cloud/data/cfn-userdata > /root/cfn-userdata.log 2>&1 ||
|
||||
self._distro = image_data.metadata['os_distro']
|
||||
return self._distro
|
||||
|
||||
@property
|
||||
def script(self):
|
||||
"""
|
||||
Return the config script for the Cloud Server image.
|
||||
|
||||
The config script performs the following steps:
|
||||
1) Install cloud-init
|
||||
2) Create cloud-init data source
|
||||
3) Run cloud-init
|
||||
4) If user_data_format is 'HEAT_CFNTOOLS', run cfn-userdata script
|
||||
"""
|
||||
base_script = (self.SCRIPT_INSTALL_REQUIREMENTS[self.distro] +
|
||||
self.SCRIPT_CREATE_DATA_SOURCE +
|
||||
self.SCRIPT_RUN_CLOUD_INIT)
|
||||
userdata_format = self.properties.get(self.USER_DATA_FORMAT)
|
||||
if userdata_format == 'HEAT_CFNTOOLS':
|
||||
return base_script + self.SCRIPT_RUN_CFN_USERDATA
|
||||
elif userdata_format == 'RAW':
|
||||
return base_script
|
||||
|
||||
@property
|
||||
def image(self):
|
||||
"""Return the server's image ID."""
|
||||
@ -191,152 +98,14 @@ bash -x /var/lib/cloud/data/cfn-userdata > /root/cfn-userdata.log 2>&1 ||
|
||||
self._image = nova_utils.get_image_id(self.nova(), image)
|
||||
return self._image
|
||||
|
||||
@property
|
||||
def private_key(self):
|
||||
"""Return the private SSH key for the resource."""
|
||||
return self.data().get('private_key')
|
||||
|
||||
@private_key.setter
|
||||
def private_key(self, private_key):
|
||||
"""Save the resource's private SSH key to the database."""
|
||||
if self.id is not None:
|
||||
self.data_set('private_key', private_key, True)
|
||||
|
||||
@property
|
||||
def has_userdata(self):
|
||||
"""Return True if the server has user_data, False otherwise."""
|
||||
def _config_drive(self):
|
||||
user_data = self.properties.get(self.USER_DATA)
|
||||
if user_data or self.metadata != {}:
|
||||
config_drive = self.properties.get(self.CONFIG_DRIVE)
|
||||
if user_data or config_drive:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def validate(self):
|
||||
"""Validate user parameters."""
|
||||
image = self.properties.get(self.IMAGE)
|
||||
|
||||
# It's okay if there's no script, as long as user_data and
|
||||
# metadata are both empty
|
||||
if image and self.script is None and self.has_userdata:
|
||||
msg = _("user_data is not supported for image %s.") % image
|
||||
raise exception.StackValidationFailed(message=msg)
|
||||
|
||||
# Validate that the personality does not contain a reserved
|
||||
# key and that the number of personalities does not exceed the
|
||||
# Rackspace limit.
|
||||
personality = self.properties.get(self.PERSONALITY)
|
||||
if personality:
|
||||
limits = nova_utils.absolute_limits(self.nova())
|
||||
|
||||
# One personality will be used for an SSH key
|
||||
personality_limit = limits['maxPersonality'] - 1
|
||||
|
||||
if "/root/.ssh/authorized_keys" in personality:
|
||||
msg = _('The personality property may not contain a key '
|
||||
'of "/root/.ssh/authorized_keys"')
|
||||
raise exception.StackValidationFailed(message=msg)
|
||||
|
||||
elif len(personality) > personality_limit:
|
||||
msg = _("The personality property may not contain greater "
|
||||
"than %s entries.") % personality_limit
|
||||
raise exception.StackValidationFailed(message=msg)
|
||||
|
||||
super(CloudServer, self).validate()
|
||||
|
||||
# Validate that user_data is passed for servers with bootable
|
||||
# volumes AFTER validating that the server has either an image
|
||||
# or a bootable volume in Server.validate()
|
||||
if not image and self.has_userdata:
|
||||
msg = _("user_data scripts are not supported with bootable "
|
||||
"volumes.")
|
||||
raise exception.StackValidationFailed(message=msg)
|
||||
|
||||
def _run_ssh_command(self, command):
|
||||
"""Run a shell command on the Cloud Server via SSH."""
|
||||
with tempfile.NamedTemporaryFile() as private_key_file:
|
||||
private_key_file.write(self.private_key)
|
||||
private_key_file.seek(0)
|
||||
ssh = paramiko.SSHClient()
|
||||
ssh.set_missing_host_key_policy(paramiko.MissingHostKeyPolicy())
|
||||
ssh.connect(self.server.accessIPv4,
|
||||
username="root",
|
||||
key_filename=private_key_file.name)
|
||||
chan = ssh.get_transport().open_session()
|
||||
chan.settimeout(self.stack.timeout_secs())
|
||||
chan.exec_command(command)
|
||||
try:
|
||||
# The channel timeout only works for read/write operations
|
||||
chan.recv(1024)
|
||||
except socket.timeout:
|
||||
raise exception.Error(
|
||||
_("SSH command timed out after %s seconds") %
|
||||
self.stack.timeout_secs())
|
||||
else:
|
||||
return chan.recv_exit_status()
|
||||
finally:
|
||||
ssh.close()
|
||||
chan.close()
|
||||
|
||||
def _sftp_files(self, files):
|
||||
"""Transfer files to the Cloud Server via SFTP."""
|
||||
|
||||
if self._retry_iterations > 30:
|
||||
raise exception.Error(_("Failed to establish SSH connection after "
|
||||
"30 tries"))
|
||||
self._retry_iterations += 1
|
||||
|
||||
try:
|
||||
transport = paramiko.Transport((self.server.accessIPv4, 22))
|
||||
except paramiko.SSHException:
|
||||
logger.debug("Failed to get SSH transport, will retry")
|
||||
return False
|
||||
with tempfile.NamedTemporaryFile() as private_key_file:
|
||||
private_key_file.write(self.private_key)
|
||||
private_key_file.seek(0)
|
||||
pkey = paramiko.RSAKey.from_private_key_file(private_key_file.name)
|
||||
try:
|
||||
transport.connect(hostkey=None, username="root", pkey=pkey)
|
||||
except EOFError:
|
||||
logger.debug("Failed to connect to SSH transport, will retry")
|
||||
return False
|
||||
sftp = paramiko.SFTPClient.from_transport(transport)
|
||||
try:
|
||||
for remote_file in files:
|
||||
sftp_file = sftp.open(remote_file['path'], 'w')
|
||||
sftp_file.write(remote_file['data'])
|
||||
sftp_file.close()
|
||||
except:
|
||||
raise
|
||||
finally:
|
||||
sftp.close()
|
||||
transport.close()
|
||||
|
||||
def _personality(self):
|
||||
# Generate SSH public/private keypair for the engine to use
|
||||
if self.private_key is not None:
|
||||
rsa = RSA.importKey(self.private_key)
|
||||
else:
|
||||
rsa = RSA.generate(1024)
|
||||
self.private_key = rsa.exportKey()
|
||||
public_keys = [rsa.publickey().exportKey('OpenSSH')]
|
||||
|
||||
# Add the user-provided key_name to the authorized_keys file
|
||||
key_name = self.properties.get(self.KEY_NAME)
|
||||
if key_name:
|
||||
user_keypair = nova_utils.get_keypair(self.nova(), key_name)
|
||||
public_keys.append(user_keypair.public_key)
|
||||
personality = {"/root/.ssh/authorized_keys": '\n'.join(public_keys)}
|
||||
|
||||
# Add any user-provided personality files
|
||||
user_personality = self.properties.get(self.PERSONALITY)
|
||||
if user_personality:
|
||||
personality.update(user_personality)
|
||||
|
||||
return personality
|
||||
|
||||
def _key_name(self):
|
||||
return None
|
||||
|
||||
def _check_managed_cloud_complete(self, server):
|
||||
if not self._managed_cloud_started_event_sent:
|
||||
msg = _("Waiting for Managed Cloud automation to complete")
|
||||
@ -409,36 +178,6 @@ bash -x /var/lib/cloud/data/cfn-userdata > /root/cfn-userdata.log 2>&1 ||
|
||||
msg = _("Unknown RackConnect automation status: %s") % rc_status
|
||||
raise exception.Error(msg)
|
||||
|
||||
def _run_userdata(self):
|
||||
msg = _("Running user_data")
|
||||
self._add_event(self.action, self.status, msg)
|
||||
|
||||
# Create heat-script and userdata files on server
|
||||
raw_userdata = self.properties[self.USER_DATA]
|
||||
userdata = nova_utils.build_userdata(self, raw_userdata)
|
||||
|
||||
files = [{'path': "/tmp/userdata", 'data': userdata},
|
||||
{'path': "/root/heat-script.sh", 'data': self.script}]
|
||||
if self._sftp_files(files) is False:
|
||||
return False
|
||||
|
||||
# Connect via SSH and run script
|
||||
cmd = "bash -ex /root/heat-script.sh > /root/heat-script.log 2>&1"
|
||||
exit_code = self._run_ssh_command(cmd)
|
||||
if exit_code == 42:
|
||||
raise exception.Error(self.SCRIPT_ERROR_MSG %
|
||||
{'path': "cfn-userdata",
|
||||
'ip': self.server.accessIPv4,
|
||||
'log': "/root/cfn-userdata.log"})
|
||||
elif exit_code != 0:
|
||||
raise exception.Error(self.SCRIPT_ERROR_MSG %
|
||||
{'path': "heat-script.sh",
|
||||
'ip': self.server.accessIPv4,
|
||||
'log': "/root/heat-script.log"})
|
||||
|
||||
msg = _("Successfully ran user_data")
|
||||
self._add_event(self.action, self.status, msg)
|
||||
|
||||
def check_create_complete(self, server):
|
||||
"""Check if server creation is complete and handle server configs."""
|
||||
if not self._check_active(server):
|
||||
@ -454,10 +193,6 @@ bash -x /var/lib/cloud/data/cfn-userdata > /root/cfn-userdata.log 2>&1 ||
|
||||
self._check_managed_cloud_complete(server):
|
||||
return False
|
||||
|
||||
if self.has_userdata:
|
||||
if self._run_userdata() is False:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _resolve_attribute(self, name):
|
||||
|
@ -13,16 +13,13 @@
|
||||
|
||||
import mock
|
||||
import mox
|
||||
import paramiko
|
||||
|
||||
from heat.common import exception
|
||||
from heat.common import template_format
|
||||
from heat.db import api as db_api
|
||||
from heat.engine import clients
|
||||
from heat.engine import environment
|
||||
from heat.engine import parser
|
||||
from heat.engine import resource
|
||||
from heat.engine.resources import image
|
||||
from heat.engine import scheduler
|
||||
from heat.openstack.common import uuidutils
|
||||
from heat.tests.common import HeatTestCase
|
||||
@ -57,23 +54,6 @@ wp_template = '''
|
||||
}
|
||||
'''
|
||||
|
||||
rsa_key = """-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICWwIBAAKBgQDibWGom/83F2xYfVylBZhUbREiVlw42X7afUuHzNJuh/5EyhXQ
|
||||
BmBHjVGL1mxZY4GoISrxIkW1jVmTXbm8FknIlS3jxEOC+xF3IkLBtmZEkFVLOUCv
|
||||
Fpru1xThFS0L/pRttiTWLm+dsjboCV4qtg/+y30O0RJ5AAFgGkoVs8idrQIDAQAB
|
||||
AoGAQU/7037r5yBCiGPgzVkHz5KGVrlCcMOL68ood0uFh4yCs6T3FcJBE2KYGxYG
|
||||
uuIRDEZE9LlGElBrfi6S3MYxEbewITK9Li1cr8K0fJlIbg5PI1MxwiTXzG7i0f8Y
|
||||
trtZjo/fs8XNSS4xlGWCUgtiNXvLS6wxyDGGbqeh1BmETgECQQDmoPJ3h5kuZguA
|
||||
o7B+iTaKXqyWPf0ImsZ0UQYBgnEWTaZEh8W0015jP55mndALWA9pmhHJm+BC/Hfe
|
||||
Kp6jtVyxAkEA+1YctDe62u5pXU/GK8UfDJwi4m1VxUfASrlxh+ALag9knwe6Dlev
|
||||
EKKIe8R6HZs2zavaJs6dddxHRcIi8rXfvQJAW6octOVwPMDSUY69140x4E1Ay3ZX
|
||||
29OojRKnEHKIABVcwGA2dGiOW2Qt0RtoVRnrBk32Q+twdy9hdSv7YZX0AQJAVDaj
|
||||
QYNW2Zp+tWRQa0QORkRer+2gioyjEqaWMsfQK0ZjGaIWJk4c+37qKkZIAHmMYFeP
|
||||
recW/XHEc8w7t4VXJQJAevSyciBfFcWMZTwlqq8wXNMCRLJt5CxvO4gSO+hPNrDe
|
||||
gDZkz7KcZC7TkO0NYVRssA6/84mCqx6QHpKaYNG9kg==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
"""
|
||||
|
||||
|
||||
class CloudServersTest(HeatTestCase):
|
||||
def setUp(self):
|
||||
@ -85,44 +65,6 @@ class CloudServersTest(HeatTestCase):
|
||||
resource._register_class("Rackspace::Cloud::Server",
|
||||
cloud_server.CloudServer)
|
||||
|
||||
def _mock_ssh_sftp(self, exit_code=0):
|
||||
# SSH
|
||||
self.m.StubOutWithMock(paramiko, "SSHClient")
|
||||
self.m.StubOutWithMock(paramiko, "MissingHostKeyPolicy")
|
||||
ssh = self.m.CreateMockAnything()
|
||||
paramiko.SSHClient().AndReturn(ssh)
|
||||
paramiko.MissingHostKeyPolicy()
|
||||
ssh.set_missing_host_key_policy(None)
|
||||
ssh.connect(mox.IgnoreArg(),
|
||||
key_filename=mox.IgnoreArg(),
|
||||
username='root')
|
||||
fake_chan = self.m.CreateMockAnything()
|
||||
self.m.StubOutWithMock(paramiko.SSHClient, "get_transport")
|
||||
chan = ssh.get_transport().AndReturn(fake_chan)
|
||||
fake_chan_session = self.m.CreateMockAnything()
|
||||
chan_session = chan.open_session().AndReturn(fake_chan_session)
|
||||
fake_chan_session.settimeout(3600.0)
|
||||
chan_session.exec_command(mox.IgnoreArg())
|
||||
fake_chan_session.recv(1024)
|
||||
chan_session.recv_exit_status().AndReturn(exit_code)
|
||||
fake_chan_session.close()
|
||||
ssh.close()
|
||||
|
||||
# SFTP
|
||||
self.m.StubOutWithMock(paramiko, "Transport")
|
||||
transport = self.m.CreateMockAnything()
|
||||
paramiko.Transport((mox.IgnoreArg(), 22)).AndReturn(transport)
|
||||
transport.connect(hostkey=None, username="root", pkey=mox.IgnoreArg())
|
||||
sftp = self.m.CreateMockAnything()
|
||||
self.m.StubOutWithMock(paramiko, "SFTPClient")
|
||||
paramiko.SFTPClient.from_transport(transport).AndReturn(sftp)
|
||||
sftp_file = self.m.CreateMockAnything()
|
||||
sftp.open(mox.IgnoreArg(), 'w').MultipleTimes().AndReturn(sftp_file)
|
||||
sftp_file.write(mox.IgnoreArg()).MultipleTimes()
|
||||
sftp_file.close().MultipleTimes()
|
||||
sftp.close()
|
||||
transport.close()
|
||||
|
||||
def _setup_test_stack(self, stack_name):
|
||||
t = template_format.parse(wp_template)
|
||||
template = parser.Template(t)
|
||||
@ -154,14 +96,14 @@ class CloudServersTest(HeatTestCase):
|
||||
self.m.StubOutWithMock(clients.OpenStackClients, 'nova')
|
||||
clients.OpenStackClients.nova().MultipleTimes().AndReturn(self.fc)
|
||||
|
||||
server._private_key = rsa_key
|
||||
server.t = server.stack.resolve_runtime_data(server.t)
|
||||
|
||||
if stub_create:
|
||||
self.m.StubOutWithMock(self.fc.servers, 'create')
|
||||
self.fc.servers.create(
|
||||
image=1,
|
||||
flavor=1,
|
||||
key_name=None,
|
||||
key_name='test',
|
||||
name=override_name and server.name or utils.PhysName(
|
||||
stack_name, server.name),
|
||||
security_groups=[],
|
||||
@ -171,16 +113,12 @@ class CloudServersTest(HeatTestCase):
|
||||
nics=None,
|
||||
availability_zone=None,
|
||||
block_device_mapping=None,
|
||||
config_drive=None,
|
||||
config_drive=True,
|
||||
disk_config=None,
|
||||
reservation_id=None,
|
||||
files=mox.IgnoreArg(),
|
||||
admin_pass=None).AndReturn(return_server)
|
||||
|
||||
self.m.StubOutWithMock(cloud_server.CloudServer, 'script')
|
||||
cloud_server.CloudServer.script = "foobar"
|
||||
|
||||
self._mock_ssh_sftp(exit_code)
|
||||
return server
|
||||
|
||||
def _create_test_server(self, return_server, name, override_name=False,
|
||||
@ -193,7 +131,6 @@ class CloudServersTest(HeatTestCase):
|
||||
return server
|
||||
|
||||
def _update_test_server(self, return_server, name, exit_code=0):
|
||||
self._mock_ssh_sftp(exit_code)
|
||||
self.m.StubOutWithMock(cloud_server.CloudServer, "nova")
|
||||
cloud_server.CloudServer.nova().MultipleTimes().AndReturn(self.fc)
|
||||
|
||||
@ -201,229 +138,6 @@ class CloudServersTest(HeatTestCase):
|
||||
image_data = mock.Mock(metadata={'os_distro': 'centos'})
|
||||
self.fc.images.get = mock.Mock(return_value=image_data)
|
||||
|
||||
def test_script_raw_userdata(self):
|
||||
stack_name = 'raw_userdata_s'
|
||||
(t, stack) = self._setup_test_stack(stack_name)
|
||||
|
||||
t['Resources']['WebServer']['Properties']['user_data_format'] = \
|
||||
'RAW'
|
||||
|
||||
server = cloud_server.CloudServer('WebServer',
|
||||
t['Resources']['WebServer'], stack)
|
||||
|
||||
self.m.StubOutWithMock(server, 'nova')
|
||||
server.nova().MultipleTimes().AndReturn(self.fc)
|
||||
self.m.StubOutWithMock(clients.OpenStackClients, "nova")
|
||||
clients.OpenStackClients.nova().MultipleTimes().AndReturn(self.fc)
|
||||
self._mock_metadata_os_distro()
|
||||
self.m.ReplayAll()
|
||||
|
||||
self.assertNotIn("/var/lib/cloud/data/cfn-userdata", server.script)
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_script_cfntools_userdata(self):
|
||||
stack_name = 'raw_userdata_s'
|
||||
(t, stack) = self._setup_test_stack(stack_name)
|
||||
|
||||
t['Resources']['WebServer']['Properties']['user_data_format'] = \
|
||||
'HEAT_CFNTOOLS'
|
||||
|
||||
server = cloud_server.CloudServer('WebServer',
|
||||
t['Resources']['WebServer'], stack)
|
||||
|
||||
self.m.StubOutWithMock(server, 'nova')
|
||||
server.nova().MultipleTimes().AndReturn(self.fc)
|
||||
self.m.StubOutWithMock(clients.OpenStackClients, "nova")
|
||||
clients.OpenStackClients.nova().MultipleTimes().AndReturn(self.fc)
|
||||
self._mock_metadata_os_distro()
|
||||
self.m.ReplayAll()
|
||||
|
||||
self.assertIn("/var/lib/cloud/data/cfn-userdata", server.script)
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_validate_no_script_okay(self):
|
||||
stack_name = 'srv_val'
|
||||
(t, stack) = self._setup_test_stack(stack_name)
|
||||
|
||||
# create an server with non exist image Id
|
||||
t['Resources']['WebServer']['Properties']['image'] = '1'
|
||||
server = cloud_server.CloudServer('server_create_image_err',
|
||||
t['Resources']['WebServer'], stack)
|
||||
|
||||
self.m.StubOutWithMock(server, 'nova')
|
||||
server.nova().MultipleTimes().AndReturn(self.fc)
|
||||
self.m.StubOutWithMock(clients.OpenStackClients, 'nova')
|
||||
clients.OpenStackClients.nova().MultipleTimes().AndReturn(self.fc)
|
||||
|
||||
self.m.StubOutWithMock(server.__class__, 'script')
|
||||
server.script = None
|
||||
|
||||
self.m.StubOutWithMock(server.__class__, 'has_userdata')
|
||||
server.has_userdata = False
|
||||
|
||||
self.m.StubOutWithMock(uuidutils, "is_uuid_like")
|
||||
uuidutils.is_uuid_like('1').MultipleTimes().AndReturn(True)
|
||||
self.m.ReplayAll()
|
||||
|
||||
self.assertIsNone(server.validate())
|
||||
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_validate_disallowed_personality(self):
|
||||
stack_name = 'srv_val'
|
||||
(t, stack) = self._setup_test_stack(stack_name)
|
||||
|
||||
# create an server with non exist image Id
|
||||
t['Resources']['WebServer']['Properties']['personality'] = \
|
||||
{"/fake/path1": "fake contents1",
|
||||
"/fake/path2": "fake_contents2",
|
||||
"/fake/path3": "fake_contents3",
|
||||
"/root/.ssh/authorized_keys": "fake_contents4"}
|
||||
server = cloud_server.CloudServer('server_create_image_err',
|
||||
t['Resources']['WebServer'], stack)
|
||||
|
||||
self.m.StubOutWithMock(server.__class__, 'script')
|
||||
server.script = None
|
||||
|
||||
self.m.StubOutWithMock(server.__class__, 'has_userdata')
|
||||
server.has_userdata = False
|
||||
|
||||
self.m.StubOutWithMock(server, 'nova')
|
||||
server.nova().MultipleTimes().AndReturn(self.fc)
|
||||
self.m.StubOutWithMock(clients.OpenStackClients, "nova")
|
||||
clients.OpenStackClients.nova().MultipleTimes().AndReturn(self.fc)
|
||||
self.m.ReplayAll()
|
||||
|
||||
exc = self.assertRaises(exception.StackValidationFailed,
|
||||
server.validate)
|
||||
self.assertEqual("The personality property may not contain a "
|
||||
"key of \"/root/.ssh/authorized_keys\"", str(exc))
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_user_personality(self):
|
||||
return_server = self.fc.servers.list()[1]
|
||||
stack_name = 'srv_val'
|
||||
(t, stack) = self._setup_test_stack(stack_name)
|
||||
|
||||
# create an server with non exist image Id
|
||||
t['Resources']['WebServer']['Properties']['personality'] = \
|
||||
{"/fake/path1": "fake contents1",
|
||||
"/fake/path2": "fake_contents2",
|
||||
"/fake/path3": "fake_contents3"}
|
||||
server = cloud_server.CloudServer('server_create_image_err',
|
||||
t['Resources']['WebServer'], stack)
|
||||
|
||||
self.m.StubOutWithMock(server.__class__, 'script')
|
||||
server.script = None
|
||||
|
||||
self.m.StubOutWithMock(server.__class__, 'has_userdata')
|
||||
server.has_userdata = False
|
||||
|
||||
self.m.StubOutWithMock(server, 'nova')
|
||||
server.nova().MultipleTimes().AndReturn(self.fc)
|
||||
self.m.StubOutWithMock(clients.OpenStackClients, 'nova')
|
||||
clients.OpenStackClients.nova().MultipleTimes().AndReturn(self.fc)
|
||||
self.m.ReplayAll()
|
||||
|
||||
self.assertIsNone(server.validate())
|
||||
|
||||
expected_personality = {'/fake/path1': 'fake contents1',
|
||||
'/fake/path3': 'fake_contents3',
|
||||
'/fake/path2': 'fake_contents2',
|
||||
'/root/.ssh/authorized_keys': mox.IgnoreArg()}
|
||||
self.m.StubOutWithMock(self.fc.servers, 'create')
|
||||
self.fc.servers.create(
|
||||
image=1, flavor=1, key_name=None,
|
||||
name=utils.PhysName(stack_name, server.name),
|
||||
security_groups=[],
|
||||
userdata=mox.IgnoreArg(), scheduler_hints=None,
|
||||
meta=None, nics=None, availability_zone=None,
|
||||
block_device_mapping=None, config_drive=None,
|
||||
disk_config=None, reservation_id=None,
|
||||
files=expected_personality,
|
||||
admin_pass=None).AndReturn(return_server)
|
||||
|
||||
self.m.ReplayAll()
|
||||
scheduler.TaskRunner(server.create)()
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_validate_no_script_not_okay(self):
|
||||
stack_name = 'srv_val'
|
||||
(t, stack) = self._setup_test_stack(stack_name)
|
||||
|
||||
# create a server with non-existent image ID
|
||||
t['Resources']['WebServer']['Properties']['image'] = '1'
|
||||
server = cloud_server.CloudServer('server_create_image_err',
|
||||
t['Resources']['WebServer'], stack)
|
||||
|
||||
self.m.StubOutWithMock(image.ImageConstraint, "validate")
|
||||
image.ImageConstraint.validate(
|
||||
mox.IgnoreArg(), mox.IgnoreArg()).MultipleTimes().AndReturn(True)
|
||||
|
||||
self.m.StubOutWithMock(server.__class__, 'script')
|
||||
server.script = None
|
||||
|
||||
self.m.StubOutWithMock(server.__class__, 'has_userdata')
|
||||
server.has_userdata = True
|
||||
self.m.ReplayAll()
|
||||
|
||||
exc = self.assertRaises(exception.StackValidationFailed,
|
||||
server.validate)
|
||||
self.assertIn("user_data is not supported", str(exc))
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_validate_with_bootable_vol_and_userdata(self):
|
||||
stack_name = 'srv_val'
|
||||
(t, stack) = self._setup_test_stack(stack_name)
|
||||
|
||||
# create a server without an image
|
||||
del t['Resources']['WebServer']['Properties']['image']
|
||||
t['Resources']['WebServer']['Properties']['block_device_mapping'] = \
|
||||
[{
|
||||
"device_name": u'vda',
|
||||
"volume_id": "5d7e27da-6703-4f7e-9f94-1f67abef734c",
|
||||
"delete_on_termination": False
|
||||
}]
|
||||
server = cloud_server.CloudServer('server_create_image_err',
|
||||
t['Resources']['WebServer'], stack)
|
||||
|
||||
self.m.StubOutWithMock(server.__class__, 'has_userdata')
|
||||
server.has_userdata = True
|
||||
|
||||
self.m.StubOutWithMock(cloud_server.CloudServer, "nova")
|
||||
cloud_server.CloudServer.nova().MultipleTimes().AndReturn(self.fc)
|
||||
self.m.StubOutWithMock(clients.OpenStackClients, 'nova')
|
||||
clients.OpenStackClients.nova().MultipleTimes().AndReturn(self.fc)
|
||||
|
||||
self.m.ReplayAll()
|
||||
|
||||
exc = self.assertRaises(exception.StackValidationFailed,
|
||||
server.validate)
|
||||
self.assertIn("user_data scripts are not supported with bootable "
|
||||
"volumes", str(exc))
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_private_key(self):
|
||||
stack_name = 'test_private_key'
|
||||
(t, stack) = self._setup_test_stack(stack_name)
|
||||
server = cloud_server.CloudServer('server_private_key',
|
||||
t['Resources']['WebServer'],
|
||||
stack)
|
||||
|
||||
# This gives the fake cloud server an id and created_time attribute
|
||||
server._store_or_update(server.CREATE, server.IN_PROGRESS,
|
||||
'test_store')
|
||||
|
||||
server.private_key = 'fake private key'
|
||||
self.ctx = utils.dummy_context()
|
||||
rs = db_api.resource_get_by_name_and_stack(self.ctx,
|
||||
'server_private_key',
|
||||
stack.id)
|
||||
encrypted_key = rs.data[0]['value']
|
||||
self.assertNotEqual(encrypted_key, "fake private key")
|
||||
decrypted_key = server.private_key
|
||||
self.assertEqual("fake private key", decrypted_key)
|
||||
|
||||
def test_rackconnect_deployed(self):
|
||||
return_server = self.fc.servers.list()[1]
|
||||
return_server.metadata = {'rackconnect_automation_status': 'DEPLOYED'}
|
||||
@ -594,355 +308,6 @@ class CloudServersTest(HeatTestCase):
|
||||
self.assertEqual('Error: Unknown Managed Cloud automation status: FOO',
|
||||
str(exc))
|
||||
|
||||
def test_create_heatscript_nonzero_exit_status(self):
|
||||
return_server = self.fc.servers.list()[1]
|
||||
server = self._setup_test_server(return_server, 'test_create_image_id',
|
||||
exit_code=1)
|
||||
self.m.ReplayAll()
|
||||
create = scheduler.TaskRunner(server.create)
|
||||
exc = self.assertRaises(exception.ResourceFailure, create)
|
||||
self.assertEqual("Error: The heat-script.sh script exited with a "
|
||||
"non-zero exit status. To see the error message, "
|
||||
"log into the server at 192.0.2.0 and view "
|
||||
"/root/heat-script.log", str(exc))
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_create_cfnuserdata_nonzero_exit_status(self):
|
||||
return_server = self.fc.servers.list()[1]
|
||||
server = self._setup_test_server(return_server, 'test_create_image_id',
|
||||
exit_code=42)
|
||||
self.m.ReplayAll()
|
||||
create = scheduler.TaskRunner(server.create)
|
||||
exc = self.assertRaises(exception.ResourceFailure, create)
|
||||
self.assertEqual("Error: The cfn-userdata script exited with a "
|
||||
"non-zero exit status. To see the error message, "
|
||||
"log into the server at 192.0.2.0 and view "
|
||||
"/root/cfn-userdata.log", str(exc))
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_validate_too_many_personality_rackspace(self):
|
||||
stack_name = 'srv_val'
|
||||
(t, stack) = self._setup_test_stack(stack_name)
|
||||
|
||||
# create an server with non exist image Id
|
||||
t['Resources']['WebServer']['Properties']['personality'] = \
|
||||
{"/fake/path1": "fake contents1",
|
||||
"/fake/path2": "fake_contents2",
|
||||
"/fake/path3": "fake_contents3",
|
||||
"/fake/path4": "fake_contents4",
|
||||
"/fake/path5": "fake_contents5"}
|
||||
server = cloud_server.CloudServer('server_create_image_err',
|
||||
t['Resources']['WebServer'], stack)
|
||||
|
||||
self.m.StubOutWithMock(server.__class__, 'script')
|
||||
server.script = None
|
||||
|
||||
self.m.StubOutWithMock(server.__class__, 'has_userdata')
|
||||
server.has_userdata = False
|
||||
|
||||
self.m.StubOutWithMock(server, 'nova')
|
||||
server.nova().MultipleTimes().AndReturn(self.fc)
|
||||
self.m.StubOutWithMock(clients.OpenStackClients, "nova")
|
||||
clients.OpenStackClients.nova().MultipleTimes().AndReturn(self.fc)
|
||||
self.m.ReplayAll()
|
||||
|
||||
exc = self.assertRaises(exception.StackValidationFailed,
|
||||
server.validate)
|
||||
self.assertEqual("The personality property may not contain "
|
||||
"greater than 4 entries.", str(exc))
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_ssh_exception_recovered(self):
|
||||
return_server = self.fc.servers.list()[1]
|
||||
stack_name = 'test_create_ssh_exception_recovered'
|
||||
(t, stack) = self._setup_test_stack(stack_name)
|
||||
|
||||
t['Resources']['WebServer']['Properties']['image'] = 'CentOS 5.2'
|
||||
t['Resources']['WebServer']['Properties']['flavor'] = '256 MB Server'
|
||||
|
||||
server_name = 'test_create_ssh_exception_server'
|
||||
server = cloud_server.CloudServer(server_name,
|
||||
t['Resources']['WebServer'], stack)
|
||||
|
||||
self.m.StubOutWithMock(cloud_server.CloudServer, "nova")
|
||||
cloud_server.CloudServer.nova().MultipleTimes().AndReturn(self.fc)
|
||||
self.m.StubOutWithMock(clients.OpenStackClients, 'nova')
|
||||
clients.OpenStackClients.nova().MultipleTimes().AndReturn(self.fc)
|
||||
|
||||
server._private_key = rsa_key
|
||||
|
||||
self.m.StubOutWithMock(self.fc.servers, 'create')
|
||||
self.fc.servers.create(
|
||||
image=1,
|
||||
flavor=1,
|
||||
key_name=None,
|
||||
name=mox.IgnoreArg(),
|
||||
security_groups=[],
|
||||
userdata=mox.IgnoreArg(),
|
||||
scheduler_hints=None,
|
||||
meta=None,
|
||||
nics=None,
|
||||
availability_zone=None,
|
||||
block_device_mapping=None,
|
||||
config_drive=None,
|
||||
disk_config=None,
|
||||
reservation_id=None,
|
||||
files=mox.IgnoreArg(),
|
||||
admin_pass=None).AndReturn(return_server)
|
||||
|
||||
self.m.StubOutWithMock(cloud_server.CloudServer, 'script')
|
||||
cloud_server.CloudServer.script = "foobar"
|
||||
|
||||
# Make paramiko raise an SSHException the first time
|
||||
self.m.StubOutWithMock(paramiko, "Transport")
|
||||
paramiko.Transport((mox.IgnoreArg(), 22)).AndRaise(
|
||||
paramiko.SSHException())
|
||||
|
||||
transport = self.m.CreateMockAnything()
|
||||
|
||||
# The second time it works
|
||||
paramiko.Transport((mox.IgnoreArg(), 22)).AndReturn(transport)
|
||||
|
||||
transport.connect(hostkey=None, username="root", pkey=mox.IgnoreArg())
|
||||
sftp = self.m.CreateMockAnything()
|
||||
self.m.StubOutWithMock(paramiko, "SFTPClient")
|
||||
paramiko.SFTPClient.from_transport(transport).AndReturn(sftp)
|
||||
sftp_file = self.m.CreateMockAnything()
|
||||
sftp.open(mox.IgnoreArg(), 'w').MultipleTimes().AndReturn(sftp_file)
|
||||
sftp_file.write(mox.IgnoreArg()).MultipleTimes()
|
||||
sftp_file.close().MultipleTimes()
|
||||
sftp.close()
|
||||
transport.close()
|
||||
|
||||
self.m.StubOutWithMock(paramiko, "SSHClient")
|
||||
self.m.StubOutWithMock(paramiko, "MissingHostKeyPolicy")
|
||||
ssh = self.m.CreateMockAnything()
|
||||
paramiko.SSHClient().AndReturn(ssh)
|
||||
paramiko.MissingHostKeyPolicy()
|
||||
ssh.set_missing_host_key_policy(None)
|
||||
ssh.connect(mox.IgnoreArg(),
|
||||
key_filename=mox.IgnoreArg(),
|
||||
username='root')
|
||||
fake_chan = self.m.CreateMockAnything()
|
||||
self.m.StubOutWithMock(paramiko.SSHClient, "get_transport")
|
||||
chan = ssh.get_transport().AndReturn(fake_chan)
|
||||
fake_chan_session = self.m.CreateMockAnything()
|
||||
chan_session = chan.open_session().AndReturn(fake_chan_session)
|
||||
fake_chan_session.settimeout(3600.0)
|
||||
chan_session.exec_command(mox.IgnoreArg())
|
||||
fake_chan_session.recv(1024)
|
||||
chan_session.recv_exit_status().AndReturn(0)
|
||||
fake_chan_session.close()
|
||||
ssh.close()
|
||||
self.m.ReplayAll()
|
||||
|
||||
scheduler.TaskRunner(server.create)()
|
||||
self.assertEqual((server.CREATE, server.COMPLETE), server.state)
|
||||
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_ssh_exception_failed(self):
|
||||
return_server = self.fc.servers.list()[1]
|
||||
stack_name = 'test_create_ssh_exception_failed'
|
||||
(t, stack) = self._setup_test_stack(stack_name)
|
||||
|
||||
t['Resources']['WebServer']['Properties']['image'] = 'CentOS 5.2'
|
||||
t['Resources']['WebServer']['Properties']['flavor'] = '256 MB Server'
|
||||
|
||||
server_name = 'test_create_ssh_exception_server'
|
||||
server = cloud_server.CloudServer(server_name,
|
||||
t['Resources']['WebServer'], stack)
|
||||
|
||||
self.m.StubOutWithMock(cloud_server.CloudServer, "nova")
|
||||
cloud_server.CloudServer.nova().MultipleTimes().AndReturn(self.fc)
|
||||
self.m.StubOutWithMock(clients.OpenStackClients, 'nova')
|
||||
clients.OpenStackClients.nova().MultipleTimes().AndReturn(self.fc)
|
||||
|
||||
server._private_key = rsa_key
|
||||
|
||||
self.m.StubOutWithMock(self.fc.servers, 'create')
|
||||
self.fc.servers.create(
|
||||
image=1,
|
||||
flavor=1,
|
||||
key_name=None,
|
||||
name=mox.IgnoreArg(),
|
||||
security_groups=[],
|
||||
userdata=mox.IgnoreArg(),
|
||||
scheduler_hints=None,
|
||||
meta=None,
|
||||
nics=None,
|
||||
availability_zone=None,
|
||||
block_device_mapping=None,
|
||||
config_drive=None,
|
||||
disk_config=None,
|
||||
reservation_id=None,
|
||||
files=mox.IgnoreArg(),
|
||||
admin_pass=None).AndReturn(return_server)
|
||||
|
||||
self.m.StubOutWithMock(cloud_server.CloudServer, 'script')
|
||||
cloud_server.CloudServer.script = "foobar"
|
||||
|
||||
# Make paramiko raise an SSHException every time
|
||||
self.m.StubOutWithMock(paramiko, "Transport")
|
||||
paramiko.Transport((mox.IgnoreArg(), 22)).MultipleTimes().AndRaise(
|
||||
paramiko.SSHException())
|
||||
self.m.ReplayAll()
|
||||
|
||||
create = scheduler.TaskRunner(server.create)
|
||||
exc = self.assertRaises(exception.ResourceFailure, create)
|
||||
self.assertEqual("Error: Failed to establish SSH connection after 30 "
|
||||
"tries", str(exc))
|
||||
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_eof_error_recovered(self):
|
||||
return_server = self.fc.servers.list()[1]
|
||||
stack_name = 'test_create_ssh_exception_recovered'
|
||||
(t, stack) = self._setup_test_stack(stack_name)
|
||||
|
||||
t['Resources']['WebServer']['Properties']['image'] = 'CentOS 5.2'
|
||||
t['Resources']['WebServer']['Properties']['flavor'] = '256 MB Server'
|
||||
|
||||
server_name = 'test_create_ssh_exception_server'
|
||||
server = cloud_server.CloudServer(server_name,
|
||||
t['Resources']['WebServer'], stack)
|
||||
|
||||
self.m.StubOutWithMock(cloud_server.CloudServer, "nova")
|
||||
cloud_server.CloudServer.nova().MultipleTimes().AndReturn(self.fc)
|
||||
self.m.StubOutWithMock(clients.OpenStackClients, 'nova')
|
||||
clients.OpenStackClients.nova().MultipleTimes().AndReturn(self.fc)
|
||||
|
||||
server._private_key = rsa_key
|
||||
|
||||
self.m.StubOutWithMock(self.fc.servers, 'create')
|
||||
self.fc.servers.create(
|
||||
image=1,
|
||||
flavor=1,
|
||||
key_name=None,
|
||||
name=mox.IgnoreArg(),
|
||||
security_groups=[],
|
||||
userdata=mox.IgnoreArg(),
|
||||
scheduler_hints=None,
|
||||
meta=None,
|
||||
nics=None,
|
||||
availability_zone=None,
|
||||
block_device_mapping=None,
|
||||
config_drive=None,
|
||||
disk_config=None,
|
||||
reservation_id=None,
|
||||
files=mox.IgnoreArg(),
|
||||
admin_pass=None).AndReturn(return_server)
|
||||
|
||||
self.m.StubOutWithMock(cloud_server.CloudServer, 'script')
|
||||
cloud_server.CloudServer.script = "foobar"
|
||||
|
||||
transport = self.m.CreateMockAnything()
|
||||
self.m.StubOutWithMock(paramiko, "Transport")
|
||||
paramiko.Transport((mox.IgnoreArg(), 22)).MultipleTimes().\
|
||||
AndReturn(transport)
|
||||
|
||||
# Raise an EOFError the first time
|
||||
transport.connect(hostkey=None, username="root",
|
||||
pkey=mox.IgnoreArg()).AndRaise(EOFError)
|
||||
transport.connect(hostkey=None, username="root",
|
||||
pkey=mox.IgnoreArg())
|
||||
|
||||
sftp = self.m.CreateMockAnything()
|
||||
self.m.StubOutWithMock(paramiko, "SFTPClient")
|
||||
paramiko.SFTPClient.from_transport(transport).AndReturn(sftp)
|
||||
sftp_file = self.m.CreateMockAnything()
|
||||
sftp.open(mox.IgnoreArg(), 'w').MultipleTimes().AndReturn(sftp_file)
|
||||
sftp_file.write(mox.IgnoreArg()).MultipleTimes()
|
||||
sftp_file.close().MultipleTimes()
|
||||
sftp.close()
|
||||
transport.close()
|
||||
|
||||
self.m.StubOutWithMock(paramiko, "SSHClient")
|
||||
self.m.StubOutWithMock(paramiko, "MissingHostKeyPolicy")
|
||||
ssh = self.m.CreateMockAnything()
|
||||
paramiko.SSHClient().AndReturn(ssh)
|
||||
paramiko.MissingHostKeyPolicy()
|
||||
ssh.set_missing_host_key_policy(None)
|
||||
ssh.connect(mox.IgnoreArg(),
|
||||
key_filename=mox.IgnoreArg(),
|
||||
username='root')
|
||||
fake_chan = self.m.CreateMockAnything()
|
||||
self.m.StubOutWithMock(paramiko.SSHClient, "get_transport")
|
||||
chan = ssh.get_transport().AndReturn(fake_chan)
|
||||
fake_chan_session = self.m.CreateMockAnything()
|
||||
chan_session = chan.open_session().AndReturn(fake_chan_session)
|
||||
fake_chan_session.settimeout(3600.0)
|
||||
chan_session.exec_command(mox.IgnoreArg())
|
||||
fake_chan_session.recv(1024)
|
||||
chan_session.recv_exit_status().AndReturn(0)
|
||||
fake_chan_session.close()
|
||||
ssh.close()
|
||||
self.m.ReplayAll()
|
||||
|
||||
scheduler.TaskRunner(server.create)()
|
||||
self.assertEqual((server.CREATE, server.COMPLETE), server.state)
|
||||
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_eof_error_failed(self):
|
||||
return_server = self.fc.servers.list()[1]
|
||||
stack_name = 'test_create_ssh_exception_failed'
|
||||
(t, stack) = self._setup_test_stack(stack_name)
|
||||
|
||||
t['Resources']['WebServer']['Properties']['image'] = 'CentOS 5.2'
|
||||
t['Resources']['WebServer']['Properties']['flavor'] = '256 MB Server'
|
||||
|
||||
server_name = 'test_create_ssh_exception_server'
|
||||
server = cloud_server.CloudServer(server_name,
|
||||
t['Resources']['WebServer'], stack)
|
||||
|
||||
self.m.StubOutWithMock(cloud_server.CloudServer, "nova")
|
||||
cloud_server.CloudServer.nova().MultipleTimes().AndReturn(self.fc)
|
||||
self.m.StubOutWithMock(clients.OpenStackClients, 'nova')
|
||||
clients.OpenStackClients.nova().MultipleTimes().AndReturn(self.fc)
|
||||
|
||||
server._private_key = rsa_key
|
||||
|
||||
self.m.StubOutWithMock(self.fc.servers, 'create')
|
||||
self.fc.servers.create(
|
||||
image=1,
|
||||
flavor=1,
|
||||
key_name=None,
|
||||
name=mox.IgnoreArg(),
|
||||
security_groups=[],
|
||||
userdata=mox.IgnoreArg(),
|
||||
scheduler_hints=None,
|
||||
meta=None,
|
||||
nics=None,
|
||||
availability_zone=None,
|
||||
block_device_mapping=None,
|
||||
config_drive=None,
|
||||
disk_config=None,
|
||||
reservation_id=None,
|
||||
files=mox.IgnoreArg(),
|
||||
admin_pass=None).AndReturn(return_server)
|
||||
|
||||
self.m.StubOutWithMock(cloud_server.CloudServer, 'script')
|
||||
cloud_server.CloudServer.script = "foobar"
|
||||
|
||||
transport = self.m.CreateMockAnything()
|
||||
self.m.StubOutWithMock(paramiko, "Transport")
|
||||
paramiko.Transport((mox.IgnoreArg(), 22)).MultipleTimes().\
|
||||
AndReturn(transport)
|
||||
|
||||
# Raise an EOFError every time
|
||||
transport.connect(hostkey=None, username="root",
|
||||
pkey=mox.IgnoreArg()).MultipleTimes().\
|
||||
AndRaise(EOFError)
|
||||
self.m.ReplayAll()
|
||||
|
||||
create = scheduler.TaskRunner(server.create)
|
||||
exc = self.assertRaises(exception.ResourceFailure, create)
|
||||
self.assertEqual("Error: Failed to establish SSH connection after 30 "
|
||||
"tries", str(exc))
|
||||
|
||||
self.m.VerifyAll()
|
||||
|
||||
@mock.patch.object(clients.OpenStackClients, 'nova')
|
||||
@mock.patch.object(resource.Resource, 'data_set')
|
||||
def test_create_store_admin_pass_resource_data(self,
|
||||
@ -957,8 +322,6 @@ class CloudServersTest(HeatTestCase):
|
||||
t['Resources']['WebServer']['Properties']['save_admin_pass'] = True
|
||||
server = cloud_server.CloudServer('WebServer',
|
||||
t['Resources']['WebServer'], stack)
|
||||
server._sftp_files = mock.Mock()
|
||||
server._run_ssh_command = mock.Mock(return_value=0)
|
||||
|
||||
mock_nova.return_value = self.fc
|
||||
self.fc.servers.create = mock.Mock(return_value=return_server)
|
||||
@ -982,8 +345,6 @@ class CloudServersTest(HeatTestCase):
|
||||
t['Resources']['WebServer']['Properties']['save_admin_pass'] = False
|
||||
server = cloud_server.CloudServer('WebServer',
|
||||
t['Resources']['WebServer'], stack)
|
||||
server._sftp_files = mock.Mock()
|
||||
server._run_ssh_command = mock.Mock(return_value=0)
|
||||
|
||||
mock_nova.return_value = self.fc
|
||||
self.fc.servers.create = mock.Mock(return_value=return_server)
|
||||
@ -1007,8 +368,6 @@ class CloudServersTest(HeatTestCase):
|
||||
t['Resources']['WebServer']['Properties']['save_admin_pass'] = None
|
||||
server = cloud_server.CloudServer('WebServer',
|
||||
t['Resources']['WebServer'], stack)
|
||||
server._sftp_files = mock.Mock()
|
||||
server._run_ssh_command = mock.Mock(return_value=0)
|
||||
|
||||
mock_nova.return_value = self.fc
|
||||
self.fc.servers.create = mock.Mock(return_value=return_server)
|
||||
@ -1030,8 +389,6 @@ class CloudServersTest(HeatTestCase):
|
||||
|
||||
server = cloud_server.CloudServer('WebServer',
|
||||
t['Resources']['WebServer'], stack)
|
||||
server._sftp_files = mock.Mock()
|
||||
server._run_ssh_command = mock.Mock(return_value=0)
|
||||
|
||||
mock_nova.return_value = self.fc
|
||||
self.fc.servers.create = mock.Mock(return_value=return_server)
|
||||
@ -1060,3 +417,49 @@ class CloudServersTest(HeatTestCase):
|
||||
server = cloud_server.CloudServer('WebServer',
|
||||
t['Resources']['WebServer'], stack)
|
||||
self.assertEqual('foo', server.FnGetAtt('admin_pass'))
|
||||
|
||||
@mock.patch.object(clients.OpenStackClients, 'nova')
|
||||
def _test_server_config_drive(self, user_data, config_drive, result,
|
||||
mock_nova):
|
||||
return_server = self.fc.servers.list()[1]
|
||||
stack_name = 'no_user_data'
|
||||
(t, stack) = self._setup_test_stack(stack_name)
|
||||
properties = t['Resources']['WebServer']['Properties']
|
||||
properties['user_data'] = user_data
|
||||
properties['config_drive'] = config_drive
|
||||
server = cloud_server.CloudServer('WebServer',
|
||||
t['Resources']['WebServer'], stack)
|
||||
mock_nova.return_value = self.fc
|
||||
mock_servers_create = mock.Mock(return_value=return_server)
|
||||
self.fc.servers.create = mock_servers_create
|
||||
|
||||
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,
|
||||
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)
|
||||
|
@ -1,2 +1 @@
|
||||
-e git://github.com/rackspace/pyrax.git@4441a5bf900f19fdb2304a0f9ed15b43151541d8#egg=pyrax
|
||||
paramiko>=1.9.0
|
||||
|
@ -341,13 +341,9 @@ class Server(stack_user.StackUser):
|
||||
|
||||
return super(Server, self).physical_resource_name()
|
||||
|
||||
def _personality(self):
|
||||
def _config_drive(self):
|
||||
# This method is overridden by the derived CloudServer resource
|
||||
return self.properties.get(self.PERSONALITY)
|
||||
|
||||
def _key_name(self):
|
||||
# This method is overridden by the derived CloudServer resource
|
||||
return self.properties.get(self.KEY_NAME)
|
||||
return self.properties.get(self.CONFIG_DRIVE)
|
||||
|
||||
@staticmethod
|
||||
def _get_deployments_metadata(heatclient, server_id):
|
||||
@ -503,9 +499,10 @@ class Server(stack_user.StackUser):
|
||||
block_device_mapping = self._build_block_device_mapping(
|
||||
self.properties.get(self.BLOCK_DEVICE_MAPPING))
|
||||
reservation_id = self.properties.get(self.RESERVATION_ID)
|
||||
config_drive = self.properties.get(self.CONFIG_DRIVE)
|
||||
disk_config = self.properties.get(self.DISK_CONFIG)
|
||||
admin_pass = self.properties.get(self.ADMIN_PASS) or None
|
||||
personality_files = self.properties.get(self.PERSONALITY)
|
||||
key_name = self.properties.get(self.KEY_NAME)
|
||||
|
||||
server = None
|
||||
try:
|
||||
@ -513,7 +510,7 @@ class Server(stack_user.StackUser):
|
||||
name=self.physical_resource_name(),
|
||||
image=image,
|
||||
flavor=flavor_id,
|
||||
key_name=self._key_name(),
|
||||
key_name=key_name,
|
||||
security_groups=security_groups,
|
||||
userdata=userdata,
|
||||
meta=instance_meta,
|
||||
@ -522,9 +519,9 @@ class Server(stack_user.StackUser):
|
||||
availability_zone=availability_zone,
|
||||
block_device_mapping=block_device_mapping,
|
||||
reservation_id=reservation_id,
|
||||
config_drive=config_drive,
|
||||
config_drive=self._config_drive(),
|
||||
disk_config=disk_config,
|
||||
files=self._personality(),
|
||||
files=personality_files,
|
||||
admin_pass=admin_pass)
|
||||
finally:
|
||||
# Avoid a race condition where the thread could be cancelled
|
||||
@ -911,7 +908,7 @@ class Server(stack_user.StackUser):
|
||||
|
||||
# retrieve provider's absolute limits if it will be needed
|
||||
metadata = self.properties.get(self.METADATA)
|
||||
personality = self._personality()
|
||||
personality = self.properties.get(self.PERSONALITY)
|
||||
if metadata is not None or personality is not None:
|
||||
limits = nova_utils.absolute_limits(self.nova())
|
||||
|
||||
|
@ -8,7 +8,6 @@ kombu>=2.4.8
|
||||
lxml>=2.3
|
||||
netaddr>=0.7.6
|
||||
oslo.config>=1.2.0
|
||||
paramiko>=1.9.0
|
||||
PasteDeploy>=1.5.0
|
||||
pbr>=0.6,!=0.7,<1.0
|
||||
posix_ipc
|
||||
|
Loading…
Reference in New Issue
Block a user