diff --git a/doc/topics/cloud/profitbricks.rst b/doc/topics/cloud/profitbricks.rst index b25fb6d082..aa073a5b81 100644 --- a/doc/topics/cloud/profitbricks.rst +++ b/doc/topics/cloud/profitbricks.rst @@ -11,7 +11,7 @@ and disk size without being tied to a particular server size. Dependencies ============ -* profitbricks >= 3.0.0 +* profitbricks >= 4.1.1 Configuration ============= @@ -34,8 +34,10 @@ Configuration # username: user@domain.com password: 123456 - # datacenter_id is the UUID of a pre-existing virtual data center. - datacenter_id: 9e6709a0-6bf9-4bd6-8692-60349c70ce0e + # datacenter is the UUID of a pre-existing virtual data center. + datacenter: 9e6709a0-6bf9-4bd6-8692-60349c70ce0e + # delete_volumes is forcing a deletion of all volumes attached to a server on a deletion of a server + delete_volumes: true # Connect to public LAN ID 1. public_lan: 1 ssh_public_key: /path/to/id_rsa.pub @@ -65,6 +67,13 @@ A list of existing virtual data centers can be retrieved with the following comm salt-cloud -f list_datacenters my-profitbricks-config +A new data center can be created with the following command: + +.. code-block:: bash + + salt-cloud -f create_datacenter my-profitbricks-config name=example location=us/las description="my description" + + Authentication ============== @@ -81,7 +90,9 @@ Here is an example of a profile: profitbricks_staging provider: my-profitbricks-config size: Micro Instance - image: 2f98b678-6e7e-11e5-b680-52540066fee9 + image_alias: 'ubuntu:latest' + # image or image_alias must be provided + # image: 2f98b678-6e7e-11e5-b680-52540066fee9 cores: 2 ram: 4096 public_lan: 1 @@ -117,8 +128,31 @@ Here is an example of a profile: disk_size: 500 db_log: disk_size: 50 - disk_type: HDD - disk_availability_zone: ZONE_3 + disk_type: SSD + +Locations can be obtained using the ``--list-locations`` option for the ``salt-cloud`` +command: + +.. code-block:: bash + + # salt-cloud --list-locations my-profitbricks-config + +Images can be obtained using the ``--list-sizes`` option for the ``salt-cloud`` +command: + +.. code-block:: bash + + # salt-cloud --list-images my-profitbricks-config + +Sizes can be obtained using the ``--list-sizes`` option for the ``salt-cloud`` +command: + +.. code-block:: bash + + # salt-cloud --list-sizes my-profitbricks-config + +Profile Specifics: +------------------ The following list explains some of the important properties. @@ -127,14 +161,21 @@ size .. code-block:: bash - salt-cloud --list-sizes my-profitbricks + salt-cloud --list-sizes my-profitbricks-config image Can be one of the options listed in the output of the following command: .. code-block:: bash - salt-cloud --list-images my-profitbricks + salt-cloud --list-images my-profitbricks-config + +image_alias + Can be one of the options listed in the output of the following command: + +.. code-block:: bash + + salt-cloud -f list_images my-profitbricks-config disk_size This option allows you to override the size of the disk as defined by the @@ -144,9 +185,6 @@ disk_type This option allow the disk type to be set to HDD or SSD. The default is HDD. -disk_availability_zone - This option will provision the volume in the specified availability_zone. - cores This option allows you to override the number of CPU cores as defined by the size. @@ -156,10 +194,6 @@ ram The value must be a multiple of 256, e.g. 256, 512, 768, 1024, and so forth. -availability_zone - This options specifies in which availability zone the server should be - built. Zones include ZONE_1 and ZONE_2. The default is AUTO. - public_lan This option will connect the server to the specified public LAN. If no LAN exists, then a new public LAN will be created. The value accepts a LAN @@ -179,9 +213,6 @@ public_firewall_rules icmp_type: icmp_code: -nat - This option will enable NAT on the private NIC. - private_lan This option will connect the server to the specified private LAN. If no LAN exists, then a new private LAN will be created. The value accepts a LAN @@ -209,7 +240,7 @@ ssh_public_key ssh_interface This option will use the private LAN IP for node connections (such as - bootstrapping the node) instead of the public LAN IP. The value accepts + as bootstrapping the node) instead of the public LAN IP. The value accepts 'private_lan'. cpu_family @@ -228,5 +259,5 @@ wait_for_timeout The timeout to wait in seconds for provisioning resources such as servers. The default wait_for_timeout is 15 minutes. -For more information concerning cloud profiles, see :ref:`here -`. +For more information concerning cloud profiles, see :doc:`here +`. diff --git a/salt/cloud/clouds/profitbricks.py b/salt/cloud/clouds/profitbricks.py index 9a4d4799d9..74c6a2dddc 100644 --- a/salt/cloud/clouds/profitbricks.py +++ b/salt/cloud/clouds/profitbricks.py @@ -48,6 +48,9 @@ Set up the cloud configuration at ``/etc/salt/cloud.providers`` or availability_zone: ZONE_1 # Name or UUID of the HDD image to use. image: + # Image alias could be provided instead of image. + # Example 'ubuntu:latest' + #image_alias: # Size of the node disk in GB (overrides server size). disk_size: 40 # Type of disk (HDD or SSD). @@ -96,6 +99,7 @@ import logging import os import pprint import time +from distutils.version import LooseVersion # Import salt libs import salt.utils.cloud @@ -112,11 +116,12 @@ from salt.exceptions import ( # Import 3rd-party libs from salt.ext import six try: + import profitbricks from profitbricks.client import ( ProfitBricksService, Server, NIC, Volume, FirewallRule, Datacenter, LoadBalancer, LAN, - PBNotFoundError + PBNotFoundError, PBError ) HAS_PROFITBRICKS = True except ImportError: @@ -153,6 +158,13 @@ def get_configured_provider(): ) +def version_compatible(version): + ''' + Checks profitbricks version + ''' + return LooseVersion(profitbricks.API_VERSION) >= LooseVersion(version) + + def get_dependencies(): ''' Warn if dependencies are not met. @@ -183,6 +195,31 @@ def get_conn(): ) +def avail_locations(call=None): + ''' + Return a dict of all available VM locations on the cloud provider with + relevant data + ''' + if call == 'action': + raise SaltCloudSystemExit( + 'The avail_images function must be called with ' + '-f or --function, or with the --list-locations option' + ) + + ret = {} + conn = get_conn() + + for item in conn.list_locations()['items']: + reg, loc = item['id'].split('/') + location = {'id': item['id']} + + if reg not in ret: + ret[reg] = {} + + ret[reg][loc] = location + return ret + + def avail_images(call=None): ''' Return a list of the images that are on the provider @@ -195,18 +232,51 @@ def avail_images(call=None): ret = {} conn = get_conn() - datacenter = get_datacenter(conn) for item in conn.list_images()['items']: - if (item['properties']['location'] == - datacenter['properties']['location']): - image = {'id': item['id']} - image.update(item['properties']) - ret[image['name']] = image + image = {'id': item['id']} + image.update(item['properties']) + ret[image['name']] = image return ret +def list_images(call=None, kwargs=None): + ''' + List all the images with alias by location + + CLI Example: + + .. code-block:: bash + + salt-cloud -f list_images my-profitbricks-config location=us/las + ''' + if call != 'function': + raise SaltCloudSystemExit( + 'The list_images function must be called with ' + '-f or --function.' + ) + + if not version_compatible('4.0'): + raise SaltCloudNotFound( + "The 'image_alias' feature requires the profitbricks " + "SDK v4.0.0 or greater." + ) + + ret = {} + conn = get_conn() + + if kwargs.get('location') is not None: + item = conn.get_location(kwargs.get('location'), 3) + ret[item['id']] = {'image_alias': item['properties']['imageAliases']} + return ret + + for item in conn.list_locations(3)['items']: + ret[item['id']] = {'image_alias': item['properties']['imageAliases']} + + return ret + + def avail_sizes(call=None): ''' Return a dict of all available VM sizes on the cloud provider with @@ -288,13 +358,24 @@ def get_datacenter_id(): ''' Return datacenter ID from provider configuration ''' - return config.get_cloud_config_value( + datacenter_id = config.get_cloud_config_value( 'datacenter_id', get_configured_provider(), __opts__, search_global=False ) + conn = get_conn() + + try: + conn.get_datacenter(datacenter_id=datacenter_id) + except PBNotFoundError: + log.error('Failed to get datacenter: {0}'.format( + datacenter_id)) + raise + + return datacenter_id + def list_loadbalancers(call=None): ''' @@ -373,7 +454,8 @@ def create_datacenter(call=None, kwargs=None): .. code-block:: bash - salt-cloud -f create_datacenter profitbricks name=mydatacenter location=us/las description="my description" + salt-cloud -f create_datacenter profitbricks name=mydatacenter + location=us/las description="my description" ''' if call != 'function': raise SaltCloudSystemExit( @@ -492,6 +574,7 @@ def list_nodes(conn=None, call=None): for item in nodes['items']: node = {'id': item['id']} node.update(item['properties']) + node['state'] = node.pop('vmState') ret[node['name']] = node return ret @@ -517,10 +600,13 @@ def list_nodes_full(conn=None, call=None): for item in nodes['items']: node = {'id': item['id']} node.update(item['properties']) + node['state'] = node.pop('vmState') node['public_ips'] = [] node['private_ips'] = [] if item['entities']['nics']['items'] > 0: for nic in item['entities']['nics']['items']: + if len(nic['properties']['ips']) > 0: + pass ip_address = nic['properties']['ips'][0] if salt.utils.cloud.is_public_ip(ip_address): node['public_ips'].append(ip_address) @@ -673,6 +759,23 @@ def get_key_filename(vm_): return key_filename +def signal_event(vm_, event, description): + args = __utils__['cloud.filter_event']( + event, + vm_, + ['name', 'profile', 'provider', 'driver'] + ) + + __utils__['cloud.fire_event']( + 'event', + description, + 'salt/cloud/{0}/creating'.format(vm_['name']), + args=args, + sock_dir=__opts__['sock_dir'], + transport=__opts__['transport'] + ) + + def create(vm_): ''' Create a single VM from a data dict @@ -680,22 +783,24 @@ def create(vm_): try: # Check for required profile parameters before sending any API calls. if (vm_['profile'] and - config.is_profile_configured(__opts__, - (__active_provider_name__ or - 'profitbricks'), - vm_['profile']) is False): + config.is_profile_configured(__opts__, + (__active_provider_name__ or + 'profitbricks'), + vm_['profile']) is False): return False except AttributeError: pass - __utils__['cloud.fire_event']( - 'event', - 'starting create', - 'salt/cloud/{0}/creating'.format(vm_['name']), - args=__utils__['cloud.filter_event']('creating', vm_, ['name', 'profile', 'provider', 'driver']), - sock_dir=__opts__['sock_dir'], - transport=__opts__['transport'] - ) + if 'image_alias' in vm_ and not version_compatible('4.0'): + raise SaltCloudNotFound( + "The 'image_alias' parameter requires the profitbricks " + "SDK v4.0.0 or greater." + ) + + if 'image' not in vm_ and 'image_alias' not in vm_: + log.error('The image or image_alias parameter is required.') + + signal_event(vm_, 'creating', 'starting create') data = None datacenter_id = get_datacenter_id() @@ -712,14 +817,7 @@ def create(vm_): # Assembla the composite server object. server = _get_server(vm_, volumes, nics) - __utils__['cloud.fire_event']( - 'event', - 'requesting instance', - 'salt/cloud/{0}/requesting'.format(vm_['name']), - args=__utils__['cloud.filter_event']('requesting', vm_, ['name', 'profile', 'provider', 'driver']), - sock_dir=__opts__['sock_dir'], - transport=__opts__['transport'] - ) + signal_event(vm_, 'requesting', 'requesting instance') try: data = conn.create_server(datacenter_id=datacenter_id, server=server) @@ -728,11 +826,20 @@ def create(vm_): _wait_for_completion(conn, data, get_wait_timeout(vm_), 'create_server') - except Exception as exc: # pylint: disable=W0703 + except PBError as exc: log.error( 'Error creating {0} on ProfitBricks\n\n' 'The following exception was thrown by the profitbricks library ' - 'when trying to run the initial deployment: \n{1}'.format( + 'when trying to run the initial deployment: \n{1}:\n{2}'.format( + vm_['name'], exc, exc.content + ), + exc_info_on_loglevel=logging.DEBUG + ) + return False + except Exception as exc: # pylint: disable=W0703 + log.error( + 'Error creating {0} \n\n' + 'Error: \n{1}'.format( vm_['name'], exc ), exc_info_on_loglevel=logging.DEBUG @@ -754,7 +861,7 @@ def create(vm_): 'Loaded node data for {0}:\nname: {1}\nstate: {2}'.format( vm_['name'], pprint.pformat(data['name']), - data['vmState'] + data['state'] ) ) except Exception as err: @@ -768,7 +875,7 @@ def create(vm_): # Trigger a failure in the wait for IP function return False - running = data['vmState'] == 'RUNNING' + running = data['state'] == 'RUNNING' if not running: # Still not running, trigger another iteration return @@ -807,14 +914,7 @@ def create(vm_): ) ) - __utils__['cloud.fire_event']( - 'event', - 'created instance', - 'salt/cloud/{0}/created'.format(vm_['name']), - args=__utils__['cloud.filter_event']('created', vm_, ['name', 'profile', 'provider', 'driver']), - sock_dir=__opts__['sock_dir'], - transport=__opts__['transport'] - ) + signal_event(vm_, 'created', 'created instance') if 'ssh_host' in vm_: vm_['key_filename'] = get_key_filename(vm_) @@ -859,9 +959,32 @@ def destroy(name, call=None): datacenter_id = get_datacenter_id() conn = get_conn() node = get_node(conn, name) + attached_volumes = None + + delete_volumes = config.get_cloud_config_value( + 'delete_volumes', + get_configured_provider(), + __opts__, + search_global=False + ) + # Get volumes before the server is deleted + attached_volumes = conn.get_attached_volumes( + datacenter_id=datacenter_id, + server_id=node['id'] + ) conn.delete_server(datacenter_id=datacenter_id, server_id=node['id']) + # The server is deleted and now is safe to delete the volumes + if delete_volumes: + for vol in attached_volumes['items']: + log.debug('Deleting volume {0}'.format(vol['id'])) + conn.delete_volume( + datacenter_id=datacenter_id, + volume_id=vol['id'] + ) + log.debug('Deleted volume {0}'.format(vol['id'])) + __utils__['cloud.fire_event']( 'event', 'destroyed instance', @@ -1010,14 +1133,17 @@ def _get_system_volume(vm_): volume = Volume( name='{0} Storage'.format(vm_['name']), size=disk_size, - image=get_image(vm_)['id'], disk_type=get_disk_type(vm_), ssh_keys=ssh_keys ) - # Set volume availability zone if defined in the cloud profile - if 'disk_availability_zone' in vm_: - volume.availability_zone = vm_['disk_availability_zone'] + if 'image_alias' in vm_.keys(): + volume.image_alias = vm_['image_alias'] + else: + volume.image = get_image(vm_)['id'] + # Set volume availability zone if defined in the cloud profile + if 'disk_availability_zone' in vm_: + volume.availability_zone = vm_['disk_availability_zone'] return volume @@ -1109,4 +1235,4 @@ def _wait_for_completion(conn, promise, wait_timeout, msg): raise Exception( 'Timed out waiting for async operation ' + msg + ' "' + str( promise['requestId'] - ) + '" to complete.') + ) + '" to complete.') diff --git a/salt/config/__init__.py b/salt/config/__init__.py index 4f77bcfe19..d37955398a 100644 --- a/salt/config/__init__.py +++ b/salt/config/__init__.py @@ -124,15 +124,6 @@ VALID_OPTS = { # master address will not be split into IP and PORT. 'master_uri_format': str, - # The following optiosn refer to the Minion only, and they specify - # the details of the source address / port to be used when connecting to - # the Master. This is useful when dealing withmachines where due to firewall - # rules you are restricted to use a certain IP/port combination only. - 'source_interface_name': str, - 'source_address': str, - 'source_ret_port': (six.string_types, int), - 'source_publish_port': (six.string_types, int), - # The fingerprint of the master key may be specified to increase security. Generate # a master fingerprint with `salt-key -F master` 'master_finger': str, @@ -174,10 +165,6 @@ VALID_OPTS = { # The master_pubkey_signature must also be set for this. 'master_use_pubkey_signature': bool, - # Enable master stats eveents to be fired, these events will contain information about - # what commands the master is processing and what the rates are of the executions - 'master_stats': bool, - 'master_stats_event_iter': int, # The key fingerprint of the higher-level master for the syndic to verify it is talking to the # intended master 'syndic_finger': str, @@ -255,10 +242,7 @@ VALID_OPTS = { 'autoload_dynamic_modules': bool, # Force the minion into a single environment when it fetches files from the master - 'saltenv': str, - - # Prevent saltenv from being overriden on the command line - 'lock_saltenv': bool, + 'environment': str, # Force the minion into a single pillar root when it fetches pillar data from the master 'pillarenv': str, @@ -1153,9 +1137,6 @@ VALID_OPTS = { # part of the extra_minion_data param # Subconfig entries can be specified by using the ':' notation (e.g. key:subkey) 'pass_to_ext_pillars': (six.string_types, list), - - # Used by salt.modules.dockermod.compare_container_networks to specify which keys are compared - 'docker.compare_container_networks': dict, } # default configurations @@ -1164,10 +1145,6 @@ DEFAULT_MINION_OPTS = { 'master': 'salt', 'master_type': 'str', 'master_uri_format': 'default', - 'source_interface_name': '', - 'source_address': '', - 'source_ret_port': 0, - 'source_publish_port': 0, 'master_port': 4506, 'master_finger': '', 'master_shuffle': False, @@ -1200,8 +1177,7 @@ DEFAULT_MINION_OPTS = { 'random_startup_delay': 0, 'failhard': False, 'autoload_dynamic_modules': True, - 'saltenv': None, - 'lock_saltenv': False, + 'environment': None, 'pillarenv': None, 'pillarenv_from_saltenv': False, 'pillar_opts': False, @@ -1435,11 +1411,6 @@ DEFAULT_MINION_OPTS = { 'extmod_whitelist': {}, 'extmod_blacklist': {}, 'minion_sign_messages': False, - 'docker.compare_container_networks': { - 'static': ['Aliases', 'Links', 'IPAMConfig'], - 'automatic': ['IPAddress', 'Gateway', - 'GlobalIPv6Address', 'IPv6Gateway'], - }, } DEFAULT_MASTER_OPTS = { @@ -1483,8 +1454,7 @@ DEFAULT_MASTER_OPTS = { }, 'top_file_merging_strategy': 'merge', 'env_order': [], - 'saltenv': None, - 'lock_saltenv': False, + 'environment': None, 'default_top': 'base', 'file_client': 'local', 'git_pillar_base': 'master', @@ -1545,8 +1515,6 @@ DEFAULT_MASTER_OPTS = { 'svnfs_saltenv_whitelist': [], 'svnfs_saltenv_blacklist': [], 'max_event_size': 1048576, - 'master_stats': False, - 'master_stats_event_iter': 60, 'minionfs_env': 'base', 'minionfs_mountpoint': '', 'minionfs_whitelist': [], @@ -2444,7 +2412,7 @@ def syndic_config(master_config_path, # Prepend root_dir to other paths prepend_root_dirs = [ 'pki_dir', 'key_dir', 'cachedir', 'pidfile', 'sock_dir', 'extension_modules', - 'autosign_file', 'autoreject_file', 'token_dir', 'autosign_grains_dir' + 'autosign_file', 'autoreject_file', 'token_dir' ] for config_key in ('log_file', 'key_logfile', 'syndic_log_file'): # If this is not a URI and instead a local path @@ -3339,7 +3307,7 @@ def is_profile_configured(opts, provider, profile_name, vm_=None): alias, driver = provider.split(':') # Most drivers need an image to be specified, but some do not. - non_image_drivers = ['nova', 'virtualbox', 'libvirt', 'softlayer', 'oneandone'] + non_image_drivers = ['nova', 'virtualbox', 'libvirt', 'softlayer', 'oneandone', 'profitbricks'] # Most drivers need a size, but some do not. non_size_drivers = ['opennebula', 'parallels', 'proxmox', 'scaleway', @@ -3624,24 +3592,6 @@ def apply_minion_config(overrides=None, if overrides: opts.update(overrides) - if 'environment' in opts: - if 'saltenv' in opts: - log.warning( - 'The \'saltenv\' and \'environment\' minion config options ' - 'cannot both be used. Ignoring \'environment\' in favor of ' - '\'saltenv\'.', - ) - # Set environment to saltenv in case someone's custom module is - # refrencing __opts__['environment'] - opts['environment'] = opts['saltenv'] - else: - log.warning( - 'The \'environment\' minion config option has been renamed ' - 'to \'saltenv\'. Using %s as the \'saltenv\' config value.', - opts['environment'] - ) - opts['saltenv'] = opts['environment'] - opts['__cli'] = os.path.basename(sys.argv[0]) # No ID provided. Will getfqdn save us? @@ -3794,24 +3744,6 @@ def apply_master_config(overrides=None, defaults=None): if overrides: opts.update(overrides) - if 'environment' in opts: - if 'saltenv' in opts: - log.warning( - 'The \'saltenv\' and \'environment\' master config options ' - 'cannot both be used. Ignoring \'environment\' in favor of ' - '\'saltenv\'.', - ) - # Set environment to saltenv in case someone's custom runner is - # refrencing __opts__['environment'] - opts['environment'] = opts['saltenv'] - else: - log.warning( - 'The \'environment\' master config option has been renamed ' - 'to \'saltenv\'. Using %s as the \'saltenv\' config value.', - opts['environment'] - ) - opts['saltenv'] = opts['environment'] - if len(opts['sock_dir']) > len(opts['cachedir']) + 10: opts['sock_dir'] = os.path.join(opts['cachedir'], '.salt-unix') @@ -3854,7 +3786,7 @@ def apply_master_config(overrides=None, defaults=None): prepend_root_dirs = [ 'pki_dir', 'key_dir', 'cachedir', 'pidfile', 'sock_dir', 'extension_modules', 'autosign_file', 'autoreject_file', 'token_dir', 'syndic_dir', - 'sqlite_queue_dir', 'autosign_grains_dir' + 'sqlite_queue_dir' ] # These can be set to syslog, so, not actual paths on the system diff --git a/tests/integration/cloud/providers/test_profitbricks.py b/tests/integration/cloud/providers/test_profitbricks.py index bb4f30ad60..ea0184a195 100644 --- a/tests/integration/cloud/providers/test_profitbricks.py +++ b/tests/integration/cloud/providers/test_profitbricks.py @@ -18,7 +18,8 @@ from salt.config import cloud_providers_config # Import Third-Party Libs try: - from profitbricks.client import ProfitBricksService # pylint: disable=unused-import + # pylint: disable=unused-import + from profitbricks.client import ProfitBricksService HAS_PROFITBRICKS = True except ImportError: HAS_PROFITBRICKS = False @@ -29,7 +30,7 @@ PROVIDER_NAME = 'profitbricks' DRIVER_NAME = 'profitbricks' -@skipIf(HAS_PROFITBRICKS is False, 'salt-cloud requires >= profitbricks 2.3.0') +@skipIf(HAS_PROFITBRICKS is False, 'salt-cloud requires >= profitbricks 4.1.0') class ProfitBricksTest(ShellCase): ''' Integration tests for the ProfitBricks cloud provider @@ -65,6 +66,7 @@ class ProfitBricksTest(ShellCase): username = config[profile_str][DRIVER_NAME]['username'] password = config[profile_str][DRIVER_NAME]['password'] datacenter_id = config[profile_str][DRIVER_NAME]['datacenter_id'] + self.datacenter_id = datacenter_id if username == '' or password == '' or datacenter_id == '': self.skipTest( 'A username, password, and an datacenter must be provided to ' @@ -77,10 +79,104 @@ class ProfitBricksTest(ShellCase): ''' Tests the return of running the --list-images command for ProfitBricks ''' - image_list = self.run_cloud('--list-images {0}'.format(PROVIDER_NAME)) + list_images = self.run_cloud('--list-images {0}'.format(PROVIDER_NAME)) self.assertIn( - 'Ubuntu-16.04-LTS-server-2016-10-06', - [i.strip() for i in image_list] + 'Ubuntu-16.04-LTS-server-2017-10-01', + [i.strip() for i in list_images] + ) + + def test_list_image_alias(self): + ''' + Tests the return of running the -f list_images + command for ProfitBricks + ''' + cmd = '-f list_images {0}'.format(PROVIDER_NAME) + list_images = self.run_cloud(cmd) + self.assertIn( + '- ubuntu:latest', + [i.strip() for i in list_images] + ) + + def test_list_sizes(self): + ''' + Tests the return of running the --list_sizes command for ProfitBricks + ''' + list_sizes = self.run_cloud('--list-sizes {0}'.format(PROVIDER_NAME)) + self.assertIn( + 'Micro Instance:', + [i.strip() for i in list_sizes] + ) + + def test_list_datacenters(self): + ''' + Tests the return of running the -f list_datacenters + command for ProfitBricks + ''' + cmd = '-f list_datacenters {0}'.format(PROVIDER_NAME) + list_datacenters = self.run_cloud(cmd) + self.assertIn( + self.datacenter_id, + [i.strip() for i in list_datacenters] + ) + + def test_list_nodes(self): + ''' + Tests the return of running the -f list_nodes command for ProfitBricks + ''' + list_nodes = self.run_cloud('-f list_nodes {0}'.format(PROVIDER_NAME)) + self.assertIn( + 'state:', + [i.strip() for i in list_nodes] + ) + + self.assertIn( + 'name:', + [i.strip() for i in list_nodes] + ) + + def test_list_nodes_full(self): + ''' + Tests the return of running the -f list_nodes_full + command for ProfitBricks + ''' + cmd = '-f list_nodes_full {0}'.format(PROVIDER_NAME) + list_nodes = self.run_cloud(cmd) + self.assertIn( + 'state:', + [i.strip() for i in list_nodes] + ) + + self.assertIn( + 'name:', + [i.strip() for i in list_nodes] + ) + + def test_list_location(self): + ''' + Tests the return of running the --list-locations + command for ProfitBricks + ''' + cmd = '--list-locations {0}'.format(PROVIDER_NAME) + list_locations = self.run_cloud(cmd) + + self.assertIn( + 'de/fkb', + [i.strip() for i in list_locations] + ) + + self.assertIn( + 'de/fra', + [i.strip() for i in list_locations] + ) + + self.assertIn( + 'us/las', + [i.strip() for i in list_locations] + ) + + self.assertIn( + 'us/ewr', + [i.strip() for i in list_locations] ) def test_instance(self): @@ -92,11 +188,15 @@ class ProfitBricksTest(ShellCase): self.assertIn( INSTANCE_NAME, [i.strip() for i in self.run_cloud( - '-p profitbricks-test {0}'.format(INSTANCE_NAME), timeout=500 + '-p profitbricks-test {0}'.format(INSTANCE_NAME), + timeout=500 )] ) except AssertionError: - self.run_cloud('-d {0} --assume-yes'.format(INSTANCE_NAME), timeout=500) + self.run_cloud( + '-d {0} --assume-yes'.format(INSTANCE_NAME), + timeout=500 + ) raise # delete the instance @@ -119,4 +219,7 @@ class ProfitBricksTest(ShellCase): # if test instance is still present, delete it if ret in query: - self.run_cloud('-d {0} --assume-yes'.format(INSTANCE_NAME), timeout=500) + self.run_cloud( + '-d {0} --assume-yes'.format(INSTANCE_NAME), + timeout=500 + ) diff --git a/tests/integration/files/conf/cloud.profiles.d/profitbricks.conf b/tests/integration/files/conf/cloud.profiles.d/profitbricks.conf index a328ea466c..a375d4362c 100644 --- a/tests/integration/files/conf/cloud.profiles.d/profitbricks.conf +++ b/tests/integration/files/conf/cloud.profiles.d/profitbricks.conf @@ -1,6 +1,6 @@ profitbricks-test: provider: profitbricks-config - image: Ubuntu-16.04-LTS-server-2016-10-06 + image_alias: 'ubuntu:latest' image_password: volume2016 size: Small Instance disk_size: 10