Blob Blame History Raw
# This file is part of cloud-init. See LICENSE file for license information.

"""Base EC2 instance."""
import os

import botocore

from ..instances import Instance
from tests.cloud_tests import LOG, util


class EC2Instance(Instance):
    """EC2 backed instance."""

    platform_name = "ec2"
    _ssh_client = None

    def __init__(self, platform, properties, config, features,
                 image_ami, user_data=None):
        """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
        """
        super(EC2Instance, self).__init__(
            platform, image_ami, properties, config, features)

        self.image_ami = image_ami
        self.instance = 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 console_log(self):
        """Collect console log from instance.

        The console log is buffered and not always present, therefore
        may return empty string.
        """
        try:
            # OutputBytes comes from platform._decode_console_output_as_bytes
            response = self.instance.console_output()
            return response['OutputBytes']
        except KeyError as e:
            if 'Output' in response:
                msg = ("'OutputBytes' did not exist in console_output() but "
                       "'Output' did: %s..." % response['Output'][0:128])
                raise util.PlatformError('console_log', msg) from e
            return ('No Console Output [%s]' % self.instance).encode()

    def destroy(self):
        """Clean up instance."""
        if self.instance:
            LOG.debug('destroying instance %s', self.instance.id)
            self.instance.terminate()
            self.instance.wait_until_terminated()

        self._ssh_close()

        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."""
        if self.instance:
            if self.instance.state['Name'] == 'running':
                return

            LOG.debug('starting instance %s', self.instance.id)
            self.instance.start()
        else:
            LOG.debug('launching instance')

            args = {
                'ImageId': self.image_ami,
                'InstanceType': self.platform.instance_type,
                'KeyName': self.platform.key_name,
                'MaxCount': 1,
                'MinCount': 1,
                'SecurityGroupIds': [self.platform.security_group.id],
                'SubnetId': self.platform.subnet.id,
                'TagSpecifications': [{
                    'ResourceType': 'instance',
                    'Tags': [{
                        'Key': 'Name', 'Value': self.platform.tag
                    }]
                }],
            }

            if self.user_data:
                args['UserData'] = self.user_data

            try:
                instances = self.platform.ec2_resource.create_instances(**args)
            except botocore.exceptions.ClientError as error:
                error_msg = error.response['Error']['Message']
                raise util.PlatformError('start', error_msg)

            self.instance = instances[0]

        LOG.debug('instance id: %s', self.instance.id)
        if wait:
            self.instance.wait_until_running()
            self.instance.reload()
            self.ssh_ip = self.instance.public_ip_address
            self._wait_for_system(wait_for_cloud_init)

    def shutdown(self, wait=True):
        """Shutdown instance."""
        LOG.debug('stopping instance %s', self.instance.id)
        self.instance.stop()

        if wait:
            self.instance.wait_until_stopped()
            self.instance.reload()

# vi: ts=4 expandtab