summaryrefslogtreecommitdiff
path: root/tests/cloud_tests
diff options
context:
space:
mode:
authorzsdc <taras@vyos.io>2020-03-11 21:20:58 +0200
committerzsdc <taras@vyos.io>2020-03-11 21:22:23 +0200
commitc6627bc05a57645e6af8b9a5a67e452d9f37e487 (patch)
treeb754b3991e5e57a9ae9155819f73fa0cbd4be269 /tests/cloud_tests
parentca9a4eb26b41c204d1bd3a15586b14a5dde950bb (diff)
parent13e82554728b1cb524438163784e5b955c7c5ed0 (diff)
downloadvyos-cloud-init-c6627bc05a57645e6af8b9a5a67e452d9f37e487.tar.gz
vyos-cloud-init-c6627bc05a57645e6af8b9a5a67e452d9f37e487.zip
Cloud-init: T2117: Updated to 20.1
- Merge 20.1 version from the Canonical repository - Removed unneeded changes in datasources (now only OVF datasource is not equal to upstream's version) - Adapted cc_vyos module to new Cloud-init version - Changed Jenkinsfile to use build scripts, provided by upstream
Diffstat (limited to 'tests/cloud_tests')
-rw-r--r--tests/cloud_tests/__init__.py3
-rw-r--r--tests/cloud_tests/config.py2
-rw-r--r--tests/cloud_tests/platforms.yaml7
-rw-r--r--tests/cloud_tests/platforms/__init__.py2
-rw-r--r--tests/cloud_tests/platforms/azurecloud/__init__.py0
-rw-r--r--tests/cloud_tests/platforms/azurecloud/image.py116
-rw-r--r--tests/cloud_tests/platforms/azurecloud/instance.py248
-rw-r--r--tests/cloud_tests/platforms/azurecloud/platform.py233
-rw-r--r--tests/cloud_tests/platforms/azurecloud/regions.json42
-rw-r--r--tests/cloud_tests/platforms/azurecloud/snapshot.py58
-rw-r--r--tests/cloud_tests/platforms/ec2/image.py1
-rw-r--r--tests/cloud_tests/platforms/ec2/platform.py3
-rw-r--r--tests/cloud_tests/platforms/lxd/instance.py14
-rw-r--r--tests/cloud_tests/platforms/nocloudkvm/instance.py13
-rw-r--r--tests/cloud_tests/platforms/nocloudkvm/platform.py10
-rw-r--r--tests/cloud_tests/platforms/platforms.py2
-rw-r--r--tests/cloud_tests/releases.yaml18
-rw-r--r--tests/cloud_tests/setup_image.py5
-rw-r--r--tests/cloud_tests/testcases/modules/TODO.md7
-rw-r--r--tests/cloud_tests/testcases/modules/apt_pipelining_disable.yaml3
-rw-r--r--tests/cloud_tests/testcases/modules/apt_pipelining_os.py6
-rw-r--r--tests/cloud_tests/testcases/modules/apt_pipelining_os.yaml9
-rw-r--r--tests/cloud_tests/testcases/modules/snappy.py17
-rw-r--r--tests/cloud_tests/testcases/modules/snappy.yaml18
-rw-r--r--tests/cloud_tests/testcases/modules/ssh_auth_key_fingerprints_disable.py2
-rw-r--r--tests/cloud_tests/verify.py9
26 files changed, 781 insertions, 67 deletions
diff --git a/tests/cloud_tests/__init__.py b/tests/cloud_tests/__init__.py
index dd436989..6c632f99 100644
--- a/tests/cloud_tests/__init__.py
+++ b/tests/cloud_tests/__init__.py
@@ -22,7 +22,8 @@ def _initialize_logging():
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter(
- '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
+ '%(asctime)s - %(pathname)s:%(funcName)s:%(lineno)s '
+ '[%(levelname)s]: %(message)s')
console = logging.StreamHandler()
console.setLevel(logging.DEBUG)
diff --git a/tests/cloud_tests/config.py b/tests/cloud_tests/config.py
index 8bd569fd..06536edc 100644
--- a/tests/cloud_tests/config.py
+++ b/tests/cloud_tests/config.py
@@ -114,7 +114,7 @@ def load_os_config(platform_name, os_name, require_enabled=False,
feature_conf = main_conf['features']
feature_groups = conf.get('feature_groups', [])
overrides = merge_config(get(conf, 'features'), feature_overrides)
- conf['arch'] = c_util.get_architecture()
+ conf['arch'] = c_util.get_dpkg_architecture()
conf['features'] = merge_feature_groups(
feature_conf, feature_groups, overrides)
diff --git a/tests/cloud_tests/platforms.yaml b/tests/cloud_tests/platforms.yaml
index 448aa98d..eaaa0a71 100644
--- a/tests/cloud_tests/platforms.yaml
+++ b/tests/cloud_tests/platforms.yaml
@@ -66,5 +66,12 @@ platforms:
{{ config_get("user.vendor-data", properties.default) }}
nocloud-kvm:
enabled: true
+ cache_mode: cache=none,aio=native
+ azurecloud:
+ enabled: true
+ region: West US 2
+ vm_size: Standard_DS1_v2
+ storage_sku: standard_lrs
+ tag: ci
# vi: ts=4 expandtab
diff --git a/tests/cloud_tests/platforms/__init__.py b/tests/cloud_tests/platforms/__init__.py
index a01e51ac..6a410b84 100644
--- a/tests/cloud_tests/platforms/__init__.py
+++ b/tests/cloud_tests/platforms/__init__.py
@@ -5,11 +5,13 @@
from .ec2 import platform as ec2
from .lxd import platform as lxd
from .nocloudkvm import platform as nocloudkvm
+from .azurecloud import platform as azurecloud
PLATFORMS = {
'ec2': ec2.EC2Platform,
'nocloud-kvm': nocloudkvm.NoCloudKVMPlatform,
'lxd': lxd.LXDPlatform,
+ 'azurecloud': azurecloud.AzureCloudPlatform,
}
diff --git a/tests/cloud_tests/platforms/azurecloud/__init__.py b/tests/cloud_tests/platforms/azurecloud/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tests/cloud_tests/platforms/azurecloud/__init__.py
diff --git a/tests/cloud_tests/platforms/azurecloud/image.py b/tests/cloud_tests/platforms/azurecloud/image.py
new file mode 100644
index 00000000..aad2bca1
--- /dev/null
+++ b/tests/cloud_tests/platforms/azurecloud/image.py
@@ -0,0 +1,116 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+
+"""Azure Cloud image Base class."""
+
+from tests.cloud_tests import LOG
+
+from ..images import Image
+from .snapshot import AzureCloudSnapshot
+
+
+class AzureCloudImage(Image):
+ """Azure Cloud backed image."""
+
+ platform_name = 'azurecloud'
+
+ def __init__(self, platform, config, image_id):
+ """Set up image.
+
+ @param platform: platform object
+ @param config: image configuration
+ @param image_id: image id used to boot instance
+ """
+ super(AzureCloudImage, self).__init__(platform, config)
+ self._img_instance = None
+ self.image_id = image_id
+
+ @property
+ def _instance(self):
+ """Internal use only, returns a running instance"""
+ if not self._img_instance:
+ self._img_instance = self.platform.create_instance(
+ self.properties, self.config, self.features,
+ self.image_id, user_data=None)
+ self._img_instance.start(wait=True, wait_for_cloud_init=True)
+ return self._img_instance
+
+ def destroy(self):
+ """Delete the instance used to create a custom image."""
+ if self._img_instance:
+ LOG.debug('Deleting backing instance %s',
+ self._img_instance.vm_name)
+ delete_vm = self.platform.compute_client.virtual_machines.delete(
+ self.platform.resource_group.name, self._img_instance.vm_name)
+ delete_vm.wait()
+
+ super(AzureCloudImage, self).destroy()
+
+ def _execute(self, *args, **kwargs):
+ """Execute command in image, modifying image."""
+ LOG.debug('executing commands on image')
+ self._instance.start(wait=True)
+ return self._instance._execute(*args, **kwargs)
+
+ def push_file(self, local_path, remote_path):
+ """Copy file at 'local_path' to instance at 'remote_path'."""
+ LOG.debug('pushing file to image')
+ return self._instance.push_file(local_path, remote_path)
+
+ def run_script(self, *args, **kwargs):
+ """Run script in image, modifying image.
+
+ @return_value: script output
+ """
+ LOG.debug('running script on image')
+ self._instance.start()
+ return self._instance.run_script(*args, **kwargs)
+
+ def snapshot(self):
+ """ Create snapshot (image) of instance, wait until done.
+
+ If no instance has been booted, base image is returned.
+ Otherwise runs the clean script, deallocates, generalizes
+ and creates custom image from instance.
+ """
+ LOG.debug('creating snapshot of image')
+ if not self._img_instance:
+ LOG.debug('No existing image, snapshotting base image')
+ return AzureCloudSnapshot(self.platform, self.properties,
+ self.config, self.features,
+ self._instance.vm_name,
+ delete_on_destroy=False)
+
+ LOG.debug('creating snapshot from instance: %s', self._img_instance)
+ if self.config.get('boot_clean_script'):
+ self._img_instance.run_script(self.config.get('boot_clean_script'))
+
+ LOG.debug('deallocating instance %s', self._instance.vm_name)
+ deallocate = self.platform.compute_client.virtual_machines.deallocate(
+ self.platform.resource_group.name, self._instance.vm_name)
+ deallocate.wait()
+
+ LOG.debug('generalizing instance %s', self._instance.vm_name)
+ self.platform.compute_client.virtual_machines.generalize(
+ self.platform.resource_group.name, self._instance.vm_name)
+
+ image_params = {
+ "location": self.platform.location,
+ "properties": {
+ "sourceVirtualMachine": {
+ "id": self._img_instance.instance.id
+ }
+ }
+ }
+ LOG.debug('updating resource group image %s', self._instance.vm_name)
+ self.platform.compute_client.images.create_or_update(
+ self.platform.resource_group.name, self._instance.vm_name,
+ image_params)
+
+ LOG.debug('destroying self')
+ self.destroy()
+
+ LOG.debug('snapshot complete')
+ return AzureCloudSnapshot(self.platform, self.properties, self.config,
+ self.features, self._instance.vm_name)
+
+# vi: ts=4 expandtab
diff --git a/tests/cloud_tests/platforms/azurecloud/instance.py b/tests/cloud_tests/platforms/azurecloud/instance.py
new file mode 100644
index 00000000..f1e28a96
--- /dev/null
+++ b/tests/cloud_tests/platforms/azurecloud/instance.py
@@ -0,0 +1,248 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+
+"""Base Azure Cloud instance."""
+
+from datetime import datetime, timedelta
+from urllib.parse import urlparse
+from time import sleep
+import traceback
+import os
+
+
+# pylint: disable=no-name-in-module
+from azure.storage.blob import BlockBlobService, BlobPermissions
+from msrestazure.azure_exceptions import CloudError
+
+from tests.cloud_tests import LOG
+
+from ..instances import Instance
+
+
+class AzureCloudInstance(Instance):
+ """Azure Cloud backed instance."""
+
+ platform_name = 'azurecloud'
+
+ def __init__(self, platform, properties, config,
+ features, image_id, 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_id: image to find and/or use
+ @param user_data: test user-data to pass to instance
+ """
+ super(AzureCloudInstance, self).__init__(
+ platform, image_id, properties, config, features)
+
+ self.ssh_port = 22
+ self.ssh_ip = None
+ self.instance = None
+ self.image_id = image_id
+ self.vm_name = 'ci-azure-i-%s' % self.platform.tag
+ self.user_data = user_data
+ 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'])
+ self.blob_client, self.container, self.blob = None, None, None
+
+ def start(self, wait=True, wait_for_cloud_init=False):
+ """Start instance with the platforms NIC."""
+ if self.instance:
+ return
+ data = self.image_id.split('-')
+ release, support = data[2].replace('_', '.'), data[3]
+ sku = '%s-%s' % (release, support) if support == 'LTS' else release
+ image_resource_id = '/subscriptions/%s' \
+ '/resourceGroups/%s' \
+ '/providers/Microsoft.Compute/images/%s' % (
+ self.platform.subscription_id,
+ self.platform.resource_group.name,
+ self.image_id)
+ storage_uri = "http://%s.blob.core.windows.net" \
+ % self.platform.storage.name
+ with open(self.ssh_pubkey_file, 'r') as key:
+ ssh_pub_keydata = key.read()
+
+ image_exists = False
+ try:
+ LOG.debug('finding image in resource group using image_id')
+ self.platform.compute_client.images.get(
+ self.platform.resource_group.name,
+ self.image_id
+ )
+ image_exists = True
+ LOG.debug('image found, launching instance, image_id=%s',
+ self.image_id)
+ except CloudError:
+ LOG.debug(('image not found, launching instance with base image, '
+ 'image_id=%s'), self.image_id)
+ pass
+
+ vm_params = {
+ 'name': self.vm_name,
+ 'location': self.platform.location,
+ 'os_profile': {
+ 'computer_name': 'CI-%s' % self.platform.tag,
+ 'admin_username': self.ssh_username,
+ "customData": self.user_data,
+ "linuxConfiguration": {
+ "disable_password_authentication": True,
+ "ssh": {
+ "public_keys": [{
+ "path": "/home/%s/.ssh/authorized_keys" %
+ self.ssh_username,
+ "keyData": ssh_pub_keydata
+ }]
+ }
+ }
+ },
+ "diagnosticsProfile": {
+ "bootDiagnostics": {
+ "storageUri": storage_uri,
+ "enabled": True
+ }
+ },
+ 'hardware_profile': {
+ 'vm_size': self.platform.vm_size
+ },
+ 'storage_profile': {
+ 'image_reference': {
+ 'id': image_resource_id
+ } if image_exists else {
+ 'publisher': 'Canonical',
+ 'offer': 'UbuntuServer',
+ 'sku': sku,
+ 'version': 'latest'
+ }
+ },
+ 'network_profile': {
+ 'network_interfaces': [{
+ 'id': self.platform.nic.id
+ }]
+ },
+ 'tags': {
+ 'Name': self.platform.tag,
+ }
+ }
+
+ try:
+ self.instance = self.platform.compute_client.virtual_machines.\
+ create_or_update(self.platform.resource_group.name,
+ self.vm_name, vm_params)
+ LOG.debug('creating instance %s from image_id=%s', self.vm_name,
+ self.image_id)
+ except CloudError:
+ raise RuntimeError('failed creating instance:\n{}'.format(
+ traceback.format_exc()))
+
+ if wait:
+ self.instance.wait()
+ self.ssh_ip = self.platform.network_client.\
+ public_ip_addresses.get(
+ self.platform.resource_group.name,
+ self.platform.public_ip.name
+ ).ip_address
+ self._wait_for_system(wait_for_cloud_init)
+
+ self.instance = self.instance.result()
+ self.blob_client, self.container, self.blob =\
+ self._get_blob_client()
+
+ def shutdown(self, wait=True):
+ """Finds console log then stopping/deallocates VM"""
+ LOG.debug('waiting on console log before stopping')
+ attempts, exists = 5, False
+ while not exists and attempts:
+ try:
+ attempts -= 1
+ exists = self.blob_client.get_blob_to_bytes(
+ self.container, self.blob)
+ LOG.debug('found console log')
+ except Exception as e:
+ if attempts:
+ LOG.debug('Unable to find console log, '
+ '%s attempts remaining', attempts)
+ sleep(15)
+ else:
+ LOG.warning('Could not find console log: %s', e)
+ pass
+
+ LOG.debug('stopping instance %s', self.image_id)
+ vm_deallocate = \
+ self.platform.compute_client.virtual_machines.deallocate(
+ self.platform.resource_group.name, self.image_id)
+ if wait:
+ vm_deallocate.wait()
+
+ def destroy(self):
+ """Delete VM and close all connections"""
+ if self.instance:
+ LOG.debug('destroying instance: %s', self.image_id)
+ vm_delete = self.platform.compute_client.virtual_machines.delete(
+ self.platform.resource_group.name, self.image_id)
+ vm_delete.wait()
+
+ self._ssh_close()
+
+ super(AzureCloudInstance, 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 _get_blob_client(self):
+ """
+ Use VM details to retrieve container and blob name.
+ Then Create blob service client for sas token to
+ retrieve console log.
+
+ :return: blob service, container name, blob name
+ """
+ LOG.debug('creating blob service for console log')
+ storage = self.platform.storage_client.storage_accounts.get_properties(
+ self.platform.resource_group.name, self.platform.storage.name)
+
+ keys = self.platform.storage_client.storage_accounts.list_keys(
+ self.platform.resource_group.name, self.platform.storage.name
+ ).keys[0].value
+
+ virtual_machine = self.platform.compute_client.virtual_machines.get(
+ self.platform.resource_group.name, self.instance.name,
+ expand='instanceView')
+
+ blob_uri = virtual_machine.instance_view.boot_diagnostics.\
+ serial_console_log_blob_uri
+
+ container, blob = urlparse(blob_uri).path.split('/')[-2:]
+
+ blob_client = BlockBlobService(
+ account_name=storage.name,
+ account_key=keys)
+
+ sas = blob_client.generate_blob_shared_access_signature(
+ container_name=container, blob_name=blob, protocol='https',
+ expiry=datetime.utcnow() + timedelta(hours=1),
+ permission=BlobPermissions.READ)
+
+ blob_client = BlockBlobService(
+ account_name=storage.name,
+ sas_token=sas)
+
+ return blob_client, container, blob
+
+ def console_log(self):
+ """Instance console.
+
+ @return_value: bytes of this instance’s console
+ """
+ boot_diagnostics = self.blob_client.get_blob_to_bytes(
+ self.container, self.blob)
+ return boot_diagnostics.content
diff --git a/tests/cloud_tests/platforms/azurecloud/platform.py b/tests/cloud_tests/platforms/azurecloud/platform.py
new file mode 100644
index 00000000..cb62a74b
--- /dev/null
+++ b/tests/cloud_tests/platforms/azurecloud/platform.py
@@ -0,0 +1,233 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+
+"""Base Azure Cloud class."""
+
+import os
+import base64
+import traceback
+from datetime import datetime
+from tests.cloud_tests import LOG
+
+# pylint: disable=no-name-in-module
+from azure.common.credentials import ServicePrincipalCredentials
+# pylint: disable=no-name-in-module
+from azure.mgmt.resource import ResourceManagementClient
+# pylint: disable=no-name-in-module
+from azure.mgmt.network import NetworkManagementClient
+# pylint: disable=no-name-in-module
+from azure.mgmt.compute import ComputeManagementClient
+# pylint: disable=no-name-in-module
+from azure.mgmt.storage import StorageManagementClient
+from msrestazure.azure_exceptions import CloudError
+
+from .image import AzureCloudImage
+from .instance import AzureCloudInstance
+from ..platforms import Platform
+
+from cloudinit import util as c_util
+
+
+class AzureCloudPlatform(Platform):
+ """Azure Cloud test platforms."""
+
+ platform_name = 'azurecloud'
+
+ def __init__(self, config):
+ """Set up platform."""
+ super(AzureCloudPlatform, self).__init__(config)
+ self.tag = '%s-%s' % (
+ config['tag'], datetime.now().strftime('%Y%m%d%H%M%S'))
+ self.storage_sku = config['storage_sku']
+ self.vm_size = config['vm_size']
+ self.location = config['region']
+
+ try:
+ self.credentials, self.subscription_id = self._get_credentials()
+
+ self.resource_client = ResourceManagementClient(
+ self.credentials, self.subscription_id)
+ self.compute_client = ComputeManagementClient(
+ self.credentials, self.subscription_id)
+ self.network_client = NetworkManagementClient(
+ self.credentials, self.subscription_id)
+ self.storage_client = StorageManagementClient(
+ self.credentials, self.subscription_id)
+
+ self.resource_group = self._create_resource_group()
+ self.public_ip = self._create_public_ip_address()
+ self.storage = self._create_storage_account(config)
+ self.vnet = self._create_vnet()
+ self.subnet = self._create_subnet()
+ self.nic = self._create_nic()
+ except CloudError:
+ raise RuntimeError('failed creating a resource:\n{}'.format(
+ traceback.format_exc()))
+
+ def create_instance(self, properties, config, features,
+ image_id, user_data=None):
+ """Create an instance
+
+ @param properties: image properties
+ @param config: image configuration
+ @param features: image features
+ @param image_id: string of image id
+ @param user_data: test user-data to pass to instance
+ @return_value: cloud_tests.instances instance
+ """
+ if user_data is not None:
+ user_data = str(base64.b64encode(
+ user_data.encode('utf-8')), 'utf-8')
+
+ return AzureCloudInstance(self, properties, config, features,
+ image_id, user_data)
+
+ def get_image(self, img_conf):
+ """Get image using specified image configuration.
+
+ @param img_conf: configuration for image
+ @return_value: cloud_tests.images instance
+ """
+ ss_region = self.azure_location_to_simplestreams_region()
+
+ filters = [
+ 'arch=%s' % 'amd64',
+ 'endpoint=https://management.core.windows.net/',
+ 'region=%s' % ss_region,
+ 'release=%s' % img_conf['release']
+ ]
+
+ LOG.debug('finding image using streams')
+ image = self._query_streams(img_conf, filters)
+
+ try:
+ image_id = image['id']
+ LOG.debug('found image: %s', image_id)
+ if image_id.find('__') > 0:
+ image_id = image_id.split('__')[1]
+ LOG.debug('image_id shortened to %s', image_id)
+ except KeyError:
+ raise RuntimeError('no images found for %s' % img_conf['release'])
+
+ return AzureCloudImage(self, img_conf, image_id)
+
+ def destroy(self):
+ """Delete all resources in resource group."""
+ LOG.debug("Deleting resource group: %s", self.resource_group.name)
+ delete = self.resource_client.resource_groups.delete(
+ self.resource_group.name)
+ delete.wait()
+
+ def azure_location_to_simplestreams_region(self):
+ """Convert location to simplestreams region"""
+ location = self.location.lower().replace(' ', '')
+ LOG.debug('finding location %s using simple streams', location)
+ regions_file = os.path.join(
+ os.path.dirname(os.path.abspath(__file__)), 'regions.json')
+ region_simplestreams_map = c_util.load_json(
+ c_util.load_file(regions_file))
+ return region_simplestreams_map.get(location, location)
+
+ def _get_credentials(self):
+ """Get credentials from environment"""
+ LOG.debug('getting credentials from environment')
+ cred_file = os.path.expanduser('~/.azure/credentials.json')
+ try:
+ azure_creds = c_util.load_json(
+ c_util.load_file(cred_file))
+ subscription_id = azure_creds['subscriptionId']
+ credentials = ServicePrincipalCredentials(
+ client_id=azure_creds['clientId'],
+ secret=azure_creds['clientSecret'],
+ tenant=azure_creds['tenantId'])
+ return credentials, subscription_id
+ except KeyError:
+ raise RuntimeError('Please configure Azure service principal'
+ ' credentials in %s' % cred_file)
+
+ def _create_resource_group(self):
+ """Create resource group"""
+ LOG.debug('creating resource group')
+ resource_group_name = self.tag
+ resource_group_params = {
+ 'location': self.location
+ }
+ resource_group = self.resource_client.resource_groups.create_or_update(
+ resource_group_name, resource_group_params)
+ return resource_group
+
+ def _create_storage_account(self, config):
+ LOG.debug('creating storage account')
+ storage_account_name = 'storage%s' % datetime.now().\
+ strftime('%Y%m%d%H%M%S')
+ storage_params = {
+ 'sku': {
+ 'name': config['storage_sku']
+ },
+ 'kind': "Storage",
+ 'location': self.location
+ }
+ storage_account = self.storage_client.storage_accounts.create(
+ self.resource_group.name, storage_account_name, storage_params)
+ return storage_account.result()
+
+ def _create_public_ip_address(self):
+ """Create public ip address"""
+ LOG.debug('creating public ip address')
+ public_ip_name = '%s-ip' % self.resource_group.name
+ public_ip_params = {
+ 'location': self.location,
+ 'public_ip_allocation_method': 'Dynamic'
+ }
+ ip = self.network_client.public_ip_addresses.create_or_update(
+ self.resource_group.name, public_ip_name, public_ip_params)
+ return ip.result()
+
+ def _create_vnet(self):
+ """create virtual network"""
+ LOG.debug('creating vnet')
+ vnet_name = '%s-vnet' % self.resource_group.name
+ vnet_params = {
+ 'location': self.location,
+ 'address_space': {
+ 'address_prefixes': ['10.0.0.0/16']
+ }
+ }
+ vnet = self.network_client.virtual_networks.create_or_update(
+ self.resource_group.name, vnet_name, vnet_params)
+ return vnet.result()
+
+ def _create_subnet(self):
+ """create sub-network"""
+ LOG.debug('creating subnet')
+ subnet_name = '%s-subnet' % self.resource_group.name
+ subnet_params = {
+ 'address_prefix': '10.0.0.0/24'
+ }
+ subnet = self.network_client.subnets.create_or_update(
+ self.resource_group.name, self.vnet.name,
+ subnet_name, subnet_params)
+ return subnet.result()
+
+ def _create_nic(self):
+ """Create network interface controller"""
+ LOG.debug('creating nic')
+ nic_name = '%s-nic' % self.resource_group.name
+ nic_params = {
+ 'location': self.location,
+ 'ip_configurations': [{
+ 'name': 'ipconfig',
+ 'subnet': {
+ 'id': self.subnet.id
+ },
+ 'publicIpAddress': {
+ 'id': "/subscriptions/%s"
+ "/resourceGroups/%s/providers/Microsoft.Network"
+ "/publicIPAddresses/%s" % (
+ self.subscription_id, self.resource_group.name,
+ self.public_ip.name),
+ }
+ }]
+ }
+ nic = self.network_client.network_interfaces.create_or_update(
+ self.resource_group.name, nic_name, nic_params)
+ return nic.result()
diff --git a/tests/cloud_tests/platforms/azurecloud/regions.json b/tests/cloud_tests/platforms/azurecloud/regions.json
new file mode 100644
index 00000000..c1b4da20
--- /dev/null
+++ b/tests/cloud_tests/platforms/azurecloud/regions.json
@@ -0,0 +1,42 @@
+{
+ "eastasia": "East Asia",
+ "southeastasia": "Southeast Asia",
+ "centralus": "Central US",
+ "eastus": "East US",
+ "eastus2": "East US 2",
+ "westus": "West US",
+ "northcentralus": "North Central US",
+ "southcentralus": "South Central US",
+ "northeurope": "North Europe",
+ "westeurope": "West Europe",
+ "japanwest": "Japan West",
+ "japaneast": "Japan East",
+ "brazilsouth": "Brazil South",
+ "australiaeast": "Australia East",
+ "australiasoutheast": "Australia Southeast",
+ "southindia": "South India",
+ "centralindia": "Central India",
+ "westindia": "West India",
+ "canadacentral": "Canada Central",
+ "canadaeast": "Canada East",
+ "uksouth": "UK South",
+ "ukwest": "UK West",
+ "westcentralus": "West Central US",
+ "westus2": "West US 2",
+ "koreacentral": "Korea Central",
+ "koreasouth": "Korea South",
+ "francecentral": "France Central",
+ "francesouth": "France South",
+ "australiacentral": "Australia Central",
+ "australiacentral2": "Australia Central 2",
+ "uaecentral": "UAE Central",
+ "uaenorth": "UAE North",
+ "southafricanorth": "South Africa North",
+ "southafricawest": "South Africa West",
+ "switzerlandnorth": "Switzerland North",
+ "switzerlandwest": "Switzerland West",
+ "germanynorth": "Germany North",
+ "germanywestcentral": "Germany West Central",
+ "norwaywest": "Norway West",
+ "norwayeast": "Norway East"
+}
diff --git a/tests/cloud_tests/platforms/azurecloud/snapshot.py b/tests/cloud_tests/platforms/azurecloud/snapshot.py
new file mode 100644
index 00000000..580cc596
--- /dev/null
+++ b/tests/cloud_tests/platforms/azurecloud/snapshot.py
@@ -0,0 +1,58 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+
+"""Base Azure Cloud snapshot."""
+
+from ..snapshots import Snapshot
+
+from tests.cloud_tests import LOG
+
+
+class AzureCloudSnapshot(Snapshot):
+ """Azure Cloud image copy backed snapshot."""
+
+ platform_name = 'azurecloud'
+
+ def __init__(self, platform, properties, config, features, image_id,
+ delete_on_destroy=True):
+ """Set up snapshot.
+
+ @param platform: platform object
+ @param properties: image properties
+ @param config: image config
+ @param features: supported feature flags
+ """
+ super(AzureCloudSnapshot, self).__init__(
+ platform, properties, config, features)
+
+ self.image_id = image_id
+ self.delete_on_destroy = delete_on_destroy
+
+ 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 meta_data: meta_data for the instance
+ @param block: wait until instance is created
+ @param start: start instance and wait until fully started
+ @param use_desc: description of snapshot instance use
+ @return_value: an Instance
+ """
+ if meta_data is not None:
+ raise ValueError("metadata not supported on Azure Cloud tests")
+
+ instance = self.platform.create_instance(
+ self.properties, self.config, self.features,
+ self.image_id, user_data)
+
+ return instance
+
+ def destroy(self):
+ """Clean up snapshot data."""
+ LOG.debug('destroying image %s', self.image_id)
+ if self.delete_on_destroy:
+ self.platform.compute_client.images.delete(
+ self.platform.resource_group.name,
+ self.image_id)
+
+# vi: ts=4 expandtab
diff --git a/tests/cloud_tests/platforms/ec2/image.py b/tests/cloud_tests/platforms/ec2/image.py
index 7bedf59d..d7b2c908 100644
--- a/tests/cloud_tests/platforms/ec2/image.py
+++ b/tests/cloud_tests/platforms/ec2/image.py
@@ -4,6 +4,7 @@
from ..images import Image
from .snapshot import EC2Snapshot
+
from tests.cloud_tests import LOG
diff --git a/tests/cloud_tests/platforms/ec2/platform.py b/tests/cloud_tests/platforms/ec2/platform.py
index f188c27b..7a3d0fe0 100644
--- a/tests/cloud_tests/platforms/ec2/platform.py
+++ b/tests/cloud_tests/platforms/ec2/platform.py
@@ -135,6 +135,7 @@ class EC2Platform(Platform):
def _create_internet_gateway(self):
"""Create Internet Gateway and assign to VPC."""
LOG.debug('creating internet gateway')
+ # pylint: disable=no-member
internet_gateway = self.ec2_resource.create_internet_gateway()
internet_gateway.attach_to_vpc(VpcId=self.vpc.id)
self._tag_resource(internet_gateway)
@@ -190,7 +191,7 @@ class EC2Platform(Platform):
"""Setup AWS EC2 VPC or return existing VPC."""
LOG.debug('creating new vpc')
try:
- vpc = self.ec2_resource.create_vpc(
+ vpc = self.ec2_resource.create_vpc( # pylint: disable=no-member
CidrBlock=self.ipv4_cidr,
AmazonProvidedIpv6CidrBlock=True)
except botocore.exceptions.ClientError as e:
diff --git a/tests/cloud_tests/platforms/lxd/instance.py b/tests/cloud_tests/platforms/lxd/instance.py
index 83c97ab4..2b804a62 100644
--- a/tests/cloud_tests/platforms/lxd/instance.py
+++ b/tests/cloud_tests/platforms/lxd/instance.py
@@ -4,6 +4,7 @@
import os
import shutil
+import time
from tempfile import mkdtemp
from cloudinit.util import load_yaml, subp, ProcessExecutionError, which
@@ -224,7 +225,18 @@ class LXDInstance(Instance):
LOG.debug("%s: deleting container.", self)
self.unfreeze()
self.shutdown()
- self.pylxd_container.delete(wait=True)
+ retries = [1] * 5
+ for attempt, wait in enumerate(retries):
+ try:
+ self.pylxd_container.delete(wait=True)
+ break
+ except Exception:
+ if attempt + 1 >= len(retries):
+ raise
+ LOG.debug('Failed to delete container %s (%s/%s) retrying...',
+ self, attempt + 1, len(retries))
+ time.sleep(wait)
+
self._pylxd_container = None
if self.platform.container_exists(self.name):
diff --git a/tests/cloud_tests/platforms/nocloudkvm/instance.py b/tests/cloud_tests/platforms/nocloudkvm/instance.py
index 33ff3f24..96185b75 100644
--- a/tests/cloud_tests/platforms/nocloudkvm/instance.py
+++ b/tests/cloud_tests/platforms/nocloudkvm/instance.py
@@ -74,6 +74,8 @@ class NoCloudKVMInstance(Instance):
self.pid_file = None
self.console_file = None
self.disk = image_path
+ self.cache_mode = platform.config.get('cache_mode',
+ 'cache=none,aio=native')
self.meta_data = meta_data
def shutdown(self, wait=True):
@@ -113,7 +115,10 @@ class NoCloudKVMInstance(Instance):
pass
if self.pid_file:
- os.remove(self.pid_file)
+ try:
+ os.remove(self.pid_file)
+ except Exception:
+ pass
self.pid = None
self._ssh_close()
@@ -160,13 +165,13 @@ class NoCloudKVMInstance(Instance):
self.ssh_port = self.get_free_port()
cmd = ['./tools/xkvm',
- '--disk', '%s,cache=unsafe' % self.disk,
- '--disk', '%s,cache=unsafe' % seed,
+ '--disk', '%s,%s' % (self.disk, self.cache_mode),
+ '--disk', '%s' % seed,
'--netdev', ','.join(['user',
'hostfwd=tcp::%s-:22' % self.ssh_port,
'dnssearch=%s' % CI_DOMAIN]),
'--', '-pidfile', self.pid_file, '-vnc', 'none',
- '-m', '2G', '-smp', '2', '-nographic',
+ '-m', '2G', '-smp', '2', '-nographic', '-name', self.name,
'-serial', 'file:' + self.console_file]
subprocess.Popen(cmd,
close_fds=True,
diff --git a/tests/cloud_tests/platforms/nocloudkvm/platform.py b/tests/cloud_tests/platforms/nocloudkvm/platform.py
index 85933463..2d1480f5 100644
--- a/tests/cloud_tests/platforms/nocloudkvm/platform.py
+++ b/tests/cloud_tests/platforms/nocloudkvm/platform.py
@@ -29,9 +29,13 @@ class NoCloudKVMPlatform(Platform):
"""
(url, path) = s_util.path_from_mirror_url(img_conf['mirror_url'], None)
- filter = filters.get_filters(['arch=%s' % c_util.get_architecture(),
- 'release=%s' % img_conf['release'],
- 'ftype=disk1.img'])
+ filter = filters.get_filters(
+ [
+ 'arch=%s' % c_util.get_dpkg_architecture(),
+ 'release=%s' % img_conf['release'],
+ 'ftype=disk1.img',
+ ]
+ )
mirror_config = {'filters': filter,
'keep_items': False,
'max_items': 1,
diff --git a/tests/cloud_tests/platforms/platforms.py b/tests/cloud_tests/platforms/platforms.py
index abbfebba..bebdf1c6 100644
--- a/tests/cloud_tests/platforms/platforms.py
+++ b/tests/cloud_tests/platforms/platforms.py
@@ -48,7 +48,7 @@ class Platform(object):
if os.path.exists(filename):
c_util.del_file(filename)
- c_util.subp(['ssh-keygen', '-t', 'rsa', '-b', '4096',
+ c_util.subp(['ssh-keygen', '-m', 'PEM', '-t', 'rsa', '-b', '4096',
'-f', filename, '-P', '',
'-C', 'ubuntu@cloud_test'],
capture=True)
diff --git a/tests/cloud_tests/releases.yaml b/tests/cloud_tests/releases.yaml
index ec5da724..7ddc5b85 100644
--- a/tests/cloud_tests/releases.yaml
+++ b/tests/cloud_tests/releases.yaml
@@ -55,6 +55,8 @@ default_release_config:
# cloud-init, so must pull cloud-init in from repo using
# setup_image.upgrade
upgrade: true
+ azurecloud:
+ boot_timeout: 300
features:
# all currently supported feature flags
@@ -129,6 +131,22 @@ features:
releases:
# UBUNTU =================================================================
+ eoan:
+ # EOL: Jul 2020
+ default:
+ enabled: true
+ release: eoan
+ version: 19.10
+ os: ubuntu
+ feature_groups:
+ - base
+ - debian_base
+ - ubuntu_specific
+ lxd:
+ sstreams_server: https://cloud-images.ubuntu.com/daily
+ alias: eoan
+ setup_overrides: null
+ override_templates: false
disco:
# EOL: Jan 2020
default:
diff --git a/tests/cloud_tests/setup_image.py b/tests/cloud_tests/setup_image.py
index 39f4517f..69e66e3f 100644
--- a/tests/cloud_tests/setup_image.py
+++ b/tests/cloud_tests/setup_image.py
@@ -222,13 +222,14 @@ def setup_image(args, image):
for name, func, desc in handlers if getattr(args, name, None)]
try:
- data = yaml.load(image.read_data("/etc/cloud/build.info", decode=True))
+ data = yaml.safe_load(
+ image.read_data("/etc/cloud/build.info", decode=True))
info = ' '.join(["%s=%s" % (k, data.get(k))
for k in ("build_name", "serial") if k in data])
except Exception as e:
info = "N/A (%s)" % e
- LOG.info('setting up %s (%s)', image, info)
+ LOG.info('setting up image %s (info %s)', image, info)
res = stage.run_stage(
'set up for {}'.format(image), calls, continue_after_error=False)
return res
diff --git a/tests/cloud_tests/testcases/modules/TODO.md b/tests/cloud_tests/testcases/modules/TODO.md
index 0b933b3b..9513cb2d 100644
--- a/tests/cloud_tests/testcases/modules/TODO.md
+++ b/tests/cloud_tests/testcases/modules/TODO.md
@@ -78,11 +78,8 @@ Not applicable to write a test for this as it specifies when something should be
## scripts vendor
Not applicable to write a test for this as it specifies when something should be run.
-## snappy
-2016-11-17: Need test to install snaps from store
-
-## snap-config
-2016-11-17: Need to investigate
+## snap
+2019-12-19: Need to investigate
## spacewalk
diff --git a/tests/cloud_tests/testcases/modules/apt_pipelining_disable.yaml b/tests/cloud_tests/testcases/modules/apt_pipelining_disable.yaml
index bd9b5d08..22a31dc4 100644
--- a/tests/cloud_tests/testcases/modules/apt_pipelining_disable.yaml
+++ b/tests/cloud_tests/testcases/modules/apt_pipelining_disable.yaml
@@ -5,8 +5,7 @@ required_features:
- apt
cloud_config: |
#cloud-config
- apt:
- apt_pipelining: false
+ apt_pipelining: false
collect_scripts:
90cloud-init-pipelining: |
#!/bin/bash
diff --git a/tests/cloud_tests/testcases/modules/apt_pipelining_os.py b/tests/cloud_tests/testcases/modules/apt_pipelining_os.py
index 740dc7c0..2b940a66 100644
--- a/tests/cloud_tests/testcases/modules/apt_pipelining_os.py
+++ b/tests/cloud_tests/testcases/modules/apt_pipelining_os.py
@@ -8,8 +8,8 @@ class TestAptPipeliningOS(base.CloudTestCase):
"""Test apt-pipelining module."""
def test_os_pipelining(self):
- """Test pipelining set to os."""
- out = self.get_data_file('90cloud-init-pipelining')
- self.assertIn('Acquire::http::Pipeline-Depth "0";', out)
+ """test 'os' settings does not write apt config file."""
+ out = self.get_data_file('90cloud-init-pipelining_not_written')
+ self.assertEqual(0, int(out))
# vi: ts=4 expandtab
diff --git a/tests/cloud_tests/testcases/modules/apt_pipelining_os.yaml b/tests/cloud_tests/testcases/modules/apt_pipelining_os.yaml
index cbed3ba3..86d5220b 100644
--- a/tests/cloud_tests/testcases/modules/apt_pipelining_os.yaml
+++ b/tests/cloud_tests/testcases/modules/apt_pipelining_os.yaml
@@ -1,15 +1,14 @@
#
-# Set apt pipelining value to OS
+# Set apt pipelining value to OS, no conf written
#
required_features:
- apt
cloud_config: |
#cloud-config
- apt:
- apt_pipelining: os
+ apt_pipelining: os
collect_scripts:
- 90cloud-init-pipelining: |
+ 90cloud-init-pipelining_not_written: |
#!/bin/bash
- cat /etc/apt/apt.conf.d/90cloud-init-pipelining
+ ls /etc/apt/apt.conf.d/90cloud-init-pipelining | wc -l
# vi: ts=4 expandtab
diff --git a/tests/cloud_tests/testcases/modules/snappy.py b/tests/cloud_tests/testcases/modules/snappy.py
deleted file mode 100644
index 7d17fc5b..00000000
--- a/tests/cloud_tests/testcases/modules/snappy.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# This file is part of cloud-init. See LICENSE file for license information.
-
-"""cloud-init Integration Test Verify Script"""
-from tests.cloud_tests.testcases import base
-
-
-class TestSnappy(base.CloudTestCase):
- """Test snappy module"""
-
- expected_warnings = ('DEPRECATION',)
-
- def test_snappy_version(self):
- """Test snappy version output"""
- out = self.get_data_file('snapd')
- self.assertIn('Status: install ok installed', out)
-
-# vi: ts=4 expandtab
diff --git a/tests/cloud_tests/testcases/modules/snappy.yaml b/tests/cloud_tests/testcases/modules/snappy.yaml
deleted file mode 100644
index 8ac322ae..00000000
--- a/tests/cloud_tests/testcases/modules/snappy.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
-#
-# Install snappy
-#
-# Aug 17, 2018: Disabled due to requiring a proxy for testing
-# tests do not handle the proxy well at this time.
-enabled: False
-required_features:
- - snap
-cloud_config: |
- #cloud-config
- snappy:
- system_snappy: auto
-collect_scripts:
- snapd: |
- #!/bin/bash
- dpkg -s snapd
-
-# vi: ts=4 expandtab
diff --git a/tests/cloud_tests/testcases/modules/ssh_auth_key_fingerprints_disable.py b/tests/cloud_tests/testcases/modules/ssh_auth_key_fingerprints_disable.py
index e7329d48..02935447 100644
--- a/tests/cloud_tests/testcases/modules/ssh_auth_key_fingerprints_disable.py
+++ b/tests/cloud_tests/testcases/modules/ssh_auth_key_fingerprints_disable.py
@@ -11,6 +11,6 @@ class TestSshKeyFingerprintsDisable(base.CloudTestCase):
"""Verify disabled."""
out = self.get_data_file('cloud-init.log')
self.assertIn('Skipping module named ssh-authkey-fingerprints, '
- 'logging of ssh fingerprints disabled', out)
+ 'logging of SSH fingerprints disabled', out)
# vi: ts=4 expandtab
diff --git a/tests/cloud_tests/verify.py b/tests/cloud_tests/verify.py
index 9911ecf2..7018f4d5 100644
--- a/tests/cloud_tests/verify.py
+++ b/tests/cloud_tests/verify.py
@@ -61,12 +61,17 @@ def format_test_failures(test_result):
if not test_result['failures']:
return ''
failure_hdr = ' test failures:'
- failure_fmt = ' * {module}.{class}.{function}\n {error}'
+ failure_fmt = ' * {module}.{class}.{function}\n '
output = []
for failure in test_result['failures']:
if not output:
output = [failure_hdr]
- output.append(failure_fmt.format(**failure))
+ msg = failure_fmt.format(**failure)
+ if failure.get('error'):
+ msg += failure['error']
+ else:
+ msg += failure.get('traceback', '')
+ output.append(msg)
return '\n'.join(output)