Beispiel #1
0
class ServiceOffersCommand(CloudClientCommand):
    DEFAULT_TIMEOUT = 600
    EXCHANGE_RATES_SERVICE_URL = 'https://api.exchangeratesapi.io/latest'

    RESOURCE_SERVICE_ATTRIBUTE_NAMESPACES = 'serviceAttributeNamespaces'

    DRY_RUN_KEY = 'dry-run'
    COUNTRY_KEY = 'country'
    SS_ENDPOINT_KEY = 'ss-url'
    SS_USERNAME_KEY = 'ss-user'
    SS_PASSWORD_KEY = 'ss-pass'
    BASE_CURRENCY_KEY = 'currency'
    CONNECTOR_NAME_KEY = 'connector-name'

    def __init__(self):
        super(ServiceOffersCommand, self).__init__()

        self.cc = None
        self.ssapi = None
        self.base_currency = 'EUR'
        self._exchange_rates = {}

    def _initialize(self):
        """
        This method is called once command arguments have been parsed and self.cc and self.ssapi are available
        """
        pass

    def _get_default_timeout(self):
        return self.DEFAULT_TIMEOUT

    def _list_vm_sizes(self):
        """
        Return a list of available VM sizes.
        """
        return self.cc._list_vm_sizes() if self.cc else None

    def _get_cpu(self, vm_size):
        """
        Extract and return the amount of vCPU from the specified vm_size.
        :param vm_size: A 'size' object as in the list returned by _list_vm_sizes().
        :rtype int
        """
        return self.cc._size_get_cpu(vm_size) if self.cc else None

    def _get_ram(self, vm_size):
        """
        Extract and return the size of the RAM memory in MB from the specified vm_size.
        :param vm_size: A 'size' object as in the list returned by _list_vm_sizes().
        :rtype int
        """
        return self.cc._size_get_ram(vm_size) if self.cc else None

    def _get_disk(self, vm_size):
        """
        Extract and return the size of the root disk in GB from the specified vm_size.
        :param vm_size: A 'size' object as in the list returned by _list_vm_sizes().
        :rtype float
        """
        return self.cc._size_get_disk(vm_size) if self.cc else None

    def _get_instance_type(self, vm_size):
        """
        Extract and return the instance type from the specified vm_size.
        :param vm_size: A 'size' object as in the list returned by _list_vm_sizes().
        :rtype int
        """
        return self.cc._size_get_instance_type(vm_size) if self.cc else None

    def _get_country(self):
        """
        Return the 2-letters symbol of the country where the Cloud reside.
        """
        return self.get_option(self.COUNTRY_KEY)

    def _get_supported_os(self, vm_size):
        """
        Return a list of supported OS for the specified vm_size
        :param vm_size: A vm_size object as returned by the method _list_vm_sizes() of the connector
        """
        return ['linux', 'windows']

    def _get_price(self, vm_size, os, root_disk_size=None):
        """
        Get the price for a give vm_size, OS and optionnal root disk size
        :param vm_size: A vm_size object as returned by the method _list_vm_sizes() of the connector
        :param os: The name of the operating system type (eg: 'linux', 'suse', 'windows')
        :param root_disk_size: The size of the root disk in GB
        :return: A tuple containing the price per hour and the currency. eg:(0.24, 'USD') )
        """
        return None, None

    def _get_root_disk_sizes(self, vm_size, os):
        """
        Return a list of available root disk sizes for the given vm_size
        :param vm_size: A vm_size object as returned by the method _list_vm_sizes() of the connector
        :param os: The name of the operating system type (eg: 'linux', 'suse', 'windows')
        :return: A list of available disk sizes
        """
        disk_size = self._get_disk(vm_size)
        if disk_size is not None and disk_size > 0:
            return [disk_size]

        return [
            10, 25, 50, 100, 200, 400, 600, 800, 1000, 1600, 2000, 4000, 6000,
            8000, 10000
        ]

    def _get_root_disk_type(self, vm_size):
        """
        Return the type of the root disk (eg: HDD, SSD, EBS, ...)
        :param vm_size: A vm_size object as returned by the method _list_vm_sizes() of the connector
        """
        return 'Unknown'

    def _get_billing_unit(self, vm_size):
        """
        Return the billing period
        :param vm_size: A vm_size object as returned by the method _list_vm_sizes() of the connector
        """
        return 'MIN'

    def _get_platform(self, vm_size):
        """
        Return the name of platform
        :param vm_size: A vm_size object as returned by the method _list_vm_sizes() of the connector
        """
        pass

    def _get_prefix(self):
        """
        Return the prefix (namespace) to use for extra attributes
        :rtype: str
        """
        pass

    def _get_extra_attributes(self, vm_size):
        """
        Return the billing period
        :param vm_size: A vm_size object as returned by the method _list_vm_sizes() of the connector
        """
        pass

    def get_exchange_rate(self, src_currency, dst_currency):
        if dst_currency not in self._exchange_rates:
            self._exchange_rates[dst_currency] = requests.get(
                self.EXCHANGE_RATES_SERVICE_URL, params={
                    'base': dst_currency
                }).json().get('rates', {})
        return 1.0 / self._exchange_rates.get(dst_currency,
                                              {}).get(src_currency)

    def convert_currency(self, src_currency, dst_currency, amount):
        return amount * self.get_exchange_rate(
            src_currency,
            dst_currency) if src_currency != dst_currency else amount

    @staticmethod
    def _generate_service_attribute_namespace(prefix,
                                              description=None,
                                              acl=None):

        if acl is None:
            acl = {
                "owner": {
                    "principal": "ADMIN",
                    "type": "ROLE"
                },
                "rules": [{
                    "principal": "USER",
                    "type": "ROLE",
                    "right": "VIEW"
                }, {
                    "type": "ROLE",
                    "principal": "ADMIN",
                    "right": "ALL"
                }]
            }

        san = {
            "prefix": prefix,
            "id": "service-attribute-namespace/" + prefix,
            "acl": acl,
            "resourceURI":
            "http://sixsq.com/slipstream/1/ServiceAttributeNamespace",
            "uri": "http://sixsq.com/slipstream/schema/1/" + prefix
        }

        if description is not None:
            san['description'] = description

        return san

    def _add_service_attribute_namespace_if_not_exist(self,
                                                      prefix,
                                                      description=None,
                                                      acl=None):
        verbose = self.get_option('verbose')

        cimi_resp = self.ssapi.cimi_search(
            self.RESOURCE_SERVICE_ATTRIBUTE_NAMESPACES,
            filter='prefix="{0}"'.format(prefix))

        if cimi_resp.count == 0:
            service_attribute_namespace = self._generate_service_attribute_namespace(
                prefix, description, acl)
            if verbose:
                print(
                    '\nAddinging the following service attribute namespace {0} ...\n{1}'
                    .format(prefix, service_attribute_namespace))
            self.ssapi.cimi_add(self.RESOURCE_SERVICE_ATTRIBUTE_NAMESPACES,
                                service_attribute_namespace)

    @staticmethod
    def _generate_service_offer(connector_instance_name,
                                cpu,
                                ram,
                                root_disk,
                                root_disk_type,
                                os,
                                price,
                                instance_type=None,
                                base_currency='EUR',
                                billing_unit='MIN',
                                country=None,
                                platform=None,
                                prefix=None,
                                extra_attributes=None):
        resource_type = 'VM'
        resource_class = 'standard'

        instance_type = instance_type or ''
        instance_type_in_name = ' {0}'.format(
            instance_type) if instance_type else ''
        instance_type_in_description = ' ({0})'.format(
            instance_type) if instance_type else ''

        service_offer = {
            "name":
            "({0:d}/{1:d}/{2:d}{3} {4}) [{5}]".format(cpu, ram, root_disk,
                                                      instance_type_in_name,
                                                      os, country),
            "description":
            "{0} ({1}) with {2:d} vCPU, {3:d} MiB RAM, {4:d} GiB root disk, {5} [{6}]{7}"
            .format(resource_type, resource_class, cpu, ram, root_disk, os,
                    country, instance_type_in_description),
            "resource:vcpu":
            cpu,
            "resource:ram":
            ram,
            "resource:disk":
            root_disk,
            "resource:diskType":
            root_disk_type,
            "resource:type":
            resource_type,
            "resource:class":
            resource_class,
            "resource:country":
            country,
            "resource:platform":
            platform,
            "resource:operatingSystem":
            os,
            "resource:instanceType":
            instance_type,
            "price:unitCost":
            price,  # price  price:currency/price:unitcode
            "price:unitCode":
            "HUR",
            "price:freeUnits":
            0,
            "price:currency":
            base_currency,
            "price:billingUnitCode":
            billing_unit,  # Minimum time quantum for a resource
            "price:billingPeriodCode":
            "MON",  # A bill is sent every billingPeriodCode
            "connector": {
                "href": connector_instance_name
            },
            "acl": {
                "owner": {
                    "type": "ROLE",
                    "principal": "ADMIN"
                },
                "rules": [{
                    "principal": "USER",
                    "right": "VIEW",
                    "type": "ROLE"
                }, {
                    "principal": "ADMIN",
                    "right": "ALL",
                    "type": "ROLE"
                }]
            },
        }
        if extra_attributes:
            if not prefix:
                raise ValueError(
                    'A prefix has to be defined with _get_prefix() to specify extra_attributes'
                )
            for k, v in extra_attributes.items():
                service_offer['{0}:{1}'.format(prefix, k)] = v

        return service_offer

    def _generate_service_offers(self, connector_instance_name):
        service_offers = []

        for vm_size in self._list_vm_sizes():
            cpu = int(self._get_cpu(vm_size))
            ram = int(self._get_ram(vm_size))
            root_disk_type = self._get_root_disk_type(vm_size)
            instance_type = self._get_instance_type(vm_size)
            billing_unit = self._get_billing_unit(vm_size)
            platform = self._get_platform(vm_size)
            country = self._get_country()
            prefix = self._get_prefix()
            extra_attributes = self._get_extra_attributes(vm_size)

            if not platform and self.cc:
                platform = self.cc.cloudName

            for os in self._get_supported_os(vm_size):
                for root_disk in self._get_root_disk_sizes(vm_size, os):
                    price = None
                    raw_price, currency = self._get_price(
                        vm_size, os, root_disk)
                    if raw_price is not None:
                        price = self.convert_currency(currency,
                                                      self.base_currency,
                                                      raw_price)

                    service_offers.append(
                        self._generate_service_offer(
                            connector_instance_name, cpu, ram, root_disk,
                            root_disk_type, os, price, instance_type,
                            self.base_currency, billing_unit, country,
                            platform, prefix, extra_attributes))
        return service_offers

    def do_work(self):
        ch = ConfigHolder(options={
            'verboseLevel': 0,
            'retry': False,
            KEY_RUN_CATEGORY: ''
        },
                          context={'foo': 'bar'})
        self.cc = self.get_connector_class()(ch)
        self.cc._initialization(self.user_info,
                                **self.get_initialization_extra_kwargs())

        self.base_currency = self.get_option(self.BASE_CURRENCY_KEY)

        verbose = self.get_option('verbose')
        dry_run = self.get_option(self.DRY_RUN_KEY)
        ss_endpoint = self.get_option(self.SS_ENDPOINT_KEY)
        ss_username = self.get_option(self.SS_USERNAME_KEY)
        ss_password = self.get_option(self.SS_PASSWORD_KEY)
        connector_instance_name = self.get_option(self.CONNECTOR_NAME_KEY)

        filter_connector_vm = ' and '.join([
            'connector/href="{0}"'.format(connector_instance_name),
            'resource:type="VM"'
        ])

        self.ssapi = Api(endpoint=ss_endpoint, cookie_file=None, insecure=True)
        if not dry_run:
            self.ssapi.login_internal(ss_username, ss_password)

        self._initialize()

        service_offers = self._generate_service_offers(connector_instance_name)

        if not service_offers:
            raise RuntimeError("No service offer found")

        if not dry_run and service_offers:
            self._add_service_attribute_namespace_if_not_exist('resource')
            self._add_service_attribute_namespace_if_not_exist('price')
            prefix = self._get_prefix()
            if prefix:
                self._add_service_attribute_namespace_if_not_exist(prefix)

        service_offers_ids = set()

        for service_offer in service_offers:
            if dry_run:
                print('\nService offer {0}:\n{1}'.format(
                    service_offer['name'], service_offer))
            else:
                cimi_filter = \
                    ' and '.join([filter_connector_vm,
                                  'resource:class="{0}"'.format(service_offer['resource:class']),
                                  'resource:vcpu={0}'.format(service_offer['resource:vcpu']),
                                  'resource:ram={0}'.format(service_offer['resource:ram']),
                                  'resource:disk={0}'.format(service_offer['resource:disk']),
                                  'resource:operatingSystem="{0}"'.format(service_offer['resource:operatingSystem']),
                                  'resource:country="{0}"'.format(service_offer['resource:country']),
                                  'resource:instanceType="{0}"'.format(service_offer['resource:instanceType'])])

                search_result = self.ssapi.cimi_search('serviceOffers',
                                                       filter=cimi_filter)
                result_list = search_result.resources_list
                result_count = len(result_list)

                if result_count == 0:
                    if verbose:
                        print(
                            '\nAddinging the following service offer {0} to {1}...\n{2}'
                            .format(service_offer['name'], ss_endpoint,
                                    service_offer))

                    response = self.ssapi.cimi_add('serviceOffers',
                                                   service_offer)
                    service_offers_ids.add(response.json['resource-id'])
                elif result_count == 1:
                    if verbose:
                        print(
                            '\nUpdating the following service offer {0} to {1}...\n{2}'
                            .format(service_offer['name'], ss_endpoint,
                                    service_offer))

                    response = self.ssapi.cimi_edit(result_list[0].id,
                                                    service_offer)
                    service_offers_ids.add(response.id)
                else:
                    print(
                        '\n!!! Warning duplicates found of following service offer on {0} !!!\n{1}'
                        .format(ss_endpoint, service_offer['name']))
                    for result in result_list:
                        service_offers_ids.add(result.id)

        if not dry_run:
            response = self.ssapi.cimi_search('serviceOffers',
                                              filter=filter_connector_vm)
            old_service_offers_ids = set(r.id for r in response.resources())
            service_offers_ids_to_delete = old_service_offers_ids - service_offers_ids

            for id in service_offers_ids_to_delete:
                if verbose:
                    offer = self.ssapi.cimi_get(id)
                    print(
                        '\nDeleting the following service offer with id {0}...\n{1}'
                        .format(id, offer.json))

                self.ssapi.cimi_delete(id)

        print('\n\nCongratulation, executon completed.')

    def _set_command_specific_options(self, parser):
        parser.add_option('--' + self.BASE_CURRENCY_KEY,
                          dest=self.BASE_CURRENCY_KEY,
                          help='Currency to use',
                          default='EUR',
                          metavar='CURRENCY')

        parser.add_option('--' + self.COUNTRY_KEY,
                          dest=self.COUNTRY_KEY,
                          help='Country where the Cloud reside',
                          default='Unknown',
                          metavar='COUNTRY')

        parser.add_option(
            '--' + self.CONNECTOR_NAME_KEY,
            dest=self.CONNECTOR_NAME_KEY,
            help=
            'Connector instance name to be used as a connector href for service offers',
            default=None,
            metavar='CONNECTOR_NAME')

        parser.add_option(
            '--' + self.SS_ENDPOINT_KEY,
            dest=self.SS_ENDPOINT_KEY,
            help=
            'SlipStream endpoint used where the service offers are pushed to. '
            + '(default: https://nuv.la)',
            default='https://nuv.la',
            metavar='URL')

        parser.add_option('--' + self.SS_USERNAME_KEY,
                          dest=self.SS_USERNAME_KEY,
                          help='Username to be used on SlipStream Endpoint',
                          default=None,
                          metavar='USERNAME')

        parser.add_option('--' + self.SS_PASSWORD_KEY,
                          dest=self.SS_PASSWORD_KEY,
                          help='Password to be used on SlipStream Endpoint',
                          default=None,
                          metavar='PASSWORD')

        parser.add_option('--' + self.DRY_RUN_KEY,
                          dest=self.DRY_RUN_KEY,
                          help='Just print service offers to stdout and exit',
                          action='store_true')

    def _get_command_mandatory_options(self):
        return [self.CONNECTOR_NAME_KEY]
class SlipStreamNodeDriver(NodeDriver):

    """
    SlipStream node driver

    Note: This driver manage KeyPair in a slighty different way than others.
          All configured key pairs are added to VMs at the creation of VMs.
    """

    name = 'SlipStream'
    type = 'slipstream'
    website = 'https://sixsq.com/slipstream'

    features = {'create_node': []}

    NODE_STATE_MAP = {
        # Deployment states
        'initializing': NodeState.PENDING,
        'provisioning': NodeState.REBOOTING, #STARTING,
        'executing': NodeState.RUNNING, #RECONFIGURING,
        'sendingReports': NodeState.RUNNING, #RECONFIGURING,
        'ready': NodeState.RUNNING,
        'finalizing': NodeState.RUNNING, #STOPPING,
        'done': NodeState.TERMINATED,
        'aborted': NodeState.ERROR,
        'cancelled': NodeState.TERMINATED,
        # VirtualMachine states
        'rebooting': NodeState.REBOOTING,
        'poweroff': NodeState.STOPPED,
        'running': NodeState.RUNNING,
        'stopped': NodeState.STOPPED,
        'deleted': NodeState.TERMINATED,
        'terminated': NodeState.TERMINATED,
        'error': NodeState.ERROR,
        'stopping': NodeState.RUNNING,
        'failed': NodeState.ERROR,
        'pending': NodeState.PENDING,
        'paused': NodeState.PAUSED,
        'suspended': NodeState.PAUSED,
    }

    def __init__(self, key, secret=None, secure=True, host='nuv.la', port=None,
                 api_version=None, **kwargs):
        """
        Instanciate a SlipStream node driver.

        :param      key:  Username or API key
        :type       key:  ``str``

        :param      secret:  Password or Secret key
        :type       secret:  ``str``

        :param      secure:  Use secure (HTTPS) connection
        :type       secure:  ``bool``

        :param      host:  Hostname of the SlipStream endpoint (default: nuv.la)
        :type       host:  ``str``

        :param      port:  Port of the SlipStream endpoint (default: 443 if secure else 80)
        :type       port:  ``int``

        :param      api_version:  [Unused]
        :type       api_version:  ``str``

        :keyword    ex_endpoint:  The SlipStream endpoint (example: https://nuv.la)
        :type       ex_endpoint:  ``str``

        :keyword    ex_cookie_file:  Path to a existing cookie file to use instead of key and secret
        :type       ex_cookie_file:  ``str``

        :keyword    ex_login_method:  Login method (internal for username/password and api-key for key/secret)
        :type       ex_login_method:  ``str``
        
        :keyword    ex_login_parameters: Extra parameters to provide to the login method
        :type       ex_login_parameters: ``dict``
        """
        
        insecure = not secure
        endpoint = kwargs.get('ex_endpoint')
        cookie_file = kwargs.get('ex_cookie_file')
        login_method = kwargs.get('ex_login_method', 'internal')
        login_parameters = kwargs.get('ex_login_parameters', {})

        if not endpoint:
            scheme = 'https' if secure else 'http'
            port = ':{}'.format(port) if port else ''
            endpoint = '{}://{}{}'.format(scheme, host, port)

        self.ss_api = Api(endpoint=endpoint, 
                          cookie_file=cookie_file,
                          insecure=insecure)
                          
        if not cookie_file:
            login_params = {}
            
            if login_parameters:
                login_params.update(login_parameters)
                
            if login_method:
                login_params['href'] = 'session-template/{}'.format(login_method)
                
                if login_method == 'internal':
                    if key:
                        login_params['username'] = key
                    if secret:
                        login_params['password'] = secret
                        
                elif login_method == 'api-key':
                    if key:
                        login_params['key'] = key
                    if secret:
                        login_params['secret'] = secret
            
            self.ss_api.login(login_params)

    def list_nodes(self):
        """
        List Nodes (SlipStream deployments)

        :return:    List of node objects
        :rtype:     ``list`` of :class:`Node`
        """
        deployments = self.ss_api.list_deployments(limit=500)
        return [self._deployment_to_node(depl) for depl in deployments]

    def list_sizes(self, location=None):
        """
        List Sizes (SlipStream service offers)list_virt

        :param    location:  Return only sizes for the specified location
        :type     location:  :class:`NodeLocation`

        :return:  List of node size objects
        :rtype:   ``list`` of :class:`.NodeSize`
        """
        filter = 'resource:type="VM"'
        
        if location:
            filter += ' and connector/href = "{}"'.format(location.name)
        
        service_offers = self.ss_api.cimi_search('serviceOffers', filter=filter)

        return [self._service_offer_to_size(so) 
                for so in service_offers.json.get('serviceOffers', [])]

    def list_locations(self):
        """
        List Locations (SlipStream cloud connectors)

        :return:  List of node location objects
        :rtype:   ``list`` of :class:`NodeLocation`
        """
        return [self._cloud_to_location(cloud)
                for cloud in self.ss_api.get_user().configured_clouds]

    def create_node(self, **kwargs):
        """Create a new Node (deploy an application or a component)

        :keyword  name:  Name of the node (set as a SlipStream Tag). (optional)
        :type     name:  ``str``

        :keyword  size:  Size of Cloud resources (SlipStream serviec offer). (optional)
                         If not provided the default of each VM will be used.
                         If provided the size will be applied to all VM.
        :type     size:  :class:`NodeSize`

        :keyword  image:  Image to deploy (SlipStream application or component). (required)
        :type     image:  :class:`NodeImage`

        :keyword  location:  Location where to create the node (SlipStream cloud). (optional)
                             If provided all VM will be started in the specified location.
                             If not provided the default location will be used.
        :type     location:  :class:`NodeLocation`

        :keyword  ex_tags:  List of tags that can be used to identify or annotate a node.
        :type     ex_tags:  ``str`` or ``list``

        :keyword  ex_cloud:  To be used instead of location to specify the Cloud name
                             on which to start VMs.
                             To deploy a component simply specify the Cloud name as a string.
                             To deploy a deployment specify a dict with the nodenames as keys 
                             and Cloud names as values.
        :type     ex_cloud:  ``str`` or ``dict``

        :keyword  ex_parameters:  Parameters to (re)define for this image.
                           To redefine a parameter of a SlipStream application's node 
                           use "<nodename>" as keys and dict of parameters as values.
                           To redefine a parameter of a SlipStream component or 
                           a global parameter use "<parametername>" as the key.
        :type     ex_parameters:  ``dict``

        :keyword  ex_keep_running:  [Only applies to SlipStream applications] 
                                    Define when to terminate or not a deployment 
                                    when it reach the 'Ready' state. 
                                    If scalable is set to True, this value is ignored 
                                    and it will behave as if it was set to 'always'.
        :type     ex_keep_running:  'always' or 'never' or 'on-success' or 'on-error'

        :keyword  ex_multiplicity:  [Only applies to SlipStream applications]
                                    A dict to specify how many instances to start 
                                    per application's node.
                                    Application's nodenames as keys and number of 
                                    instances to start as values.
        :type     ex_multiplicity:  ``dict``

        :keyword  ex_tolerate_failures:  [Only applies to SlipStream applications]
                                         A dict to specify how many failures to tolerate
                                         per application's node.
                                         Nodenames as keys and number of failure to 
                                         tolerate as values.
        :type     ex_tolerate_failures:  ``dict``

        :keyword  ex_check_ssh_key:  Set it to True if you want the SlipStream server 
                                     to check if you have a public ssh key defined. 
                                     Useful if you want to ensure you will have access to VMs.
        :type     ex_check_ssh_key:  ``bool``

        :keyword  ex_scalable:  [Only applies to SlipStream applications]
        :type     ex_scalable:  True to start a scalable deployment. (default: False)

        :return:  The newly created node.
        :rtype:   :class:`Node`

        """
        name = kwargs.get('name')
        size = kwargs.get('size')
        image = kwargs.get('image')
        location = kwargs.get('location')
        
        tags = kwargs.get('ex_tags', [])
        cloud = kwargs.get('ex_cloud')
        parameters = kwargs.get('ex_parameters', {})
        keep_running = kwargs.get('ex_keep_running')
        multiplicity = kwargs.get('ex_multiplicity')
        tolerate_failures = kwargs.get('ex_tolerate_failures')
        check_ssh_key = kwargs.get('ex_check_ssh_key', False)
        scalable = kwargs.get('ex_scalable', False)
        
        path = image.id
        element = self.ss_api.get_element(path)
        
        if not cloud and location:
            if element.type == 'application':
                cloud = {}
                for element_node in self.ss_api.get_application_nodes(path):
                    cloud[element_node.name] = location.name
            else:
                cloud = location.name
                
        if size:
            if element.type == 'application':
                for app_node in self.ss_api.get_application_nodes(path):
                    node_params = parameters.setdefault(app_node.name, {})
                    if 'service-offer' not in node_params:
                        node_params['service-offer'] = size.id
            else:
                if 'service-offer' not in parameters:
                    parameters['service-offer'] = size.id
        
        tags = [tags] if isinstance(tags, basestring) else tags
        if name:
            tags = [name] + tags

        node_id = self.ss_api.deploy(path=path,
                                    cloud=cloud,
                                    parameters=parameters,
                                    tags=tags,
                                    keep_running=keep_running,
                                    scalable=scalable,
                                    multiplicity=multiplicity,
                                    tolerate_failures=tolerate_failures,
                                    check_ssh_key=check_ssh_key)
        return self.ex_get_node(node_id)

    def destroy_node(self, node):
        """"
        Destroy a node.

        :param    node:  The node to be destroyed
        :type     node:  :class:`Node`

        :return:  True if the destroy was successful, False otherwise.
        :rtype:   ``bool``
        """
        try:
            return self.ss_api.terminate(node.id)
        except Exception as e:
            warnings.warn('Exception while destroying node "{}": {}'
                          .format(node.name, 
                                  traceback.format_exc(),
                                  RuntimeWarning))
            return False
    
    def list_images(self, location=None, ex_path=None, ex_recurse=False):
        """ List images (SlipStream components and applications)

        :param    location:  [NOT IMPLEMENTED] 
                             Return only images for the specified location
        :type     location:  :class:`NodeLocation`

        :param    ex_path:  Path on which to search for images. (optional)
                            If not provided it will list the content of the
                            App Store.
        :type     ex_path:  ``str``

        :param    ex_recurse: Recurse into subprojects. (default: False)
                              Setting this value to True can be expensive.

        :return:  list of node image objects.
        :rtype:   ``list`` of :class:`NodeImage`
        """
        if ex_path is None:
            elements = self.ss_api.list_applications()
        else:
            ex_path = ex_path.lstrip('/')
            elements = self.ss_api.list_project_content(path=ex_path,
                                                        recurse=ex_recurse)
        return [self._element_to_image(el)
                for el in elements if el.type in ['component', 'application']]

    def delete_image(self, node_image):
        """
        Deletes a node image from a provider.

        :param    node_image:  Node image object.
        :type     node_image:  :class:`NodeImage`

        :return:  ``True`` if delete_image was successful, ``False`` otherwise.
        :rtype:   ``bool``
        """
        try:
            return self.ss_api.delete_element(path=node_image.id)
        except Exception as e:
            warnings.warn('Exception while deleting image "{}": {}'
                          .format(node.name,
                                  traceback.format_exc(),
                                  RuntimeWarning))
        return False

    def get_image(self, image_id):
        """
        Get an image from it's image_id

        :param    image_id:  Image ID (SlipStream path)
        :type     image_id:  ``str``

        :return:  NodeImage instance on success.
        :rtype:   :class:`NodeImage`:
        """
        return self._element_to_image(self.ss_api.get_element(path=image_id))

    def list_key_pairs(self):
        """
        List all the available key pair objects.

        :return:  List of configured key pairs
        :rtype:   ``list`` of :class:`KeyPair` objects
        """
        return [self._ssh_public_key_to_key_pair(kp)
                for kp in self.ss_api.get_user().ssh_public_keys if kp]

    def get_key_pair(self, name):
        """
        Retrieve a single key pair.

        :param    name:  Name of the key pair to retrieve.
        :type     name:  ``str``

        :return:  A key pair
        :rtype:   :class:`KeyPair`
        """
        return self._list_key_pairs_by_names().get(name)

    def create_key_pair(self, name):
        """
        Create a new key pair object.

        This operation require a working PyCrypto installation with RSA object

        :param name:    Key pair name.
        :type name:     ``str``
        """
        if not have_pycrypto:
            raise RuntimeError('create_key_pair require pyCrypto')

        rsa_keypair = RSA.generate(2048)
        private_key_pem = rsa_keypair.exportKey()
        public_key_openssh = rsa_keypair.publickey().exportKey(format='OpenSSH')
        
        key_pair = self._ssh_public_key_to_key_pair(public_key_openssh, name)
        key_pair.private_key = private_key_pem
        
        self._add_ssh_public_key(key_pair.public_key)
        
        return key_pair

    def import_key_pair_from_string(self, name, key_material):
        """
        Import a new public key from string.

        :param    name:  Key pair name.
        :type     name:  ``str``

        :param    key_material:  Public key material.
        :type     key_material:  ``str``

        :return:  The key pair
        :rtype:   :class:`KeyPair` object
        """
        key_pair = self._ssh_public_key_to_key_pair(key_material, name)
        
        self._add_ssh_public_key(key_pair.public_key)
        
        return key_pair
        
    def import_key_pair_from_file(self, name, key_file_path):
        """
        Import a new public key from string.

        :param    name:  Key pair name.
        :type     name:  ``str``

        :param    key_file_path:  Path to the public key file.
        :type     key_file_path:  ``str``

        :return:  The key pair
        :rtype:   :class:`KeyPair` object
        """
        with open(key_file_path, 'r') as f:
            ssh_public_key = f.read()
        return self.import_key_pair_from_string(name, ssh_public_key)
    
    def delete_key_pair(self, key_pair):
        """
        Delete an existing key pair.

        :param    key_pair:  Key pair object.
        :type     key_pair:  :class:`KeyPair`
        """
        key_pairs = self._list_key_pairs_by_names()
        del key_pairs[key_pair.name]
        
        ssh_public_keys = '\n'.join([kp.public_key for kp in key_pairs.values()])

        return self.ss_api.update_user(ssh_public_keys=ssh_public_keys)

    def ex_get_node(self, node_id):
        """
        Get a node from it's ID
        
        :param    node_id:  ID of the node to retrieve
        :type     node_id:  ``str`` or :class:`UUID`
        
        :return:    The requested node
        :rtype:     :class:`Node`
        """
        return self._deployment_to_node(self.ss_api.get_deployment(node_id))
        
    def ex_wait_node_in_state(self, node, states='Ready', wait_period=10,
                              timeout=600, ignore_abort=False):
        """
        Wait a node to be in one of the specified states (default: Ready)
        
        :param    states:  The names of the states to wait for. (default: Ready)
        :type     states:  ``str`` or ``list``
        
        :param    wait_period:  How many seconds to wait between each loop iteration. (default: 10)
        :type     wait_period: ``int``
        
        :param    timeout:  How many seconds to wait before giving up. (default: 600)
        :type     timeout: ``int``
        
        :param    ignore_abort: If False, raise an exception if the node has failed (default: False)
        :type     ignore_abort: ``bool``
        
        :return:    The state that was reached or raise a LibcloudError if timeout
        :rtype:     ``str``
        """
        _states = [states] if isinstance(states, basestring) else states
        deadline = time.time() + timeout
        
        while time.time() < deadline:
            state = self.ss_api.get_deployment_parameter(node.id, 'ss:state', 
                                                         ignore_abort)
            if state in _states:
                return state
            
            time.sleep(wait_period)
        
        raise LibcloudError(value='Timed out after %s seconds' % (timeout),
                            driver=self)

    def ex_list_virtual_machines(self, location=None, node=None):
        """
        List Virtual Machines (SlipStream virtual machines)

        :param    location:  Return only virtual machines for the specified location
        :type     location:  :class:`NodeLocation`

        :param    node:  List VM belonging to the specified node
        :type     node:  :class:`Node`

        :return:    List of virtualmachine objects
        :rtype:     ``list`` of :class:`VirtualMachine`
        """
        filters = []
        
        if location:
            filters.append('connector/href = "connector/{}"'.format(location.name))
            
        if node:
            filters.append('deployment/href = "run/{}"'.format(node.id))
            
        filter = ' and '.join(filters) or None
        
        virtual_machines = self.ss_api.cimi_search('virtualMachines', filter=filter)

        return [self._virtual_machine_to_node(vm)
                for vm in virtual_machines.json.get('virtualMachines', [])]

    def ex_get_node_parameter(self, node, parameter_name, ignore_abort=True):
        """
        Get the value of a parameter for a node

        :param    node:  The node from which to get the parameter
        :type     node:  :class:`Node`

        :param    parameter_name:  The name of the parameter to retrieve
        :type     parameter_name:  `str`

        :param    ignore_abort: If False, raise an exception if the node has failed (default: True)
        :type     ignore_abort: ``bool``

        """
        return self.ss_api.get_deployment_parameter(node.id, parameter_name, 
                                                    ignore_abort)

    def _state_to_node_state(self, state):
        return self.NODE_STATE_MAP.get(state.lower(),
                                       NodeState.UNKNOWN)
                                       
    def _cloud_to_location(self, cloud):
        country = None
        try:
            filter = 'resource:type="VM" and connector/href = "{}"'.format(cloud)
            service_offer = self.ss_api.cimi_search('serviceOffers', 
                                                    filter=filter, 
                                                    end=1)
            country = service_offer.json['serviceOffers'][0]['resource:country']
        except Exception as e:
            pass
        
        return NodeLocation(id='connector/{}'.format(cloud),
                            name=cloud,
                            country=country,
                            driver=self)
                                       
    def _deployment_to_node(self, deployment):
        return Node(id=str(deployment.id),
                    name=str(deployment.id),
                    state=self._state_to_node_state(deployment.status),
                    public_ips=None,
                    private_ips=None,
                    driver=self,
                    size=None,
                    image=deployment.module,
                    #created_at=deployment.started_at,
                    extra=dict(deployment._asdict()))

    def _service_offer_to_size(self, service_offer):
        return NodeSize(id=service_offer.get('id'),
                        name=service_offer.get('name'),
                        ram=service_offer.get('resource:ram'),
                        disk=service_offer.get('resource:disk'),
                        bandwidth=None,
                        price=service_offer.get('price:unitCost'),
                        driver=self,
                        extra=service_offer)
                        
    def _element_to_image(self, element):
        return NodeImage(id='{}/{}'.format(element.path, element.version),
                         name=element.name,
                         driver=self,
                         extra=dict(element._asdict()))
                         
    def _virtual_machine_to_node(self, virtual_machine):
        ip = virtual_machine.get('ip')
        state = virtual_machine.get('state', 'unknown')

        public_ips = None
        private_ips = None
        try:
            if is_public_subnet(ip):
                public_ips = [ip]
            else:
                private_ips = [ip]
        except:
            pass
        
        return VirtualMachine(id=virtual_machine.get('id'),
                              name=virtual_machine.get('instanceID'),
                              state=self._state_to_node_state(state),
                              public_ips=public_ips,
                              private_ips=private_ips,
                              driver=self,
                              size=virtual_machine.get('serviceOffer', {}).get('href'),
                              image=None,
                              #created_at=virtual_machine.get('created'),
                              extra=dict(virtual_machine))

    def _list_key_pairs_by_names(self):
        return dict([(kp.name, kp) for kp in self.list_key_pairs() 
                     if kp and kp.name])

    def _ssh_public_key_to_key_pair(self, ssh_public_key, name=None):
        key_type, key_content, key_name = self._parse_ssh_public_key(ssh_public_key)
                             
        public_key_name = name if name else key_name                   
        public_key = '{} {} {}'.format(key_type, key_content, public_key_name)
            
        return KeyPair(name=public_key_name,
                       public_key=public_key,
                       fingerprint=None,
                       driver=self,
                       extra={'public_key_type': key_type,
                              'public_key_content': key_content})
                              
    def _parse_ssh_public_key(self, ssh_public_key):
        try:
            key = ssh_public_key.strip('\t\r\n ').split(' ', 2)
            key_type = key[0]
            key_content = key[1]
            key_name = key[2] if len(key) > 2 else ''
        except Exception:
            raise ValueError('Invalid OpenSSH key format for key: {}'
                             .format(ssh_public_key))
        return key_type, key_content, key_name
                              
    def _add_ssh_public_key(self, ssh_public_key):
        user_public_keys = self.ss_api.get_user().ssh_public_keys
        
        user_public_keys.append(ssh_public_key)
        ssh_public_keys = '\n'.join(user_public_keys)
        
        return self.ss_api.update_user(ssh_public_keys=ssh_public_keys)
class ServiceOffersCommand(CloudClientCommand):
    DEFAULT_TIMEOUT = 600
    EXCHANGE_RATES_SERVICE_URL = 'https://api.exchangeratesapi.io/latest'

    RESOURCE_SERVICE_ATTRIBUTE_NAMESPACES = 'serviceAttributeNamespaces'

    DRY_RUN_KEY = 'dry-run'
    COUNTRY_KEY = 'country'
    SS_ENDPOINT_KEY = 'ss-url'
    SS_USERNAME_KEY = 'ss-user'
    SS_PASSWORD_KEY = 'ss-pass'
    BASE_CURRENCY_KEY = 'currency'
    CONNECTOR_NAME_KEY = 'connector-name'

    def __init__(self):
        super(ServiceOffersCommand, self).__init__()

        self.cc = None
        self.ssapi = None
        self.base_currency = 'EUR'
        self._exchange_rates = {}

    def _initialize(self):
        """
        This method is called once command arguments have been parsed and self.cc and self.ssapi are available
        """
        pass

    def _get_default_timeout(self):
        return self.DEFAULT_TIMEOUT

    def _list_vm_sizes(self):
        """
        Return a list of available VM sizes.
        """
        return self.cc._list_vm_sizes() if self.cc else None

    def _get_cpu(self, vm_size):
        """
        Extract and return the amount of vCPU from the specified vm_size.
        :param vm_size: A 'size' object as in the list returned by _list_vm_sizes().
        :rtype int
        """
        return self.cc._size_get_cpu(vm_size) if self.cc else None

    def _get_ram(self, vm_size):
        """
        Extract and return the size of the RAM memory in MB from the specified vm_size.
        :param vm_size: A 'size' object as in the list returned by _list_vm_sizes().
        :rtype int
        """
        return self.cc._size_get_ram(vm_size) if self.cc else None

    def _get_disk(self, vm_size):
        """
        Extract and return the size of the root disk in GB from the specified vm_size.
        :param vm_size: A 'size' object as in the list returned by _list_vm_sizes().
        :rtype float
        """
        return self.cc._size_get_disk(vm_size) if self.cc else None

    def _get_instance_type(self, vm_size):
        """
        Extract and return the instance type from the specified vm_size.
        :param vm_size: A 'size' object as in the list returned by _list_vm_sizes().
        :rtype int
        """
        return self.cc._size_get_instance_type(vm_size) if self.cc else None

    def _get_country(self):
        """
        Return the 2-letters symbol of the country where the Cloud reside.
        """
        return self.get_option(self.COUNTRY_KEY)

    def _get_supported_os(self, vm_size):
        """
        Return a list of supported OS for the specified vm_size
        :param vm_size: A vm_size object as returned by the method _list_vm_sizes() of the connector
        """
        return ['linux', 'windows']

    def _get_price(self, vm_size, os, root_disk_size=None):
        """
        Get the price for a give vm_size, OS and optionnal root disk size
        :param vm_size: A vm_size object as returned by the method _list_vm_sizes() of the connector
        :param os: The name of the operating system type (eg: 'linux', 'suse', 'windows')
        :param root_disk_size: The size of the root disk in GB
        :return: A tuple containing the price per hour and the currency. eg:(0.24, 'USD') )
        """
        return None, None

    def _get_root_disk_sizes(self, vm_size, os):
        """
        Return a list of available root disk sizes for the given vm_size
        :param vm_size: A vm_size object as returned by the method _list_vm_sizes() of the connector
        :param os: The name of the operating system type (eg: 'linux', 'suse', 'windows')
        :return: A list of available disk sizes
        """
        disk_size = self._get_disk(vm_size)
        if disk_size is not None and disk_size > 0:
            return [disk_size]

        return [10, 25, 50, 100, 200, 400, 600, 800, 1000, 1600, 2000, 4000, 6000, 8000, 10000]

    def _get_root_disk_type(self, vm_size):
        """
        Return the type of the root disk (eg: HDD, SSD, EBS, ...)
        :param vm_size: A vm_size object as returned by the method _list_vm_sizes() of the connector
        """
        return 'Unknown'

    def _get_billing_unit(self, vm_size):
        """
        Return the billing period
        :param vm_size: A vm_size object as returned by the method _list_vm_sizes() of the connector
        """
        return 'MIN'

    def _get_platform(self, vm_size):
        """
        Return the name of platform
        :param vm_size: A vm_size object as returned by the method _list_vm_sizes() of the connector
        """
        pass

    def _get_prefix(self):
        """
        Return the prefix (namespace) to use for extra attributes
        :rtype: str
        """
        pass

    def _get_extra_attributes(self, vm_size):
        """
        Return the billing period
        :param vm_size: A vm_size object as returned by the method _list_vm_sizes() of the connector
        """
        pass

    def get_exchange_rate(self, src_currency, dst_currency):
        if dst_currency not in self._exchange_rates:
            self._exchange_rates[dst_currency] = requests.get(self.EXCHANGE_RATES_SERVICE_URL,
                                                              params={'base': dst_currency}).json().get('rates', {})
        return 1.0 / self._exchange_rates.get(dst_currency, {}).get(src_currency)

    def convert_currency(self, src_currency, dst_currency, amount):
        return amount * self.get_exchange_rate(src_currency, dst_currency) if src_currency != dst_currency else amount

    @staticmethod
    def _generate_service_attribute_namespace(prefix, description=None, acl=None):

        if acl is None:
            acl = {
                "owner": {
                    "principal": "ADMIN",
                    "type": "ROLE"
                },
                "rules": [{
                    "principal": "USER",
                    "type": "ROLE",
                    "right": "VIEW"
                }, {
                    "type": "ROLE",
                    "principal": "ADMIN",
                    "right": "ALL"
                }]
            }

        san = {
            "prefix": prefix,
            "id": "service-attribute-namespace/" + prefix,
            "acl": acl,
            "resourceURI": "http://sixsq.com/slipstream/1/ServiceAttributeNamespace",
            "uri": "http://sixsq.com/slipstream/schema/1/" + prefix
        }

        if description is not None:
            san['description'] = description

        return san

    def _add_service_attribute_namespace_if_not_exist(self, prefix, description=None, acl=None):
        verbose = self.get_option('verbose')

        cimi_resp = self.ssapi.cimi_search(self.RESOURCE_SERVICE_ATTRIBUTE_NAMESPACES,
                                           filter='prefix="{0}"'.format(prefix))

        if cimi_resp.count == 0:
            service_attribute_namespace = self._generate_service_attribute_namespace(prefix, description, acl)
            if verbose:
                print('\nAddinging the following service attribute namespace {0} ...\n{1}'
                      .format(prefix, service_attribute_namespace))
            self.ssapi.cimi_add(self.RESOURCE_SERVICE_ATTRIBUTE_NAMESPACES, service_attribute_namespace)

    @staticmethod
    def _generate_service_offer(connector_instance_name, cpu, ram, root_disk, root_disk_type, os, price,
                                instance_type=None, base_currency='EUR', billing_unit='MIN', country=None,
                                platform=None, prefix=None, extra_attributes=None):
        resource_type = 'VM'
        resource_class = 'standard'

        instance_type = instance_type or ''
        instance_type_in_name = ' {0}'.format(instance_type) if instance_type else ''
        instance_type_in_description = ' ({0})'.format(instance_type) if instance_type else ''

        service_offer = {
            "name": "({0:d}/{1:d}/{2:d}{3} {4}) [{5}]".format(cpu, ram, root_disk, instance_type_in_name, os, country),
            "description": "{0} ({1}) with {2:d} vCPU, {3:d} MiB RAM, {4:d} GiB root disk, {5} [{6}]{7}"
                .format(resource_type, resource_class, cpu, ram, root_disk, os, country, instance_type_in_description),
            "resource:vcpu": cpu,
            "resource:ram": ram,
            "resource:disk": root_disk,
            "resource:diskType": root_disk_type,
            "resource:type": resource_type,
            "resource:class": resource_class,
            "resource:country": country,
            "resource:platform": platform,
            "resource:operatingSystem": os,
            "resource:instanceType": instance_type,
            "price:unitCost": price,  # price  price:currency/price:unitcode
            "price:unitCode": "HUR",
            "price:freeUnits": 0,
            "price:currency": base_currency,
            "price:billingUnitCode": billing_unit,  # Minimum time quantum for a resource
            "price:billingPeriodCode": "MON",  # A bill is sent every billingPeriodCode
            "connector": {"href": connector_instance_name},
            "acl": {
                "owner": {
                    "type": "ROLE",
                    "principal": "ADMIN"
                },
                "rules": [{
                    "principal": "USER",
                    "right": "VIEW",
                    "type": "ROLE"
                }, {
                    "principal": "ADMIN",
                    "right": "ALL",
                    "type": "ROLE"
                }]
            },
        }
        if extra_attributes:
            if not prefix:
                raise ValueError('A prefix has to be defined with _get_prefix() to specify extra_attributes')
            for k, v in extra_attributes.items():
                service_offer['{0}:{1}'.format(prefix, k)] = v

        return service_offer

    def _generate_service_offers(self, connector_instance_name):
        service_offers = []

        for vm_size in self._list_vm_sizes():
            cpu = int(self._get_cpu(vm_size))
            ram = int(self._get_ram(vm_size))
            root_disk_type = self._get_root_disk_type(vm_size)
            instance_type = self._get_instance_type(vm_size)
            billing_unit = self._get_billing_unit(vm_size)
            platform = self._get_platform(vm_size)
            country = self._get_country()
            prefix = self._get_prefix()
            extra_attributes = self._get_extra_attributes(vm_size)

            if not platform and self.cc:
                platform = self.cc.cloudName

            for os in self._get_supported_os(vm_size):
                for root_disk in self._get_root_disk_sizes(vm_size, os):
                    price = None
                    raw_price, currency = self._get_price(vm_size, os, root_disk)
                    if raw_price is not None:
                        price = self.convert_currency(currency, self.base_currency, raw_price)

                    service_offers.append(self._generate_service_offer(connector_instance_name, cpu, ram, root_disk,
                                                                       root_disk_type, os, price, instance_type,
                                                                       self.base_currency, billing_unit, country,
                                                                       platform, prefix, extra_attributes))
        return service_offers

    def do_work(self):
        ch = ConfigHolder(options={'verboseLevel': 0,
                                   'retry': False,
                                   KEY_RUN_CATEGORY: ''},
                          context={'foo': 'bar'})
        self.cc = self.get_connector_class()(ch)
        self.cc._initialization(self.user_info, **self.get_initialization_extra_kwargs())

        self.base_currency = self.get_option(self.BASE_CURRENCY_KEY)

        verbose = self.get_option('verbose')
        dry_run = self.get_option(self.DRY_RUN_KEY)
        ss_endpoint = self.get_option(self.SS_ENDPOINT_KEY)
        ss_username = self.get_option(self.SS_USERNAME_KEY)
        ss_password = self.get_option(self.SS_PASSWORD_KEY)
        connector_instance_name = self.get_option(self.CONNECTOR_NAME_KEY)

        filter_connector_vm = ' and '.join(['connector/href="{0}"'.format(connector_instance_name),
                                            'resource:type="VM"'])

        self.ssapi = Api(endpoint=ss_endpoint, cookie_file=None, insecure=True)
        if not dry_run:
            self.ssapi.login_internal(ss_username, ss_password)

        self._initialize()

        service_offers = self._generate_service_offers(connector_instance_name)

        if not service_offers:
            raise RuntimeError("No service offer found")

        if not dry_run and service_offers:
            self._add_service_attribute_namespace_if_not_exist('resource')
            self._add_service_attribute_namespace_if_not_exist('price')
            prefix = self._get_prefix()
            if prefix:
                self._add_service_attribute_namespace_if_not_exist(prefix)

        service_offers_ids = set()

        for service_offer in service_offers:
            if dry_run:
                print('\nService offer {0}:\n{1}'.format(service_offer['name'], service_offer))
            else:
                cimi_filter = \
                    ' and '.join([filter_connector_vm,
                                  'resource:class="{0}"'.format(service_offer['resource:class']),
                                  'resource:vcpu={0}'.format(service_offer['resource:vcpu']),
                                  'resource:ram={0}'.format(service_offer['resource:ram']),
                                  'resource:disk={0}'.format(service_offer['resource:disk']),
                                  'resource:operatingSystem="{0}"'.format(service_offer['resource:operatingSystem']),
                                  'resource:country="{0}"'.format(service_offer['resource:country']),
                                  'resource:instanceType="{0}"'.format(service_offer['resource:instanceType'])])

                search_result = self.ssapi.cimi_search('serviceOffers', filter=cimi_filter)
                result_list = search_result.resources_list
                result_count = len(result_list)

                if result_count == 0:
                    if verbose:
                        print('\nAddinging the following service offer {0} to {1}...\n{2}'.format(service_offer['name'],
                                                                                                  ss_endpoint,
                                                                                                  service_offer))

                    response = self.ssapi.cimi_add('serviceOffers', service_offer)
                    service_offers_ids.add(response.json['resource-id'])
                elif result_count == 1:
                    if verbose:
                        print('\nUpdating the following service offer {0} to {1}...\n{2}'.format(service_offer['name'],
                                                                                                 ss_endpoint,
                                                                                                 service_offer))

                    response = self.ssapi.cimi_edit(result_list[0].id, service_offer)
                    service_offers_ids.add(response.id)
                else:
                    print('\n!!! Warning duplicates found of following service offer on {0} !!!\n{1}'
                          .format(ss_endpoint, service_offer['name']))
                    for result in result_list:
                        service_offers_ids.add(result.id)

        if not dry_run:
            response = self.ssapi.cimi_search('serviceOffers', filter=filter_connector_vm)
            old_service_offers_ids = set(r.id for r in response.resources())
            service_offers_ids_to_delete = old_service_offers_ids - service_offers_ids

            for id in service_offers_ids_to_delete:
                if verbose:
                    offer = self.ssapi.cimi_get(id)
                    print('\nDeleting the following service offer with id {0}...\n{1}'.format(id, offer.json))

                self.ssapi.cimi_delete(id)

        print('\n\nCongratulation, executon completed.')

    def _set_command_specific_options(self, parser):
        parser.add_option('--' + self.BASE_CURRENCY_KEY, dest=self.BASE_CURRENCY_KEY,
                          help='Currency to use', default='EUR', metavar='CURRENCY')

        parser.add_option('--' + self.COUNTRY_KEY, dest=self.COUNTRY_KEY,
                          help='Country where the Cloud reside', default='Unknown', metavar='COUNTRY')

        parser.add_option('--' + self.CONNECTOR_NAME_KEY, dest=self.CONNECTOR_NAME_KEY,
                          help='Connector instance name to be used as a connector href for service offers',
                          default=None, metavar='CONNECTOR_NAME')

        parser.add_option('--' + self.SS_ENDPOINT_KEY, dest=self.SS_ENDPOINT_KEY,
                          help='SlipStream endpoint used where the service offers are pushed to. ' +
                               '(default: https://nuv.la)',
                          default='https://nuv.la', metavar='URL')

        parser.add_option('--' + self.SS_USERNAME_KEY, dest=self.SS_USERNAME_KEY,
                          help='Username to be used on SlipStream Endpoint',
                          default=None, metavar='USERNAME')

        parser.add_option('--' + self.SS_PASSWORD_KEY, dest=self.SS_PASSWORD_KEY,
                          help='Password to be used on SlipStream Endpoint',
                          default=None, metavar='PASSWORD')

        parser.add_option('--' + self.DRY_RUN_KEY, dest=self.DRY_RUN_KEY,
                          help='Just print service offers to stdout and exit',
                          action='store_true')

    def _get_command_mandatory_options(self):
        return [self.CONNECTOR_NAME_KEY]
api = Api(endpoint=service_url)
api.login_apikey(api_key, api_secret)

# Recover deployment information.

deployment = api.cimi_get(deployment_id)

try:
    service_offers = deployment.json['serviceOffers']
except KeyError:
    service_offers = []

# Recover credential for mounting buckets.

depl_params = api.cimi_search('deploymentParameters',
                              filter=deployment_params_filter.format(
                                  deployment_id, 'credential.id'))

credential_id = depl_params.resources_list[0].json['value']

credential = api.cimi_get(credential_id)
credential_key = credential.key
credential_secret = credential.secret

connector_ref = credential.json['connector']['href']

connector = api.cimi_get(connector_ref)

try:
    object_store_endpoint = connector.json['objectStoreEndpoint']
except KeyError: