Merge "Fixing SSH actions to use names of private keys"

This commit is contained in:
Jenkins 2015-10-23 06:19:08 +00:00 committed by Gerrit Code Review
commit 1ec594f0c1
8 changed files with 92 additions and 22 deletions

View File

@ -765,7 +765,9 @@ Input parameters:
*Required*. *Required*.
- **username** - User name to authenticate on the host. - **username** - User name to authenticate on the host.
- **password** - User password to to authenticate on the host. *Optional.* - **password** - User password to to authenticate on the host. *Optional.*
- **private_key** - Private key string which will be used for authentication on remote host. *Optional.* - **private_key_filename** - Private key file name which will be used for authentication on remote host.
All private keys should be on executor host in **<home-user-directory>/.ssh/**.
**<home-user-directory>** should refer to user directory under which service is running. *Optional.*
**NOTE**: Authentication using key pairs is supported, key should be **NOTE**: Authentication using key pairs is supported, key should be
on Mistral Executor server machine. on Mistral Executor server machine.

View File

@ -17,6 +17,7 @@
RETVAL=0 RETVAL=0
sudo chmod -R a+rw /opt/stack/new/
cd /opt/stack/new/ cd /opt/stack/new/
echo "Repository: $ZUUL_PROJECT" echo "Repository: $ZUUL_PROJECT"
@ -25,7 +26,7 @@ echo "Repository: $ZUUL_PROJECT"
if [[ "$ZUUL_PROJECT" == "openstack/mistral" ]]; then if [[ "$ZUUL_PROJECT" == "openstack/mistral" ]]; then
cd mistral/ cd mistral/
echo "Run mistral API tests" echo "Run mistral API tests"
sudo bash ./functionaltests/run_tests.sh ./functionaltests/run_tests.sh
RETVAL=$? RETVAL=$?
fi fi
@ -33,7 +34,7 @@ fi
if [[ RETVAL -eq 0 ]]; then if [[ RETVAL -eq 0 ]]; then
cd /opt/stack/new/python-mistralclient/ cd /opt/stack/new/python-mistralclient/
echo "Run mistralclient tests" echo "Run mistralclient tests"
sudo bash ./functionaltests/run_tests.sh ./functionaltests/run_tests.sh
RETVAL=$? RETVAL=$?
fi fi

View File

@ -37,8 +37,5 @@ MISTRALCLIENT_DIR=/opt/stack/new/python-mistralclient
# Define PYTHONPATH # Define PYTHONPATH
export PYTHONPATH=$PYTHONPATH:$TEMPEST_DIR export PYTHONPATH=$PYTHONPATH:$TEMPEST_DIR
#installing requirements for tempest
pip install -r $TEMPEST_DIR/requirements.txt
pwd pwd
nosetests -sv mistral/tests/functional/ nosetests -sv mistral/tests/functional/

View File

@ -330,19 +330,20 @@ class SSHAction(base.Action):
def _execute_cmd_method(self): def _execute_cmd_method(self):
return ssh_utils.execute_command return ssh_utils.execute_command
def __init__(self, cmd, host, username, password=None, private_key=None): def __init__(self, cmd, host, username,
password=None, private_key_filename=None):
self.cmd = cmd self.cmd = cmd
self.host = host self.host = host
self.username = username self.username = username
self.password = password self.password = password
self.private_key = private_key self.private_key_filename = private_key_filename
self.params = { self.params = {
'cmd': self.cmd, 'cmd': self.cmd,
'host': self.host, 'host': self.host,
'username': self.username, 'username': self.username,
'password': self.password, 'password': self.password,
'private_key': self.private_key 'private_key_filename': self.private_key_filename
} }
def run(self): def run(self):
@ -386,14 +387,15 @@ class SSHProxiedAction(SSHAction):
def _execute_cmd_method(self): def _execute_cmd_method(self):
return ssh_utils.execute_command_via_gateway return ssh_utils.execute_command_via_gateway
def __init__(self, cmd, host, username, private_key, gateway_host, def __init__(self, cmd, host, username, private_key_filename,
gateway_username=None, password=None, proxy_command=None): gateway_host, gateway_username=None,
password=None, proxy_command=None):
super(SSHProxiedAction, self).__init__( super(SSHProxiedAction, self).__init__(
cmd, cmd,
host, host,
username, username,
password, password,
private_key private_key_filename
) )
self.gateway_host = gateway_host self.gateway_host = gateway_host

View File

@ -13,6 +13,8 @@
# limitations under the License. # limitations under the License.
import json import json
import os
from os import path
import time import time
from oslo_log import log as logging from oslo_log import log as logging
@ -27,6 +29,7 @@ from mistral.utils import ssh_utils
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
CONF = config.CONF CONF = config.CONF
SSH_KEYS_DIRECTORY = path.expanduser("~/.ssh/")
class SSHActionsTestsV2(base.TestCaseAdvanced): class SSHActionsTestsV2(base.TestCaseAdvanced):
@ -138,6 +141,27 @@ class SSHActionsTestsV2(base.TestCaseAdvanced):
cls.private_key, cls.public_key = utils.generate_key_pair() cls.private_key, cls.public_key = utils.generate_key_pair()
cls.key_name = 'mistral-functional-tests-key' cls.key_name = 'mistral-functional-tests-key'
# If ZUUL_PROJECT is specified, it means
# tests are running on Jenkins gate.
if os.environ.get('ZUUL_PROJECT'):
cls.key_dir = "/opt/stack/new/.ssh/"
if not path.exists(cls.key_dir):
os.mkdir(cls.key_dir)
else:
cls.key_dir = SSH_KEYS_DIRECTORY
utils.save_text_to(
cls.private_key,
cls.key_dir + cls.key_name,
overwrite=True
)
LOG.info(
"Private key saved to %s" % cls.key_dir + cls.key_name
)
# Create keypair in nova. # Create keypair in nova.
cls.mgr.keypairs_client.create_keypair( cls.mgr.keypairs_client.create_keypair(
name=cls.key_name, name=cls.key_name,
@ -184,6 +208,7 @@ class SSHActionsTestsV2(base.TestCaseAdvanced):
cls.mgr.security_group_rules_client.delete_security_group_rule( cls.mgr.security_group_rules_client.delete_security_group_rule(
cls.ssh_rule_id cls.ssh_rule_id
) )
os.remove(cls.key_dir + cls.key_name)
super(SSHActionsTestsV2, cls).resource_cleanup() super(SSHActionsTestsV2, cls).resource_cleanup()
@ -193,7 +218,7 @@ class SSHActionsTestsV2(base.TestCaseAdvanced):
'cmd': 'hostname', 'cmd': 'hostname',
'host': self.public_vm_ip, 'host': self.public_vm_ip,
'username': CONF.scenario.ssh_user, 'username': CONF.scenario.ssh_user,
'private_key': self.private_key 'private_key_filename': self.key_name
} }
resp, body = self.client.create_action_execution( resp, body = self.client.create_action_execution(
@ -217,7 +242,7 @@ class SSHActionsTestsV2(base.TestCaseAdvanced):
'cmd': 'hostname', 'cmd': 'hostname',
'host': guest_vm_ip, 'host': guest_vm_ip,
'username': CONF.scenario.ssh_user, 'username': CONF.scenario.ssh_user,
'private_key': self.private_key, 'private_key_filename': self.key_name,
'gateway_host': self.public_vm_ip, 'gateway_host': self.public_vm_ip,
'gateway_username': CONF.scenario.ssh_user 'gateway_username': CONF.scenario.ssh_user
} }

View File

@ -16,8 +16,10 @@
import copy import copy
from mistral import exceptions as exc
from mistral.tests import base from mistral.tests import base
from mistral import utils from mistral import utils
from mistral.utils import ssh_utils
LEFT = { LEFT = {
'key1': { 'key1': {
@ -117,3 +119,15 @@ class UtilsTest(base.BaseTest):
self.assertEqual(2, input_dict.get('param2')) self.assertEqual(2, input_dict.get('param2'))
self.assertEqual('var3', input_dict.get('param3')) self.assertEqual('var3', input_dict.get('param3'))
self.assertIs(input_dict.get('param1'), utils.NotDefined) self.assertIs(input_dict.get('param1'), utils.NotDefined)
def test_paramiko_to_private_key(self):
self.assertRaises(
exc.DataAccessException,
ssh_utils._to_paramiko_private_key,
"../dir"
)
self.assertRaises(
exc.DataAccessException,
ssh_utils._to_paramiko_private_key,
"..\\dir"
)

View File

@ -267,6 +267,7 @@ def tempdir(**kwargs):
if 'dir' not in argdict: if 'dir' not in argdict:
argdict['dir'] = '/tmp/' argdict['dir'] = '/tmp/'
tmpdir = tempfile.mkdtemp(**argdict) tmpdir = tempfile.mkdtemp(**argdict)
try: try:
@ -281,6 +282,16 @@ def tempdir(**kwargs):
) )
def save_text_to(text, file_path, overwrite=False):
if os.path.exists(file_path) and not overwrite:
raise exc.DataAccessException(
"Cannot save data to file. File %s already exists."
)
with open(file_path, 'w') as f:
f.write(text)
def generate_key_pair(key_length=2048): def generate_key_pair(key_length=2048):
"""Create RSA key pair with specified number of bits in key. """Create RSA key pair with specified number of bits in key.
Returns tuple of private and public keys. Returns tuple of private and public keys.
@ -295,15 +306,20 @@ def generate_key_pair(key_length=2048):
'-f', keyfile, # filename of the key file '-f', keyfile, # filename of the key file
'-C', 'Generated-by-Mistral' # key comment '-C', 'Generated-by-Mistral' # key comment
] ]
if key_length is not None: if key_length is not None:
args.extend(['-b', key_length]) args.extend(['-b', key_length])
processutils.execute(*args) processutils.execute(*args)
if not os.path.exists(keyfile): if not os.path.exists(keyfile):
raise exc.DataAccessException( raise exc.DataAccessException(
"Private key file hasn't been created" "Private key file hasn't been created"
) )
private_key = open(keyfile).read() private_key = open(keyfile).read()
public_key_path = keyfile + '.pub' public_key_path = keyfile + '.pub'
if not os.path.exists(public_key_path): if not os.path.exists(public_key_path):
raise exc.DataAccessException( raise exc.DataAccessException(
"Public key file hasn't been created" "Public key file hasn't been created"

View File

@ -14,12 +14,17 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from os import path
import six import six
from oslo_log import log as logging from oslo_log import log as logging
import paramiko import paramiko
from mistral import exceptions as exc
KEY_PATH = path.expanduser("~/.ssh/")
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -33,9 +38,17 @@ def _read_paramimko_stream(recv_func):
return result return result
def _to_paramiko_private_key(private_key_raw, password): def _to_paramiko_private_key(private_key_filename, password=None):
if '../' in private_key_filename or '..\\' in private_key_filename:
raise exc.DataAccessException(
"Private key filename must not contain '..'. "
"Actual: %s" % private_key_filename
)
private_key_path = KEY_PATH + private_key_filename
return paramiko.RSAKey( return paramiko.RSAKey(
file_obj=six.StringIO(private_key_raw), filename=private_key_path,
password=password password=password
) )
@ -87,13 +100,12 @@ def _execute_command(ssh_client, cmd, get_stderr=False,
_cleanup(ssh_client) _cleanup(ssh_client)
def execute_command_via_gateway(cmd, host, username, private_key, def execute_command_via_gateway(cmd, host, username, private_key_filename,
gateway_host, gateway_username=None, gateway_host, gateway_username=None,
proxy_command=None, password=None): proxy_command=None, password=None):
LOG.debug('Creating SSH connection') LOG.debug('Creating SSH connection')
if isinstance(private_key, six.string_types): private_key = _to_paramiko_private_key(private_key_filename, password)
private_key = _to_paramiko_private_key(private_key, password)
proxy = None proxy = None
@ -138,9 +150,10 @@ def execute_command_via_gateway(cmd, host, username, private_key,
_cleanup(_proxy_ssh_client) _cleanup(_proxy_ssh_client)
def execute_command(cmd, host, username, password=None, private_key=None, def execute_command(cmd, host, username, password=None,
get_stderr=False, raise_when_error=True): private_key_filename=None, get_stderr=False,
ssh_client = _connect(host, username, password, private_key) raise_when_error=True):
ssh_client = _connect(host, username, password, private_key_filename)
LOG.debug("Executing command %s" % cmd) LOG.debug("Executing command %s" % cmd)