summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoshua Powers <josh.powers@canonical.com>2017-12-07 20:54:46 (GMT)
committerJoshua Powers <josh.powers@canonical.com>2017-12-11 22:15:24 (GMT)
commit2d5c6d156cb4506260cb1abef54daefb7a0ffe05 (patch)
tree2e19d17de49099e305a39068e2ecca2f1e9b19b0
parent1d1c31292a0d10e3dd8940739b577fad9d18f5c5 (diff)
tests: Enable AWS EC2 Integration Testing
Utilizing the boto3 library, this enables integration tests to utilize the 'ec2' platform. TODO: * Configure image with custom version of cloud-init * Launch with dual-stack networking * Launch with root instance-store instead of ebs * Launch with specific instance type, t2.micro by default
-rw-r--r--test-requirements.txt1
-rw-r--r--tests/cloud_tests/collect.py4
-rw-r--r--tests/cloud_tests/platforms.yaml9
-rw-r--r--tests/cloud_tests/platforms/__init__.py2
-rw-r--r--tests/cloud_tests/platforms/ec2/image.py53
-rw-r--r--tests/cloud_tests/platforms/ec2/instance.py102
-rw-r--r--tests/cloud_tests/platforms/ec2/platform.py218
-rw-r--r--tests/cloud_tests/platforms/ec2/snapshot.py52
-rw-r--r--tests/cloud_tests/platforms/instances.py61
-rw-r--r--tests/cloud_tests/platforms/nocloudkvm/image.py4
-rw-r--r--tests/cloud_tests/platforms/nocloudkvm/instance.py55
-rw-r--r--tests/cloud_tests/platforms/nocloudkvm/platform.py5
-rw-r--r--tests/cloud_tests/platforms/nocloudkvm/snapshot.py7
-rw-r--r--tests/cloud_tests/platforms/platforms.py15
-rw-r--r--tests/cloud_tests/releases.yaml3
-rw-r--r--tests/cloud_tests/setup_image.py18
16 files changed, 531 insertions, 78 deletions
diff --git a/test-requirements.txt b/test-requirements.txt
index d9d41b5..d7a9de5 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -4,6 +4,7 @@ mock
nose
unittest2
coverage
+paramiko
# Only needed if you want to know the test times
# nose-timer
diff --git a/tests/cloud_tests/collect.py b/tests/cloud_tests/collect.py
index 4805cea..bb72245 100644
--- a/tests/cloud_tests/collect.py
+++ b/tests/cloud_tests/collect.py
@@ -31,8 +31,8 @@ def collect_console(instance, base_dir):
LOG.debug('getting console log')
try:
data = instance.console_log()
- except NotImplementedError as e:
- data = 'Not Implemented: %s' % e
+ except NotImplementedError:
+ data = b'instance.console_log: not implemented'
with open(os.path.join(base_dir, 'console.log'), "wb") as fp:
fp.write(data)
diff --git a/tests/cloud_tests/platforms.yaml b/tests/cloud_tests/platforms.yaml
index fa4f845..46c93bb 100644
--- a/tests/cloud_tests/platforms.yaml
+++ b/tests/cloud_tests/platforms.yaml
@@ -6,8 +6,13 @@ default_platform_config:
get_image_timeout: 300
# maximum time to create instance (before waiting for cloud-init)
create_instance_timeout: 60
-
platforms:
+ ec2:
+ enabled: true
+ instance-type: t2.micro
+ private_key: id_rsa
+ public_key: id_rsa.pub
+ vpc-tag: cloud-init-testing
lxd:
enabled: true
# overrides for image templates
@@ -63,7 +68,5 @@ platforms:
enabled: true
private_key: id_rsa
public_key: id_rsa.pub
- ec2: {}
- azure: {}
# vi: ts=4 expandtab
diff --git a/tests/cloud_tests/platforms/__init__.py b/tests/cloud_tests/platforms/__init__.py
index 92ed162..a01e51a 100644
--- a/tests/cloud_tests/platforms/__init__.py
+++ b/tests/cloud_tests/platforms/__init__.py
@@ -2,10 +2,12 @@
"""Main init."""
+from .ec2 import platform as ec2
from .lxd import platform as lxd
from .nocloudkvm import platform as nocloudkvm
PLATFORMS = {
+ 'ec2': ec2.EC2Platform,
'nocloud-kvm': nocloudkvm.NoCloudKVMPlatform,
'lxd': lxd.LXDPlatform,
}
diff --git a/tests/cloud_tests/platforms/ec2/image.py b/tests/cloud_tests/platforms/ec2/image.py
new file mode 100644
index 0000000..8608210
--- /dev/null
+++ b/tests/cloud_tests/platforms/ec2/image.py
@@ -0,0 +1,53 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+
+"""EC2 Image Base Class."""
+
+from ..images import Image
+from .snapshot import EC2Snapshot
+
+
+class EC2Image(Image):
+ """EC2 backed image."""
+
+ platform_name = 'ec2'
+
+ def __init__(self, platform, config, image_ami):
+ """Set up image.
+
+ @param platform: platform object
+ @param config: image configuration
+ @param image_ami: string of image ami ID
+ """
+ super(EC2Image, self).__init__(platform, config)
+
+ self.image_ami = image_ami
+
+ @property
+ def properties(self):
+ """Dictionary containing: 'arch', 'os', 'version', 'release'."""
+ return {
+ 'arch': self.config['arch'],
+ 'os': self.config['family'],
+ 'release': self.config['release'],
+ 'version': self.config['version'],
+ }
+
+ def _execute(self, command, stdin=None, env=None):
+ """Execute command in image, modifying image."""
+ raise NotImplementedError
+
+ def snapshot(self):
+ """Create snapshot of image, block until done."""
+ return EC2Snapshot(self.platform, self.properties, self.config,
+ self.features, self.image_ami)
+
+ def destroy(self):
+ """Unset path to signal image is no longer used.
+
+ The removal of the images and all other items is handled by the
+ framework. In some cases we want to keep the images, so let the
+ framework decide whether to keep or destroy everything.
+ """
+ super(EC2Image, self).destroy()
+
+# vi: ts=4 expandtab
diff --git a/tests/cloud_tests/platforms/ec2/instance.py b/tests/cloud_tests/platforms/ec2/instance.py
new file mode 100644
index 0000000..449684f
--- /dev/null
+++ b/tests/cloud_tests/platforms/ec2/instance.py
@@ -0,0 +1,102 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+
+"""Base EC2 instance."""
+import os
+import time
+
+import botocore
+
+from ..instances import Instance
+from tests.cloud_tests import util
+
+
+class EC2Instance(Instance):
+ """EC2 backed instance."""
+
+ platform_name = "ec2"
+ _ssh_client = None
+
+ def __init__(self, platform, properties, config, features,
+ image_ami, user_data):
+ """Set up instance.
+
+ @param platform: platform object
+ @param properties: dictionary of properties
+ @param config: dictionary of configuration values
+ @param features: dictionary of supported feature flags
+ @param image_ami: AWS AMI ID for image to use
+ @param user_data: Test user-data to pass to instance
+ @param use_desc:
+ """
+ super(EC2Instance, self).__init__(
+ platform, image_ami, properties, config, features)
+
+ self.image_ami = image_ami
+ self.instance_id = None
+ self.user_data = user_data
+ self.ssh_ip = None
+ self.ssh_port = 22
+ self.ssh_key_file = os.path.join(
+ platform.config['data_dir'], platform.config['private_key'])
+ self.ssh_pubkey_file = os.path.join(
+ platform.config['data_dir'], platform.config['public_key'])
+
+ def destroy(self):
+ """Clean up instance."""
+ if self._ssh_client:
+ self._ssh_client.close()
+ self._ssh_client = None
+
+ if self.instance:
+ self.instance.terminate()
+
+ super(EC2Instance, self).destroy()
+
+ def _execute(self, command, stdin=None, env=None):
+ """Execute command on instance."""
+ env_args = []
+ if env:
+ env_args = ['env'] + ["%s=%s" for k, v in env.items()]
+
+ return self.ssh(['sudo'] + env_args + list(command), stdin=stdin)
+
+ def start(self, wait=True, wait_for_cloud_init=False):
+ """Start instance on EC2 with the platfrom's VPC."""
+ name_tag = {
+ 'ResourceType': 'instance',
+ 'Tags': [
+ {
+ 'Key': 'Name',
+ 'Value': self.platform.ec2_tag,
+ },
+ ]
+ }
+
+ try:
+ instance = self.platform.ec2_resource.create_instances(
+ ImageId=self.image_ami,
+ InstanceType=self.platform.instance_type,
+ KeyName=self.platform.key_name,
+ MaxCount=1,
+ MinCount=1,
+ SecurityGroupIds=[self.platform.vpc_security_group.id],
+ SubnetId=self.platform.vpc_subnet.id,
+ TagSpecifications=[name_tag],
+ UserData=self.user_data,
+ )
+ except botocore.exceptions.ClientError as error:
+ error_msg = error.response['Error']['Message']
+ raise util.InTargetExecuteError(b'', b'', 1, '', 'start',
+ reason=error_msg)
+
+ self.instance = instance[0]
+ while self.instance.state['Name'] != 'running':
+ time.sleep(5)
+ self.instance.reload()
+
+ self.ssh_ip = self.instance.public_ip_address
+
+ if wait:
+ self._wait_for_system(wait_for_cloud_init)
+
+# vi: ts=4 expandtab
diff --git a/tests/cloud_tests/platforms/ec2/platform.py b/tests/cloud_tests/platforms/ec2/platform.py
new file mode 100644
index 0000000..ad74583
--- /dev/null
+++ b/tests/cloud_tests/platforms/ec2/platform.py
@@ -0,0 +1,218 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+
+"""Base EC2 platform."""
+import os
+import uuid
+
+import boto3
+
+from ..platforms import Platform
+from .image import EC2Image
+from .instance import EC2Instance
+
+
+class EC2Platform(Platform):
+ """EC2 test platform."""
+
+ platform_name = 'ec2'
+ ipv4_cidr = '192.168.1.0/20'
+
+ def __init__(self, config):
+ """Set up platform."""
+ super(EC2Platform, self).__init__(config)
+
+ self._generate_ssh_keys(config['data_dir'])
+ self.ec2_client = boto3.client('ec2')
+ self.ec2_resource = boto3.resource('ec2')
+ self.ec2_tag = config['vpc-tag']
+ self.instance_type = config['instance-type']
+ self.key_name = self._upload_public_key(config)
+ self.vpc = self._setup_vpc()
+ self.vpc_security_group = self._get_security_group()
+ self.vpc_subnet = self._get_subnet()
+
+ def destroy(self):
+ """Delete platform."""
+ if self.key_name:
+ self.ec2_client.delete_key_pair(KeyName=self.key_name)
+
+ def _upload_public_key(self, config):
+ """Generate random name and upload SSH key with that name.
+
+ @param config: platform config
+ """
+ key_file = os.path.join(config['data_dir'], config['public_key'])
+ with open(key_file, 'r') as file:
+ public_key = file.read().strip('\n')
+
+ name = '%s-%s' % (self.ec2_tag, str(uuid.uuid1())[0:8])
+ self.ec2_client.import_key_pair(KeyName=name,
+ PublicKeyMaterial=public_key)
+
+ return name
+
+ def _tag_resource(self, resource):
+ """Tag a resouce with the specified tag.
+
+ This makes finding and deleting resources specific to this testing
+ much easier to find.
+
+ @param resource: resource to tag"""
+ tag = {
+ 'Key': 'Name',
+ 'Value': self.ec2_tag
+ }
+ resource.create_tags(Tags=[tag])
+
+ def _setup_vpc(self):
+ """Setup AWS EC2 VPC or return existing VPC."""
+ for vpc in self.ec2_resource.vpcs.all():
+ if not vpc.tags:
+ continue
+ for tag in vpc.tags:
+ if tag['Value'] == self.ec2_tag:
+ return vpc
+
+ vpc = self.ec2_resource.create_vpc(
+ CidrBlock=self.ipv4_cidr,
+ AmazonProvidedIpv6CidrBlock=True
+ )
+ vpc.wait_until_available()
+ self._tag_resource(vpc)
+
+ internet_gateway = self._create_internet_gateway(vpc)
+ self._create_subnet(vpc)
+ self._update_routing_table(vpc, internet_gateway.id)
+ self._update_security_group(vpc)
+
+ return vpc
+
+ def _create_internet_gateway(self, vpc):
+ """Create Internet Gateway and assign to VPC.
+
+ @param vpc: VPC to create internet gateway on
+ """
+ internet_gateway = self.ec2_resource.create_internet_gateway()
+ internet_gateway.attach_to_vpc(VpcId=vpc.vpc_id)
+ self._tag_resource(internet_gateway)
+ return internet_gateway
+
+ def _create_subnet(self, vpc):
+ """Generate IPv4 and IPv6 subnets for use.
+
+ @param vpc: VPC to create subnets on
+ """
+ ipv6_block = vpc.ipv6_cidr_block_association_set[0]['Ipv6CidrBlock']
+ ipv6_cidr = ipv6_block[:-2] + '64'
+
+ subnet = vpc.create_subnet(CidrBlock=self.ipv4_cidr,
+ Ipv6CidrBlock=ipv6_cidr)
+ modify_subnet = subnet.meta.client.modify_subnet_attribute
+ modify_subnet(SubnetId=subnet.id,
+ MapPublicIpOnLaunch={'Value': True})
+ self._tag_resource(subnet)
+
+ def _update_routing_table(self, vpc, internet_gateway_id):
+ """Update default routing table with internet gateway.
+
+ This sets up internet access between the VPC via the internet gateway
+ by configuring routing tables for IPv4 and IPv6.
+
+ @param vpc: VPC containing routing table to update
+ @param internet_gateway_id: gateway ID to use for routing
+ """
+ route_table = list(vpc.route_tables.all())[0]
+ route_table.create_route(DestinationCidrBlock='0.0.0.0/0',
+ GatewayId=internet_gateway_id)
+ route_table.create_route(DestinationIpv6CidrBlock='::/0',
+ GatewayId=internet_gateway_id)
+ self._tag_resource(route_table)
+
+ def _update_security_group(self, vpc):
+ """Allow only SSH inbound to default VPC security group.
+
+ This revokes the initial accept all permissions and only allows
+ the SSH inbound.
+
+ @param vpc: VPC containing security group to update
+ """
+ ssh = {
+ 'IpProtocol': 'TCP',
+ 'FromPort': 22,
+ 'ToPort': 22,
+ 'IpRanges': [{'CidrIp': '0.0.0.0/0'}],
+ 'Ipv6Ranges': [{'CidrIpv6': '::/0'}]
+ }
+
+ security_group = list(vpc.security_groups.all())[0]
+ security_group.revoke_ingress(
+ IpPermissions=security_group.ip_permissions
+ )
+ security_group.authorize_ingress(
+ IpPermissions=[ssh]
+ )
+ self._tag_resource(security_group)
+
+ def _get_subnet(self):
+ """Return first subnet from VPC.
+
+ For now there should only be one.
+
+ @return_value: Return subnet object
+ """
+ return list(self.vpc.subnets.all())[0]
+
+ def _get_security_group(self):
+ """Return default security group from VPC.
+
+ For now there should only be one.
+
+ @return_value: Return first security group object
+ """
+ return list(self.vpc.security_groups.all())[0]
+
+ def get_image(self, img_conf):
+ """Get image using specified image configuration.
+
+ @param img_conf: configuration for image
+ @return_value: cloud_tests.images instance
+ """
+ if img_conf['root-store'] == 'ebs':
+ image_type = 'hvm-ssd'
+ elif img_conf['root-store'] == 'instance-store':
+ image_type = 'hvm-instance'
+ else:
+ raise RuntimeError('Unknown root-store type: %s' %
+ (img_conf['root-store']))
+
+ image_filter = {
+ 'Name': 'name',
+ 'Values': ['ubuntu/images-testing/%s/ubuntu-%s-daily-%s-server-*'
+ % (image_type, img_conf['release'], img_conf['arch'])]
+ }
+ response = self.ec2_client.describe_images(Filters=[image_filter])
+ images = sorted(response['Images'], key=lambda k: k['CreationDate'])
+
+ try:
+ image_ami = images[-1]['ImageId']
+ except IndexError:
+ raise RuntimeError('No images found for %s!' % img_conf['release'])
+
+ image = EC2Image(self, img_conf, image_ami)
+ return image
+
+ def create_instance(self, properties, config, features,
+ image_ami, user_data):
+ """Create an instance
+
+ @param src_img_path: image path to launch from
+ @param properties: image properties
+ @param config: image configuration
+ @param features: image features
+ @param image_desc: description of image being launched
+ @return_value: cloud_tests.instances instance
+ """
+ return EC2Instance(self, properties, config, features,
+ image_ami, user_data)
+
+# vi: ts=4 expandtab
diff --git a/tests/cloud_tests/platforms/ec2/snapshot.py b/tests/cloud_tests/platforms/ec2/snapshot.py
new file mode 100644
index 0000000..01ca22f
--- /dev/null
+++ b/tests/cloud_tests/platforms/ec2/snapshot.py
@@ -0,0 +1,52 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+
+"""Base EC2 snapshot."""
+
+from ..snapshots import Snapshot
+
+
+class EC2Snapshot(Snapshot):
+ """EC2 image copy backed snapshot."""
+
+ platform_name = 'ec2'
+
+ def __init__(self, platform, properties, config, features, image_ami):
+ """Set up snapshot.
+
+ @param platform: platform object
+ @param properties: image properties
+ @param config: image config
+ @param features: supported feature flags
+ @param image_ami: string of image ami ID
+ """
+ super(EC2Snapshot, self).__init__(
+ platform, properties, config, features)
+
+ self.image_ami = image_ami
+
+ def launch(self, user_data, meta_data=None, block=True, start=True,
+ use_desc=None):
+ """Launch instance.
+
+ @param user_data: user-data for the instance
+ @param instance_id: instance-id for the instance
+ @param block: wait until instance is created
+ @param start: start instance and wait until fully started
+ @param image_ami: string of image ami ID
+ @param use_desc: string of test name
+ @return_value: an Instance
+ """
+ instance = self.platform.create_instance(
+ self.properties, self.config, self.features,
+ self.image_ami, user_data)
+
+ if start:
+ instance.start()
+
+ return instance
+
+ def destroy(self):
+ """Clean up snapshot data."""
+ super(EC2Snapshot, self).destroy()
+
+# vi: ts=4 expandtab
diff --git a/tests/cloud_tests/platforms/instances.py b/tests/cloud_tests/platforms/instances.py
index 8c59d62..d09f96b 100644
--- a/tests/cloud_tests/platforms/instances.py
+++ b/tests/cloud_tests/platforms/instances.py
@@ -1,14 +1,22 @@
# This file is part of cloud-init. See LICENSE file for license information.
"""Base instance."""
+import time
+
+import paramiko
+from paramiko.ssh_exception import (BadHostKeyException,
+ AuthenticationException,
+ SSHException)
from ..util import TargetBase
+from tests.cloud_tests import util
class Instance(TargetBase):
"""Base instance object."""
platform_name = None
+ _ssh_client = None
def __init__(self, platform, name, properties, config, features):
"""Set up instance.
@@ -26,6 +34,10 @@ class Instance(TargetBase):
self.features = features
self._tmp_count = 0
+ self.ssh_ip = None
+ self.ssh_port = None
+ self.ssh_key_file = None
+
def console_log(self):
"""Instance console.
@@ -49,6 +61,55 @@ class Instance(TargetBase):
"""Clean up instance."""
pass
+ def _ssh_connect(self):
+ """Connect via SSH."""
+ if self._ssh_client:
+ return self._ssh_client
+
+ client = paramiko.SSHClient()
+ client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+ private_key = paramiko.RSAKey.from_private_key_file(self.ssh_key_file)
+
+ retries = 10
+ while retries:
+ try:
+ client.connect(username='ubuntu', hostname=self.ssh_ip,
+ port=self.ssh_port, pkey=private_key,
+ banner_timeout=30)
+ self._ssh_client = client
+ return client
+ except (ConnectionRefusedError, AuthenticationException,
+ BadHostKeyException, ConnectionResetError, SSHException,
+ OSError) as e:
+ retries -= 1
+ time.sleep(1)
+
+ ssh_cmd = 'Failed command to: ubuntu@%s:%s' % (
+ self.ssh_ip, self.ssh_port
+ )
+ raise util.InTargetExecuteError(b'', b'', 1, ssh_cmd, 'ssh')
+
+ def ssh(self, command, stdin=None):
+ """Run a command via SSH."""
+ client = self._ssh_connect()
+
+ cmd = util.shell_pack(command)
+ try:
+ fp_in, fp_out, fp_err = client.exec_command(cmd)
+ channel = fp_in.channel
+
+ if stdin is not None:
+ fp_in.write(stdin)
+ fp_in.close()
+
+ channel.shutdown_write()
+ rc = channel.recv_exit_status()
+ except SSHException as e:
+ raise util.InTargetExecuteError(b'', b'', 1, command, 'ssh',
+ reason=e)
+
+ return (fp_out.read(), fp_err.read(), rc)
+
def _wait_for_system(self, wait_for_cloud_init):
"""Wait until system has fully booted and cloud-init has finished.
diff --git a/tests/cloud_tests/platforms/nocloudkvm/image.py b/tests/cloud_tests/platforms/nocloudkvm/image.py
index 09ff2a3..8fff422 100644
--- a/tests/cloud_tests/platforms/nocloudkvm/image.py
+++ b/tests/cloud_tests/platforms/nocloudkvm/image.py
@@ -24,6 +24,8 @@ class NoCloudKVMImage(Image):
@param config: image configuration
@param img_path: path to the image
"""
+ super(NoCloudKVMImage, self).__init__(platform, config)
+
self.modified = False
self._workd = tempfile.mkdtemp(prefix='NoCloudKVMImage')
self._orig_img_path = orig_img_path
@@ -33,8 +35,6 @@ class NoCloudKVMImage(Image):
c_util.subp(['qemu-img', 'create', '-f', 'qcow2',
'-b', orig_img_path, self._img_path])
- super(NoCloudKVMImage, self).__init__(platform, config)
-
@property
def properties(self):
"""Dictionary containing: 'arch', 'os', 'version', 'release'."""
diff --git a/tests/cloud_tests/platforms/nocloudkvm/instance.py b/tests/cloud_tests/platforms/nocloudkvm/instance.py
index 9bb2425..27d4716 100644
--- a/tests/cloud_tests/platforms/nocloudkvm/instance.py
+++ b/tests/cloud_tests/platforms/nocloudkvm/instance.py
@@ -4,7 +4,6 @@
import copy
import os
-import paramiko
import socket
import subprocess
import time
@@ -26,7 +25,6 @@ class NoCloudKVMInstance(Instance):
"""NoCloud KVM backed instance."""
platform_name = "nocloud-kvm"
- _ssh_client = None
def __init__(self, platform, name, image_path, properties, config,
features, user_data, meta_data):
@@ -39,6 +37,10 @@ class NoCloudKVMInstance(Instance):
@param config: dictionary of configuration values
@param features: dictionary of supported feature flags
"""
+ super(NoCloudKVMInstance, self).__init__(
+ platform, name, properties, config, features
+ )
+
self.user_data = user_data
if meta_data:
meta_data = copy.deepcopy(meta_data)
@@ -56,7 +58,6 @@ class NoCloudKVMInstance(Instance):
platform.config['data_dir'], platform.config['private_key'])
self.ssh_pubkey_file = os.path.join(
platform.config['data_dir'], platform.config['public_key'])
-
self.ssh_pubkey = None
if self.ssh_pubkey_file:
with open(self.ssh_pubkey_file, "r") as fp:
@@ -66,6 +67,7 @@ class NoCloudKVMInstance(Instance):
meta_data['public-keys'] = []
meta_data['public-keys'].append(self.ssh_pubkey)
+ self.ssh_ip = '127.0.0.1'
self.ssh_port = None
self.pid = None
self.pid_file = None
@@ -73,9 +75,6 @@ class NoCloudKVMInstance(Instance):
self.disk = image_path
self.meta_data = meta_data
- super(NoCloudKVMInstance, self).__init__(
- platform, name, properties, config, features)
-
def destroy(self):
"""Clean up instance."""
if self.pid:
@@ -125,50 +124,6 @@ class NoCloudKVMInstance(Instance):
s.close()
return num
- def ssh(self, command, stdin=None):
- """Run a command via SSH."""
- client = self._ssh_connect()
-
- cmd = util.shell_pack(command)
- try:
- fp_in, fp_out, fp_err = client.exec_command(cmd)
- channel = fp_in.channel
- if stdin is not None:
- fp_in.write(stdin)
- fp_in.close()
-
- channel.shutdown_write()
- rc = channel.recv_exit_status()
- return (fp_out.read(), fp_err.read(), rc)
- except paramiko.SSHException as e:
- raise util.InTargetExecuteError(
- b'', b'', -1, command, self.name, reason=e)
-
- def _ssh_connect(self, hostname='localhost', username='ubuntu',
- banner_timeout=120, retry_attempts=30):
- """Connect via SSH."""
- if self._ssh_client:
- return self._ssh_client
-
- private_key = paramiko.RSAKey.from_private_key_file(self.ssh_key_file)
- client = paramiko.SSHClient()
- client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
- while retry_attempts:
- try:
- client.connect(hostname=hostname, username=username,
- port=self.ssh_port, pkey=private_key,
- banner_timeout=banner_timeout)
- self._ssh_client = client
- return client
- except (paramiko.SSHException, TypeError):
- time.sleep(1)
- retry_attempts = retry_attempts - 1
-
- error_desc = 'Failed command to: %s@%s:%s' % (username, hostname,
- self.ssh_port)
- raise util.InTargetExecuteError('', '', -1, 'ssh connect',
- self.name, error_desc)
-
def start(self, wait=True, wait_for_cloud_init=False):
"""Start instance."""
tmpdir = self.platform.config['data_dir']
diff --git a/tests/cloud_tests/platforms/nocloudkvm/platform.py b/tests/cloud_tests/platforms/nocloudkvm/platform.py
index 8593346..f7c915f 100644
--- a/tests/cloud_tests/platforms/nocloudkvm/platform.py
+++ b/tests/cloud_tests/platforms/nocloudkvm/platform.py
@@ -21,6 +21,11 @@ class NoCloudKVMPlatform(Platform):
platform_name = 'nocloud-kvm'
+ def __init__(self, config):
+ """Set up platform."""
+ super(NoCloudKVMPlatform, self).__init__(config)
+ self._generate_ssh_keys(config['data_dir'])
+
def get_image(self, img_conf):
"""Get image using specified image configuration.
diff --git a/tests/cloud_tests/platforms/nocloudkvm/snapshot.py b/tests/cloud_tests/platforms/nocloudkvm/snapshot.py
index 2dae359..1070dff 100644
--- a/tests/cloud_tests/platforms/nocloudkvm/snapshot.py
+++ b/tests/cloud_tests/platforms/nocloudkvm/snapshot.py
@@ -22,14 +22,15 @@ class NoCloudKVMSnapshot(Snapshot):
@param features: supported feature flags
@param image_path: image file to snapshot.
"""
+ super(NoCloudKVMSnapshot, self).__init__(
+ platform, properties, config, features
+ )
+
self._workd = tempfile.mkdtemp(prefix='NoCloudKVMSnapshot')
snapshot = os.path.join(self._workd, 'snapshot')
shutil.copyfile(image_path, snapshot)
self._image_path = snapshot
- super(NoCloudKVMSnapshot, self).__init__(
- platform, properties, config, features)
-
def launch(self, user_data, meta_data=None, block=True, start=True,
use_desc=None):
"""Launch instance.
diff --git a/tests/cloud_tests/platforms/platforms.py b/tests/cloud_tests/platforms/platforms.py
index 2897536..2791287 100644
--- a/tests/cloud_tests/platforms/platforms.py
+++ b/tests/cloud_tests/platforms/platforms.py
@@ -1,6 +1,9 @@
# This file is part of cloud-init. See LICENSE file for license information.
"""Base platform class."""
+import os
+
+from cloudinit import util as c_util
class Platform(object):
@@ -24,4 +27,16 @@ class Platform(object):
"""Clean up platform data."""
pass
+ def _generate_ssh_keys(self, data_dir):
+ """Generate SSH keys to be used with image."""
+ filename = os.path.join(data_dir, 'id_rsa')
+
+ if os.path.exists(filename):
+ c_util.del_file(filename)
+
+ c_util.subp(['ssh-keygen', '-t', 'rsa', '-b', '4096',
+ '-f', filename, '-P', '',
+ '-C', 'ubuntu@cloud_test'],
+ capture=True)
+
# vi: ts=4 expandtab
diff --git a/tests/cloud_tests/releases.yaml b/tests/cloud_tests/releases.yaml
index e593380..ea40750 100644
--- a/tests/cloud_tests/releases.yaml
+++ b/tests/cloud_tests/releases.yaml
@@ -27,6 +27,9 @@ default_release_config:
# features groups and additional feature settings
feature_groups: []
features: {}
+ ec2:
+ # Choose from: [ebs, instance-store]
+ root-store: ebs
nocloud-kvm:
mirror_url: https://cloud-images.ubuntu.com/daily
mirror_dir: '/srv/citest/nocloud-kvm'
diff --git a/tests/cloud_tests/setup_image.py b/tests/cloud_tests/setup_image.py
index 179f40d..6d24211 100644
--- a/tests/cloud_tests/setup_image.py
+++ b/tests/cloud_tests/setup_image.py
@@ -5,7 +5,6 @@
from functools import partial
import os
-from cloudinit import util as c_util
from tests.cloud_tests import LOG
from tests.cloud_tests import stage, util
@@ -192,20 +191,6 @@ def enable_repo(args, image):
image.execute(cmd, description=msg)
-def generate_ssh_keys(data_dir):
- """Generate SSH keys to be used with image."""
- LOG.info('generating SSH keys')
- filename = os.path.join(data_dir, 'id_rsa')
-
- if os.path.exists(filename):
- c_util.del_file(filename)
-
- c_util.subp(['ssh-keygen', '-t', 'rsa', '-b', '4096',
- '-f', filename, '-P', '',
- '-C', 'ubuntu@cloud_test'],
- capture=True)
-
-
def setup_image(args, image):
"""Set up image as specified in args.
@@ -239,9 +224,6 @@ def setup_image(args, image):
LOG.info('setting up %s', image)
res = stage.run_stage(
'set up for {}'.format(image), calls, continue_after_error=False)
- LOG.debug('after setup complete, installed cloud-init version is: %s',
- installed_package_version(image, 'cloud-init'))
- generate_ssh_keys(args.data_dir)
return res
# vi: ts=4 expandtab