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.
|
# under the License.
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import socket
|
|
||||||
import tempfile
|
|
||||||
|
|
||||||
from Crypto.PublicKey import RSA
|
|
||||||
import paramiko
|
|
||||||
|
|
||||||
from heat.common import exception
|
from heat.common import exception
|
||||||
from heat.engine import properties
|
from heat.engine import properties
|
||||||
@ -37,72 +32,6 @@ logger = logging.getLogger(__name__)
|
|||||||
class CloudServer(server.Server):
|
class CloudServer(server.Server):
|
||||||
"""Resource for Rackspace Cloud Servers."""
|
"""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
|
# Managed Cloud automation statuses
|
||||||
MC_STATUS_IN_PROGRESS = 'In Progress'
|
MC_STATUS_IN_PROGRESS = 'In Progress'
|
||||||
MC_STATUS_COMPLETE = 'Complete'
|
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):
|
def __init__(self, name, json_snippet, stack):
|
||||||
super(CloudServer, self).__init__(name, json_snippet, stack)
|
super(CloudServer, self).__init__(name, json_snippet, stack)
|
||||||
self.stack = stack
|
|
||||||
self._server = None
|
self._server = None
|
||||||
self._distro = None
|
self._distro = None
|
||||||
self._image = None
|
self._image = None
|
||||||
self._retry_iterations = 0
|
|
||||||
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
|
||||||
|
|
||||||
@ -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']
|
self._distro = image_data.metadata['os_distro']
|
||||||
return self._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
|
@property
|
||||||
def image(self):
|
def image(self):
|
||||||
"""Return the server's image ID."""
|
"""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)
|
self._image = nova_utils.get_image_id(self.nova(), image)
|
||||||
return self._image
|
return self._image
|
||||||
|
|
||||||
@property
|
def _config_drive(self):
|
||||||
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."""
|
|
||||||
user_data = self.properties.get(self.USER_DATA)
|
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
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
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):
|
def _check_managed_cloud_complete(self, server):
|
||||||
if not self._managed_cloud_started_event_sent:
|
if not self._managed_cloud_started_event_sent:
|
||||||
msg = _("Waiting for Managed Cloud automation to complete")
|
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
|
msg = _("Unknown RackConnect automation status: %s") % rc_status
|
||||||
raise exception.Error(msg)
|
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):
|
def check_create_complete(self, server):
|
||||||
"""Check if server creation is complete and handle server configs."""
|
"""Check if server creation is complete and handle server configs."""
|
||||||
if not self._check_active(server):
|
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):
|
self._check_managed_cloud_complete(server):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if self.has_userdata:
|
|
||||||
if self._run_userdata() is False:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _resolve_attribute(self, name):
|
def _resolve_attribute(self, name):
|
||||||
|
@ -13,16 +13,13 @@
|
|||||||
|
|
||||||
import mock
|
import mock
|
||||||
import mox
|
import mox
|
||||||
import paramiko
|
|
||||||
|
|
||||||
from heat.common import exception
|
from heat.common import exception
|
||||||
from heat.common import template_format
|
from heat.common import template_format
|
||||||
from heat.db import api as db_api
|
|
||||||
from heat.engine import clients
|
from heat.engine import clients
|
||||||
from heat.engine import environment
|
from heat.engine import environment
|
||||||
from heat.engine import parser
|
from heat.engine import parser
|
||||||
from heat.engine import resource
|
from heat.engine import resource
|
||||||
from heat.engine.resources import image
|
|
||||||
from heat.engine import scheduler
|
from heat.engine import scheduler
|
||||||
from heat.openstack.common import uuidutils
|
from heat.openstack.common import uuidutils
|
||||||
from heat.tests.common import HeatTestCase
|
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):
|
class CloudServersTest(HeatTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -85,44 +65,6 @@ class CloudServersTest(HeatTestCase):
|
|||||||
resource._register_class("Rackspace::Cloud::Server",
|
resource._register_class("Rackspace::Cloud::Server",
|
||||||
cloud_server.CloudServer)
|
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):
|
def _setup_test_stack(self, stack_name):
|
||||||
t = template_format.parse(wp_template)
|
t = template_format.parse(wp_template)
|
||||||
template = parser.Template(t)
|
template = parser.Template(t)
|
||||||
@ -154,14 +96,14 @@ class CloudServersTest(HeatTestCase):
|
|||||||
self.m.StubOutWithMock(clients.OpenStackClients, 'nova')
|
self.m.StubOutWithMock(clients.OpenStackClients, 'nova')
|
||||||
clients.OpenStackClients.nova().MultipleTimes().AndReturn(self.fc)
|
clients.OpenStackClients.nova().MultipleTimes().AndReturn(self.fc)
|
||||||
|
|
||||||
server._private_key = rsa_key
|
server.t = server.stack.resolve_runtime_data(server.t)
|
||||||
|
|
||||||
if stub_create:
|
if stub_create:
|
||||||
self.m.StubOutWithMock(self.fc.servers, 'create')
|
self.m.StubOutWithMock(self.fc.servers, 'create')
|
||||||
self.fc.servers.create(
|
self.fc.servers.create(
|
||||||
image=1,
|
image=1,
|
||||||
flavor=1,
|
flavor=1,
|
||||||
key_name=None,
|
key_name='test',
|
||||||
name=override_name and server.name or utils.PhysName(
|
name=override_name and server.name or utils.PhysName(
|
||||||
stack_name, server.name),
|
stack_name, server.name),
|
||||||
security_groups=[],
|
security_groups=[],
|
||||||
@ -171,16 +113,12 @@ class CloudServersTest(HeatTestCase):
|
|||||||
nics=None,
|
nics=None,
|
||||||
availability_zone=None,
|
availability_zone=None,
|
||||||
block_device_mapping=None,
|
block_device_mapping=None,
|
||||||
config_drive=None,
|
config_drive=True,
|
||||||
disk_config=None,
|
disk_config=None,
|
||||||
reservation_id=None,
|
reservation_id=None,
|
||||||
files=mox.IgnoreArg(),
|
files=mox.IgnoreArg(),
|
||||||
admin_pass=None).AndReturn(return_server)
|
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
|
return server
|
||||||
|
|
||||||
def _create_test_server(self, return_server, name, override_name=False,
|
def _create_test_server(self, return_server, name, override_name=False,
|
||||||
@ -193,7 +131,6 @@ class CloudServersTest(HeatTestCase):
|
|||||||
return server
|
return server
|
||||||
|
|
||||||
def _update_test_server(self, return_server, name, exit_code=0):
|
def _update_test_server(self, return_server, name, exit_code=0):
|
||||||
self._mock_ssh_sftp(exit_code)
|
|
||||||
self.m.StubOutWithMock(cloud_server.CloudServer, "nova")
|
self.m.StubOutWithMock(cloud_server.CloudServer, "nova")
|
||||||
cloud_server.CloudServer.nova().MultipleTimes().AndReturn(self.fc)
|
cloud_server.CloudServer.nova().MultipleTimes().AndReturn(self.fc)
|
||||||
|
|
||||||
@ -201,229 +138,6 @@ class CloudServersTest(HeatTestCase):
|
|||||||
image_data = mock.Mock(metadata={'os_distro': 'centos'})
|
image_data = mock.Mock(metadata={'os_distro': 'centos'})
|
||||||
self.fc.images.get = mock.Mock(return_value=image_data)
|
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):
|
def test_rackconnect_deployed(self):
|
||||||
return_server = self.fc.servers.list()[1]
|
return_server = self.fc.servers.list()[1]
|
||||||
return_server.metadata = {'rackconnect_automation_status': 'DEPLOYED'}
|
return_server.metadata = {'rackconnect_automation_status': 'DEPLOYED'}
|
||||||
@ -594,355 +308,6 @@ class CloudServersTest(HeatTestCase):
|
|||||||
self.assertEqual('Error: Unknown Managed Cloud automation status: FOO',
|
self.assertEqual('Error: Unknown Managed Cloud automation status: FOO',
|
||||||
str(exc))
|
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(clients.OpenStackClients, 'nova')
|
||||||
@mock.patch.object(resource.Resource, 'data_set')
|
@mock.patch.object(resource.Resource, 'data_set')
|
||||||
def test_create_store_admin_pass_resource_data(self,
|
def test_create_store_admin_pass_resource_data(self,
|
||||||
@ -957,8 +322,6 @@ class CloudServersTest(HeatTestCase):
|
|||||||
t['Resources']['WebServer']['Properties']['save_admin_pass'] = True
|
t['Resources']['WebServer']['Properties']['save_admin_pass'] = True
|
||||||
server = cloud_server.CloudServer('WebServer',
|
server = cloud_server.CloudServer('WebServer',
|
||||||
t['Resources']['WebServer'], stack)
|
t['Resources']['WebServer'], stack)
|
||||||
server._sftp_files = mock.Mock()
|
|
||||||
server._run_ssh_command = mock.Mock(return_value=0)
|
|
||||||
|
|
||||||
mock_nova.return_value = self.fc
|
mock_nova.return_value = self.fc
|
||||||
self.fc.servers.create = mock.Mock(return_value=return_server)
|
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
|
t['Resources']['WebServer']['Properties']['save_admin_pass'] = False
|
||||||
server = cloud_server.CloudServer('WebServer',
|
server = cloud_server.CloudServer('WebServer',
|
||||||
t['Resources']['WebServer'], stack)
|
t['Resources']['WebServer'], stack)
|
||||||
server._sftp_files = mock.Mock()
|
|
||||||
server._run_ssh_command = mock.Mock(return_value=0)
|
|
||||||
|
|
||||||
mock_nova.return_value = self.fc
|
mock_nova.return_value = self.fc
|
||||||
self.fc.servers.create = mock.Mock(return_value=return_server)
|
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
|
t['Resources']['WebServer']['Properties']['save_admin_pass'] = None
|
||||||
server = cloud_server.CloudServer('WebServer',
|
server = cloud_server.CloudServer('WebServer',
|
||||||
t['Resources']['WebServer'], stack)
|
t['Resources']['WebServer'], stack)
|
||||||
server._sftp_files = mock.Mock()
|
|
||||||
server._run_ssh_command = mock.Mock(return_value=0)
|
|
||||||
|
|
||||||
mock_nova.return_value = self.fc
|
mock_nova.return_value = self.fc
|
||||||
self.fc.servers.create = mock.Mock(return_value=return_server)
|
self.fc.servers.create = mock.Mock(return_value=return_server)
|
||||||
@ -1030,8 +389,6 @@ class CloudServersTest(HeatTestCase):
|
|||||||
|
|
||||||
server = cloud_server.CloudServer('WebServer',
|
server = cloud_server.CloudServer('WebServer',
|
||||||
t['Resources']['WebServer'], stack)
|
t['Resources']['WebServer'], stack)
|
||||||
server._sftp_files = mock.Mock()
|
|
||||||
server._run_ssh_command = mock.Mock(return_value=0)
|
|
||||||
|
|
||||||
mock_nova.return_value = self.fc
|
mock_nova.return_value = self.fc
|
||||||
self.fc.servers.create = mock.Mock(return_value=return_server)
|
self.fc.servers.create = mock.Mock(return_value=return_server)
|
||||||
@ -1060,3 +417,49 @@ class CloudServersTest(HeatTestCase):
|
|||||||
server = cloud_server.CloudServer('WebServer',
|
server = cloud_server.CloudServer('WebServer',
|
||||||
t['Resources']['WebServer'], stack)
|
t['Resources']['WebServer'], stack)
|
||||||
self.assertEqual('foo', server.FnGetAtt('admin_pass'))
|
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
|
-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()
|
return super(Server, self).physical_resource_name()
|
||||||
|
|
||||||
def _personality(self):
|
def _config_drive(self):
|
||||||
# This method is overridden by the derived CloudServer resource
|
# This method is overridden by the derived CloudServer resource
|
||||||
return self.properties.get(self.PERSONALITY)
|
return self.properties.get(self.CONFIG_DRIVE)
|
||||||
|
|
||||||
def _key_name(self):
|
|
||||||
# This method is overridden by the derived CloudServer resource
|
|
||||||
return self.properties.get(self.KEY_NAME)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_deployments_metadata(heatclient, server_id):
|
def _get_deployments_metadata(heatclient, server_id):
|
||||||
@ -503,9 +499,10 @@ class Server(stack_user.StackUser):
|
|||||||
block_device_mapping = self._build_block_device_mapping(
|
block_device_mapping = self._build_block_device_mapping(
|
||||||
self.properties.get(self.BLOCK_DEVICE_MAPPING))
|
self.properties.get(self.BLOCK_DEVICE_MAPPING))
|
||||||
reservation_id = self.properties.get(self.RESERVATION_ID)
|
reservation_id = self.properties.get(self.RESERVATION_ID)
|
||||||
config_drive = self.properties.get(self.CONFIG_DRIVE)
|
|
||||||
disk_config = self.properties.get(self.DISK_CONFIG)
|
disk_config = self.properties.get(self.DISK_CONFIG)
|
||||||
admin_pass = self.properties.get(self.ADMIN_PASS) or None
|
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
|
server = None
|
||||||
try:
|
try:
|
||||||
@ -513,7 +510,7 @@ class Server(stack_user.StackUser):
|
|||||||
name=self.physical_resource_name(),
|
name=self.physical_resource_name(),
|
||||||
image=image,
|
image=image,
|
||||||
flavor=flavor_id,
|
flavor=flavor_id,
|
||||||
key_name=self._key_name(),
|
key_name=key_name,
|
||||||
security_groups=security_groups,
|
security_groups=security_groups,
|
||||||
userdata=userdata,
|
userdata=userdata,
|
||||||
meta=instance_meta,
|
meta=instance_meta,
|
||||||
@ -522,9 +519,9 @@ class Server(stack_user.StackUser):
|
|||||||
availability_zone=availability_zone,
|
availability_zone=availability_zone,
|
||||||
block_device_mapping=block_device_mapping,
|
block_device_mapping=block_device_mapping,
|
||||||
reservation_id=reservation_id,
|
reservation_id=reservation_id,
|
||||||
config_drive=config_drive,
|
config_drive=self._config_drive(),
|
||||||
disk_config=disk_config,
|
disk_config=disk_config,
|
||||||
files=self._personality(),
|
files=personality_files,
|
||||||
admin_pass=admin_pass)
|
admin_pass=admin_pass)
|
||||||
finally:
|
finally:
|
||||||
# Avoid a race condition where the thread could be cancelled
|
# 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
|
# retrieve provider's absolute limits if it will be needed
|
||||||
metadata = self.properties.get(self.METADATA)
|
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:
|
if metadata is not None or personality is not None:
|
||||||
limits = nova_utils.absolute_limits(self.nova())
|
limits = nova_utils.absolute_limits(self.nova())
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ kombu>=2.4.8
|
|||||||
lxml>=2.3
|
lxml>=2.3
|
||||||
netaddr>=0.7.6
|
netaddr>=0.7.6
|
||||||
oslo.config>=1.2.0
|
oslo.config>=1.2.0
|
||||||
paramiko>=1.9.0
|
|
||||||
PasteDeploy>=1.5.0
|
PasteDeploy>=1.5.0
|
||||||
pbr>=0.6,!=0.7,<1.0
|
pbr>=0.6,!=0.7,<1.0
|
||||||
posix_ipc
|
posix_ipc
|
||||||
|
Loading…
Reference in New Issue
Block a user