Blame tests/cloud_tests/platforms/azurecloud/instance.py

Packit bc9a3a
# This file is part of cloud-init. See LICENSE file for license information.
Packit bc9a3a
Packit bc9a3a
"""Base Azure Cloud instance."""
Packit bc9a3a
Packit bc9a3a
from datetime import datetime, timedelta
Packit bc9a3a
from urllib.parse import urlparse
Packit bc9a3a
from time import sleep
Packit bc9a3a
import traceback
Packit bc9a3a
import os
Packit bc9a3a
Packit bc9a3a
Packit bc9a3a
# pylint: disable=no-name-in-module
Packit bc9a3a
from azure.storage.blob import BlockBlobService, BlobPermissions
Packit bc9a3a
from msrestazure.azure_exceptions import CloudError
Packit bc9a3a
Packit bc9a3a
from tests.cloud_tests import LOG
Packit bc9a3a
Packit bc9a3a
from ..instances import Instance
Packit bc9a3a
Packit bc9a3a
Packit bc9a3a
class AzureCloudInstance(Instance):
Packit bc9a3a
    """Azure Cloud backed instance."""
Packit bc9a3a
Packit bc9a3a
    platform_name = 'azurecloud'
Packit bc9a3a
Packit bc9a3a
    def __init__(self, platform, properties, config,
Packit bc9a3a
                 features, image_id, user_data=None):
Packit bc9a3a
        """Set up instance.
Packit bc9a3a
Packit bc9a3a
        @param platform: platform object
Packit bc9a3a
        @param properties: dictionary of properties
Packit bc9a3a
        @param config: dictionary of configuration values
Packit bc9a3a
        @param features: dictionary of supported feature flags
Packit bc9a3a
        @param image_id: image to find and/or use
Packit bc9a3a
        @param user_data: test user-data to pass to instance
Packit bc9a3a
        """
Packit bc9a3a
        super(AzureCloudInstance, self).__init__(
Packit bc9a3a
            platform, image_id, properties, config, features)
Packit bc9a3a
Packit bc9a3a
        self.ssh_port = 22
Packit bc9a3a
        self.ssh_ip = None
Packit bc9a3a
        self.instance = None
Packit bc9a3a
        self.image_id = image_id
Packit bc9a3a
        self.user_data = user_data
Packit bc9a3a
        self.ssh_key_file = os.path.join(
Packit bc9a3a
            platform.config['data_dir'], platform.config['private_key'])
Packit bc9a3a
        self.ssh_pubkey_file = os.path.join(
Packit bc9a3a
            platform.config['data_dir'], platform.config['public_key'])
Packit bc9a3a
        self.blob_client, self.container, self.blob = None, None, None
Packit bc9a3a
Packit bc9a3a
    def start(self, wait=True, wait_for_cloud_init=False):
Packit bc9a3a
        """Start instance with the platforms NIC."""
Packit bc9a3a
        if self.instance:
Packit bc9a3a
            return
Packit bc9a3a
        data = self.image_id.split('-')
Packit bc9a3a
        release, support = data[2].replace('_', '.'), data[3]
Packit bc9a3a
        sku = '%s-%s' % (release, support) if support == 'LTS' else release
Packit bc9a3a
        image_resource_id = '/subscriptions/%s' \
Packit bc9a3a
                            '/resourceGroups/%s' \
Packit bc9a3a
                            '/providers/Microsoft.Compute/images/%s' % (
Packit bc9a3a
                                self.platform.subscription_id,
Packit bc9a3a
                                self.platform.resource_group.name,
Packit bc9a3a
                                self.image_id)
Packit bc9a3a
        storage_uri = "http://%s.blob.core.windows.net" \
Packit bc9a3a
                      % self.platform.storage.name
Packit bc9a3a
        with open(self.ssh_pubkey_file, 'r') as key:
Packit bc9a3a
            ssh_pub_keydata = key.read()
Packit bc9a3a
Packit bc9a3a
        image_exists = False
Packit bc9a3a
        try:
Packit bc9a3a
            LOG.debug('finding image in resource group using image_id')
Packit bc9a3a
            self.platform.compute_client.images.get(
Packit bc9a3a
                self.platform.resource_group.name,
Packit bc9a3a
                self.image_id
Packit bc9a3a
            )
Packit bc9a3a
            image_exists = True
Packit bc9a3a
            LOG.debug('image found, launching instance')
Packit bc9a3a
        except CloudError:
Packit bc9a3a
            LOG.debug(
Packit bc9a3a
                'image not found, launching instance with base image')
Packit bc9a3a
            pass
Packit bc9a3a
Packit bc9a3a
        vm_params = {
Packit bc9a3a
            'location': self.platform.location,
Packit bc9a3a
            'os_profile': {
Packit bc9a3a
                'computer_name': 'CI',
Packit bc9a3a
                'admin_username': self.ssh_username,
Packit bc9a3a
                "customData": self.user_data,
Packit bc9a3a
                "linuxConfiguration": {
Packit bc9a3a
                    "disable_password_authentication": True,
Packit bc9a3a
                    "ssh": {
Packit bc9a3a
                        "public_keys": [{
Packit bc9a3a
                            "path": "/home/%s/.ssh/authorized_keys" %
Packit bc9a3a
                                    self.ssh_username,
Packit bc9a3a
                            "keyData": ssh_pub_keydata
Packit bc9a3a
                        }]
Packit bc9a3a
                    }
Packit bc9a3a
                }
Packit bc9a3a
            },
Packit bc9a3a
            "diagnosticsProfile": {
Packit bc9a3a
                "bootDiagnostics": {
Packit bc9a3a
                    "storageUri": storage_uri,
Packit bc9a3a
                    "enabled": True
Packit bc9a3a
                }
Packit bc9a3a
            },
Packit bc9a3a
            'hardware_profile': {
Packit bc9a3a
                'vm_size': self.platform.vm_size
Packit bc9a3a
            },
Packit bc9a3a
            'storage_profile': {
Packit bc9a3a
                'image_reference': {
Packit bc9a3a
                    'id': image_resource_id
Packit bc9a3a
                } if image_exists else {
Packit bc9a3a
                    'publisher': 'Canonical',
Packit bc9a3a
                    'offer': 'UbuntuServer',
Packit bc9a3a
                    'sku': sku,
Packit bc9a3a
                    'version': 'latest'
Packit bc9a3a
                }
Packit bc9a3a
            },
Packit bc9a3a
            'network_profile': {
Packit bc9a3a
                'network_interfaces': [{
Packit bc9a3a
                    'id': self.platform.nic.id
Packit bc9a3a
                }]
Packit bc9a3a
            },
Packit bc9a3a
            'tags': {
Packit bc9a3a
                'Name': self.platform.tag,
Packit bc9a3a
            }
Packit bc9a3a
        }
Packit bc9a3a
Packit bc9a3a
        try:
Packit bc9a3a
            self.instance = self.platform.compute_client.virtual_machines.\
Packit bc9a3a
                create_or_update(self.platform.resource_group.name,
Packit bc9a3a
                                 self.image_id, vm_params)
Packit bc9a3a
        except CloudError:
Packit bc9a3a
            raise RuntimeError('failed creating instance:\n{}'.format(
Packit bc9a3a
                traceback.format_exc()))
Packit bc9a3a
Packit bc9a3a
        if wait:
Packit bc9a3a
            self.instance.wait()
Packit bc9a3a
            self.ssh_ip = self.platform.network_client.\
Packit bc9a3a
                public_ip_addresses.get(
Packit bc9a3a
                    self.platform.resource_group.name,
Packit bc9a3a
                    self.platform.public_ip.name
Packit bc9a3a
                ).ip_address
Packit bc9a3a
            self._wait_for_system(wait_for_cloud_init)
Packit bc9a3a
Packit bc9a3a
        self.instance = self.instance.result()
Packit bc9a3a
        self.blob_client, self.container, self.blob =\
Packit bc9a3a
            self._get_blob_client()
Packit bc9a3a
Packit bc9a3a
    def shutdown(self, wait=True):
Packit bc9a3a
        """Finds console log then stopping/deallocates VM"""
Packit bc9a3a
        LOG.debug('waiting on console log before stopping')
Packit bc9a3a
        attempts, exists = 5, False
Packit bc9a3a
        while not exists and attempts:
Packit bc9a3a
            try:
Packit bc9a3a
                attempts -= 1
Packit bc9a3a
                exists = self.blob_client.get_blob_to_bytes(
Packit bc9a3a
                    self.container, self.blob)
Packit bc9a3a
                LOG.debug('found console log')
Packit bc9a3a
            except Exception as e:
Packit bc9a3a
                if attempts:
Packit bc9a3a
                    LOG.debug('Unable to find console log, '
Packit bc9a3a
                              '%s attempts remaining', attempts)
Packit bc9a3a
                    sleep(15)
Packit bc9a3a
                else:
Packit bc9a3a
                    LOG.warning('Could not find console log: %s', e)
Packit bc9a3a
                    pass
Packit bc9a3a
Packit bc9a3a
        LOG.debug('stopping instance %s', self.image_id)
Packit bc9a3a
        vm_deallocate = \
Packit bc9a3a
            self.platform.compute_client.virtual_machines.deallocate(
Packit bc9a3a
                self.platform.resource_group.name, self.image_id)
Packit bc9a3a
        if wait:
Packit bc9a3a
            vm_deallocate.wait()
Packit bc9a3a
Packit bc9a3a
    def destroy(self):
Packit bc9a3a
        """Delete VM and close all connections"""
Packit bc9a3a
        if self.instance:
Packit bc9a3a
            LOG.debug('destroying instance: %s', self.image_id)
Packit bc9a3a
            vm_delete = self.platform.compute_client.virtual_machines.delete(
Packit bc9a3a
                self.platform.resource_group.name, self.image_id)
Packit bc9a3a
            vm_delete.wait()
Packit bc9a3a
Packit bc9a3a
        self._ssh_close()
Packit bc9a3a
Packit bc9a3a
        super(AzureCloudInstance, self).destroy()
Packit bc9a3a
Packit bc9a3a
    def _execute(self, command, stdin=None, env=None):
Packit bc9a3a
        """Execute command on instance."""
Packit bc9a3a
        env_args = []
Packit bc9a3a
        if env:
Packit bc9a3a
            env_args = ['env'] + ["%s=%s" for k, v in env.items()]
Packit bc9a3a
Packit bc9a3a
        return self._ssh(['sudo'] + env_args + list(command), stdin=stdin)
Packit bc9a3a
Packit bc9a3a
    def _get_blob_client(self):
Packit bc9a3a
        """
Packit bc9a3a
        Use VM details to retrieve container and blob name.
Packit bc9a3a
        Then Create blob service client for sas token to
Packit bc9a3a
        retrieve console log.
Packit bc9a3a
Packit bc9a3a
        :return: blob service, container name, blob name
Packit bc9a3a
        """
Packit bc9a3a
        LOG.debug('creating blob service for console log')
Packit bc9a3a
        storage = self.platform.storage_client.storage_accounts.get_properties(
Packit bc9a3a
            self.platform.resource_group.name, self.platform.storage.name)
Packit bc9a3a
Packit bc9a3a
        keys = self.platform.storage_client.storage_accounts.list_keys(
Packit bc9a3a
            self.platform.resource_group.name, self.platform.storage.name
Packit bc9a3a
        ).keys[0].value
Packit bc9a3a
Packit bc9a3a
        virtual_machine = self.platform.compute_client.virtual_machines.get(
Packit bc9a3a
            self.platform.resource_group.name, self.instance.name,
Packit bc9a3a
            expand='instanceView')
Packit bc9a3a
Packit bc9a3a
        blob_uri = virtual_machine.instance_view.boot_diagnostics.\
Packit bc9a3a
            serial_console_log_blob_uri
Packit bc9a3a
Packit bc9a3a
        container, blob = urlparse(blob_uri).path.split('/')[-2:]
Packit bc9a3a
Packit bc9a3a
        blob_client = BlockBlobService(
Packit bc9a3a
            account_name=storage.name,
Packit bc9a3a
            account_key=keys)
Packit bc9a3a
Packit bc9a3a
        sas = blob_client.generate_blob_shared_access_signature(
Packit bc9a3a
            container_name=container, blob_name=blob, protocol='https',
Packit bc9a3a
            expiry=datetime.utcnow() + timedelta(hours=1),
Packit bc9a3a
            permission=BlobPermissions.READ)
Packit bc9a3a
Packit bc9a3a
        blob_client = BlockBlobService(
Packit bc9a3a
            account_name=storage.name,
Packit bc9a3a
            sas_token=sas)
Packit bc9a3a
Packit bc9a3a
        return blob_client, container, blob
Packit bc9a3a
Packit bc9a3a
    def console_log(self):
Packit bc9a3a
        """Instance console.
Packit bc9a3a
Packit bc9a3a
        @return_value: bytes of this instance’s console
Packit bc9a3a
        """
Packit bc9a3a
        boot_diagnostics = self.blob_client.get_blob_to_bytes(
Packit bc9a3a
            self.container, self.blob)
Packit bc9a3a
        return boot_diagnostics.content