Prepare instances for SSH access

This patchset is in preparation for a scenario for running a
test/benchmark within an instance.

Change-Id: I94276237fb5b453b194e58ed00df589a3420a6ed
This commit is contained in:
Hugh Saunders
2013-12-24 20:29:48 +00:00
parent 3f5db4a953
commit 94415a3f40
8 changed files with 161 additions and 25 deletions

View File

@@ -38,8 +38,15 @@ class NovaScenario(base.Scenario):
:returns: Created server object :returns: Created server object
""" """
server = cls.clients("nova").servers.create(server_name, image_id,
flavor_id, **kwargs) if 'security_groups' not in kwargs:
kwargs['security_groups'] = ['rally_open']
else:
if 'rally_open' not in kwargs['security_groups']:
kwargs['security_groups'].append('rally_open')
server = cls.clients("nova").servers.create(
server_name, image_id, flavor_id, **kwargs)
# NOTE(msdubov): It is reasonable to wait 5 secs before starting to # NOTE(msdubov): It is reasonable to wait 5 secs before starting to
# check whether the server is ready => less API calls. # check whether the server is ready => less API calls.
time.sleep(5) time.sleep(5)

View File

@@ -112,6 +112,65 @@ def create_openstack_clients(users_endpoints, keys):
)) for cl in client_managers )) for cl in client_managers
] ]
_prepare_for_instance_ssh(clients)
return clients
def _prepare_for_instance_ssh(clients):
"""Generate and store SSH keys, allow access to port 22.
In order to run tests on instances it is necessary to have SSH access.
This function generates an SSH key pair per user which is stored in the
clients dictionary. The public key is also submitted to nova via the
novaclient.
A security group rule is created to allow access to instances on port 22.
"""
for client_dict in clients:
nova_client = client_dict['nova']
if ('rally_ssh_key' not in
[k.name for k in nova_client.keypairs.list()]):
keypair = nova_client.keypairs.create('rally_ssh_key')
client_dict['ssh_key_pair'] = dict(private=keypair.private_key,
public=keypair.public_key)
if 'rally_open' not in [sg.name for sg in
nova_client.security_groups.list()]:
rally_open = nova_client.security_groups.create(
'rally_open',
'Allow all access to VMs for benchmarking'
)
rally_open = nova_client.security_groups.find(name='rally_open')
rules_to_add = [dict(ip_protocol='tcp',
to_port=65536,
from_port=1,
ip_range=dict(cidr='0.0.0.0/0')),
dict(ip_protocol='udp',
to_port=65536,
from_port=1,
ip_range=dict(cidr='0.0.0.0/0')),
dict(ip_protocol='icmp',
to_port=255,
from_port=-1,
ip_range=dict(cidr='0.0.0.0/0'))
]
def rule_match(criteria, existing_rule):
return all(existing_rule[key] == value
for key, value in criteria.iteritems())
for new_rule in rules_to_add:
if not any(rule_match(new_rule, existing_rule) for existing_rule
in rally_open.rules):
nova_client.security_group_rules.create(
rally_open.id,
from_port=new_rule['from_port'],
to_port=new_rule['to_port'],
ip_protocol=new_rule['ip_protocol'],
cidr=new_rule['ip_range']['cidr'])
return clients return clients

View File

@@ -20,6 +20,7 @@ import random
import select import select
import socket import socket
import string import string
from StringIO import StringIO
import time import time
from rally import exceptions from rally import exceptions
@@ -31,27 +32,46 @@ LOG = logging.getLogger(__name__)
class SSH(object): class SSH(object):
"""SSH common functions.""" """SSH common functions."""
STDOUT_INDEX = 0
STDERR_INDEX = 1
def __init__(self, ip, user, port=22, key=None, timeout=1800): def __init__(self, ip, user, port=22, key=None, key_type="file",
timeout=1800):
"""Initialize SSH client with ip, username and the default values. """Initialize SSH client with ip, username and the default values.
timeout - the timeout for execution of the command timeout - the timeout for execution of the command
key - path to private key file, or string containing actual key
key_type - "file" for key path, "string" for actual key
""" """
self.ip = ip self.ip = ip
self.port = port self.port = port
self.user = user self.user = user
self.timeout = timeout self.timeout = timeout
self.client = None self.client = None
if key: self.key = key
self.key = key self.key_type = key_type
else: if not self.key:
#Guess location of user's private key if no key is specified.
self.key = os.path.expanduser('~/.ssh/id_rsa') self.key = os.path.expanduser('~/.ssh/id_rsa')
def _get_ssh_connection(self): def _get_ssh_connection(self):
self.client = paramiko.SSHClient() self.client = paramiko.SSHClient()
self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
self.client.connect(self.ip, username=self.user, connect_params = {
key_filename=self.key, port=self.port) 'hostname': self.ip,
'port': self.port,
'username': self.user
}
# NOTE(hughsaunders): Set correct paramiko parameter names for each
# method of supplying a key.
if self.key_type == 'file':
connect_params['key_filename'] = self.key
else:
connect_params['pkey'] = paramiko.RSAKey(
file_obj=StringIO(self.key))
self.client.connect(**connect_params)
def _is_timed_out(self, start_time): def _is_timed_out(self, start_time):
return (time.time() - self.timeout) > start_time return (time.time() - self.timeout) > start_time
@@ -136,14 +156,17 @@ class SSH(object):
ftp.put(os.path.expanduser(source), destination) ftp.put(os.path.expanduser(source), destination)
ftp.close() ftp.close()
def execute_script(self, script, enterpreter='/bin/sh'): def execute_script(self, script, interpreter='/bin/sh',
get_stdout=False, get_stderr=False):
"""Execute the specified local script on the remote server.""" """Execute the specified local script on the remote server."""
destination = '/tmp/' + ''.join( destination = '/tmp/' + ''.join(
random.choice(string.lowercase) for i in range(16)) random.choice(string.lowercase) for i in range(16))
self.upload(script, destination) self.upload(script, destination)
self.execute('%s %s' % (enterpreter, destination)) streams = self.execute('%s %s' % (interpreter, destination),
get_stdout=get_stdout, get_stderr=get_stderr)
self.execute('rm %s' % destination) self.execute('rm %s' % destination)
return streams
def wait(self, timeout=120, interval=1): def wait(self, timeout=120, interval=1):
"""Wait for the host will be available via ssh.""" """Wait for the host will be available via ssh."""

View File

@@ -58,7 +58,7 @@ class NovaScenarioTestCase(test.TestCase):
fc.get_nova_client = lambda: fake_nova fc.get_nova_client = lambda: fake_nova
fsm = fakes.FakeServerManager(fake_nova.images) fsm = fakes.FakeServerManager(fake_nova.images)
fake_server = fsm.create("s1", "i1", 1) fake_server = fsm.create("s1", "i1", 1)
fsm.create = lambda name, iid, fid: fake_server fsm.create = lambda name, iid, fid, **kwargs: fake_server
fake_nova.servers = fsm fake_nova.servers = fsm
fake_image_id = fsm.create_image(fake_server, 'img') fake_image_id = fsm.create_image(fake_server, 'img')
fake_image = fsm.images.get(fake_image_id) fake_image = fsm.images.get(fake_image_id)

View File

@@ -62,9 +62,10 @@ class ScenarioTestCase(test.TestCase):
endpoints = srunner._create_temp_tenants_and_users( endpoints = srunner._create_temp_tenants_and_users(
tenants, users_per_tenant) tenants, users_per_tenant)
self.assertEqual(len(endpoints), tenants * users_per_tenant) self.assertEqual(len(endpoints), tenants * users_per_tenant)
endpoint_keys = set(["username", "password", "tenant_name", "uri"]) endpoint_keys = set(["username", "password", "tenant_name",
"uri"])
for endpoint in endpoints: for endpoint in endpoints:
self.assertEqual(set(endpoint.keys()), endpoint_keys) self.assertTrue(endpoint_keys.issubset(endpoint.keys()))
def test_run_scenario(self): def test_run_scenario(self):
with mock.patch("rally.benchmark.utils.osclients") as mock_osclients: with mock.patch("rally.benchmark.utils.osclients") as mock_osclients:
@@ -98,7 +99,10 @@ class ScenarioTestCase(test.TestCase):
@mock.patch("rally.benchmark.utils.osclients") @mock.patch("rally.benchmark.utils.osclients")
@mock.patch("multiprocessing.pool.IMapIterator.next") @mock.patch("multiprocessing.pool.IMapIterator.next")
@mock.patch("rally.benchmark.runner.time.time") @mock.patch("rally.benchmark.runner.time.time")
def test_run_scenario_timeout(self, mock_time, mock_next, mock_osclients): @mock.patch("rally.benchmark.utils._prepare_for_instance_ssh")
def test_run_scenario_timeout(self, mock_prepare_for_instance_ssh,
mock_time, mock_next, mock_osclients):
mock_time.side_effect = [1, 2, 3, 10] mock_time.side_effect = [1, 2, 3, 10]
mock_next.side_effect = multiprocessing.TimeoutError() mock_next.side_effect = multiprocessing.TimeoutError()
mock_osclients.Clients.return_value = fakes.FakeClients() mock_osclients.Clients.return_value = fakes.FakeClients()

View File

@@ -79,7 +79,17 @@ class FakeKeypair(FakeResource):
class FakeSecurityGroup(FakeResource): class FakeSecurityGroup(FakeResource):
pass
def __init__(self, manager=None):
super(FakeSecurityGroup, self).__init__(manager)
self.rules = []
class FakeSecurityGroupRule(FakeResource):
def __init__(self, name, **kwargs):
super(FakeSecurityGroupRule, self).__init__(name)
for key, value in kwargs.items():
setattr(self, key, value)
class FakeVolume(FakeResource): class FakeVolume(FakeResource):
@@ -126,6 +136,16 @@ class FakeManager(object):
resources.append(self.cache[uuid]) resources.append(self.cache[uuid])
return resources return resources
def find(self, **kwargs):
for resource in self.cache.values():
match = True
for key, value in kwargs.items():
if getattr(resource, key, None) != value:
match = False
break
if match:
return resource
class FakeServerManager(FakeManager): class FakeServerManager(FakeManager):
@@ -145,7 +165,7 @@ class FakeServerManager(FakeManager):
server.name = name server.name = name
return server return server
def create(self, name, image_id, flavor_id): def create(self, name, image_id, flavor_id, **kwargs):
return self._create(name=name) return self._create(name=name)
def create_image(self, server, name): def create_image(self, server, name):
@@ -161,7 +181,7 @@ class FakeServerManager(FakeManager):
class FakeFailedServerManager(FakeServerManager): class FakeFailedServerManager(FakeServerManager):
def create(self, name, image_id, flavor_id): def create(self, name, image_id, flavor_id, **kwargs):
return self._create(FakeFailedServer, name) return self._create(FakeFailedServer, name)
@@ -198,7 +218,7 @@ class FakeNetworkManager(FakeManager):
class FakeKeypairManager(FakeManager): class FakeKeypairManager(FakeManager):
def create(self, name): def create(self, name, public_key=None):
kp = FakeKeypair(self) kp = FakeKeypair(self)
kp.name = name or kp.name kp.name = name or kp.name
return self._cache(kp) return self._cache(kp)
@@ -206,12 +226,28 @@ class FakeKeypairManager(FakeManager):
class FakeSecurityGroupManager(FakeManager): class FakeSecurityGroupManager(FakeManager):
def create(self, name): def __init__(self):
super(FakeSecurityGroupManager, self).__init__()
self.create('default')
def create(self, name, description=""):
sg = FakeSecurityGroup(self) sg = FakeSecurityGroup(self)
sg.name = name or sg.name sg.name = name or sg.name
sg.description = description
return self._cache(sg) return self._cache(sg)
class FakeSecurityGroupRuleManager(FakeManager):
def __init__(self):
super(FakeSecurityGroupRuleManager, self).__init__()
def create(self, name, **kwargs):
sgr = FakeSecurityGroupRule(self, **kwargs)
sgr.name = name or sgr.name
return self._cache(sgr)
class FakeUsersManager(FakeManager): class FakeUsersManager(FakeManager):
def create(self, username, password, email, tenant_id): def create(self, username, password, email, tenant_id):
@@ -292,6 +328,7 @@ class FakeNovaClient(object):
self.networks = FakeNetworkManager() self.networks = FakeNetworkManager()
self.keypairs = FakeKeypairManager() self.keypairs = FakeKeypairManager()
self.security_groups = FakeSecurityGroupManager() self.security_groups = FakeSecurityGroupManager()
self.security_group_rules = FakeSecurityGroupRuleManager()
class FakeKeystoneClient(object): class FakeKeystoneClient(object):

View File

@@ -16,6 +16,7 @@
import jsonschema import jsonschema
import mock import mock
import netaddr import netaddr
import os
from rally.openstack.common.fixture import mockpatch from rally.openstack.common.fixture import mockpatch
from rally.openstack.common import test from rally.openstack.common import test
@@ -43,15 +44,17 @@ class VirshProviderTestCase(test.BaseTestCase):
mock_subp.check_output.return_value = '10.0.0.1' mock_subp.check_output.return_value = '10.0.0.1'
mock_ipaddress.return_value = '10.0.0.2' mock_ipaddress.return_value = '10.0.0.2'
server = self.provider.create_vm('name') server = self.provider.create_vm('name')
script_path = '%(virsh_path)s/virsh/get_domain_ip.sh' % dict(
virsh_path=os.path.split(virsh.__file__)[0])
mock_subp.assert_has_calls([ mock_subp.assert_has_calls([
mock.call.check_call('virt-clone --connect=qemu+ssh://user@host/' mock.call.check_call('virt-clone --connect=qemu+ssh://user@host/'
'system -o prefix -n name --auto-clone', 'system -o prefix -n name --auto-clone',
shell=True), shell=True),
mock.call.check_call('virsh --connect=qemu+ssh://user@host/system ' mock.call.check_call('virsh --connect=qemu+ssh://user@host/system '
'start name', shell=True), 'start name', shell=True),
mock.call.check_call('scp -o StrictHostKeyChecking=no rally/serve' mock.call.check_call('scp -o StrictHostKeyChecking=no %s u'
'rprovider/providers/virsh/get_domain_ip.sh u' 'ser@host:~/get_domain_ip.sh' % script_path,
'ser@host:~/get_domain_ip.sh', shell=True), shell=True),
mock.call.check_output('ssh -o StrictHostKeyChecking=no user@host ' mock.call.check_output('ssh -o StrictHostKeyChecking=no user@host '
'./get_domain_ip.sh name', shell=True), './get_domain_ip.sh name', shell=True),
]) ])

View File

@@ -77,7 +77,7 @@ class SSHTestCase(test.TestCase):
self.ssh.upload('/tmp/s', '/tmp/d') self.ssh.upload('/tmp/s', '/tmp/d')
expected = [mock.call.set_missing_host_key_policy(self.policy), expected = [mock.call.set_missing_host_key_policy(self.policy),
mock.call.connect('example.net', username='root', mock.call.connect(hostname='example.net', username='root',
key_filename=os.path.expanduser( key_filename=os.path.expanduser(
'~/.ssh/id_rsa'), port=22), '~/.ssh/id_rsa'), port=22),
mock.call.open_sftp(), mock.call.open_sftp(),
@@ -94,8 +94,11 @@ class SSHTestCase(test.TestCase):
self.ssh.execute_script('/bin/script') self.ssh.execute_script('/bin/script')
up.assert_called_once_with('/bin/script', '/tmp/aaaaaaaaaaaaaaaa') up.assert_called_once_with('/bin/script', '/tmp/aaaaaaaaaaaaaaaa')
ex.assert_has_calls([mock.call('/bin/sh /tmp/aaaaaaaaaaaaaaaa'), ex.assert_has_calls([
mock.call('rm /tmp/aaaaaaaaaaaaaaaa')]) mock.call('/bin/sh /tmp/aaaaaaaaaaaaaaaa',
get_stderr=False, get_stdout=False),
mock.call('rm /tmp/aaaaaaaaaaaaaaaa')
])
@mock.patch('rally.sshutils.SSH.execute') @mock.patch('rally.sshutils.SSH.execute')
def test_wait(self, ex): def test_wait(self, ex):