Example #1
0
    def _local_delete(self, resource_adapter_name: str,
                      resource_adapter_profile: str, cloudserver_id: str):
        from tortuga.db.models.instanceMapping import InstanceMapping
        from tortuga.db.models.instanceMetadata import InstanceMetadata
        from tortuga.db.resourceAdapterConfigDbHandler import ResourceAdapterConfigDbHandler
        from tortuga.node.nodeApi import NodeApi

        node_api = NodeApi()

        #
        # Assume the node instance ID name is the last item in the
        # delimited cloud server ID
        #
        instance = cloudserver_id.split(":")[-1]

        #
        # Lookup the resource adapter configuration profile...
        #
        rac_api = ResourceAdapterConfigDbHandler()
        rac = rac_api.get(self._sess, resource_adapter_name,
                          resource_adapter_profile)

        #
        # Check the instance mapping to see if there is a matching
        # instance id...
        #
        im_list = self._sess.query(InstanceMapping).filter(
            InstanceMapping.instance == instance,
            InstanceMapping.resource_adapter_configuration_id == rac.id)
        #
        # Found something? Delete it and then return...
        #
        for im in im_list:
            node = im.node
            node_api.deleteNode(self._sess, node.name)

        #
        # Check the instance metadata so see if there is a
        # matching vm_name...
        #
        im_list = self._sess.query(InstanceMetadata).filter(
            InstanceMetadata.key == "vm_name",
            InstanceMetadata.value == instance)
        #
        # Found something? Delete it and then return...
        #
        for im in im_list:
            if im.instance.resource_adapter_configuration_id != rac.id:
                continue
            node = im.instance.node
            node_api.deleteNode(self._sess, node.name)
Example #2
0
class CancelSpotInstanceRequestsCLI(TortugaCli):
    def __init__(self):
        super(CancelSpotInstanceRequestsCLI, self).__init__(validArgCount=1)

        self.nodeApi = NodeApi()

    def parseArgs(self, usage=None):
        self.addOption('--all', action='store_true', default=False,
                       help='Cancel all spot instance requests managed by'
                            ' Tortuga')

        self.addOption('--terminate', action='store_true', default=False,
                       help='Terminate any running (fulfilled) instance(s).')

        super(CancelSpotInstanceRequestsCLI, self).parseArgs(usage=usage)

        if not self.getOptions().all and not self.getArgs():
            self.getParser().error(
                '<spot instance request id> or --all argument must be'
                ' specified')

    def runCommand(self):
        self.parseArgs(
            usage='%prog [--terminate] <spot instance request id|--all>')

        sir_instance_cache_filename = \
            os.path.join(ConfigManager().getRoot(), 'var',
                         'spot-instances.conf')

        cfg = configparser.ConfigParser()
        cfg.read(sir_instance_cache_filename)

        if self.getOptions().all:
            result = self.__get_spot_instance_request_ids(cfg)
        else:
            sir_id = self.getArgs()[0]

            result = [self.__get_spot_instance_request_id(cfg, sir_id)]

        if not result:
            # Nothing to do...
            sys.exit(0)

        self.__cancel_spot_instances(result)

    def __get_spot_instance_request_id(self, cfg, sir_id):
        # Ensure spot instance request id
        if not cfg.has_section(sir_id):
            sys.stderr.write(
                'Spot instance request [{0}] is not managed by'
                ' Tortuga\n'.format(sir_id))

            sys.exit(0)

        resource_adapter_configuration = \
            self.__get_resource_adapter_configuration(cfg, sir_id)

        return sir_id, resource_adapter_configuration

    def __get_resource_adapter_configuration(self, cfg, sir_id): \
            # pylint: disable=no-self-use
        return cfg.get(sir_id, 'resource_adapter_configuration') \
            if cfg.has_option(
                sir_id, 'resource_adapter_configuration') else \
            'default'

    def __get_spot_instance_request_ids(self, cfg):
        result = []

        for sir_id in cfg.sections():
            resource_adapter_configuration = \
                self.__get_resource_adapter_configuration(cfg, sir_id)

            result.append((sir_id, resource_adapter_configuration))

        return result

    def __get_spot_instance_request_map(self, result): \
            # pylint: disable=no-self-use
        sir_map = {}
        adapter_cfg_map = {}

        adapter = Aws()

        # Create map of spot instance requests keyed on EC2 region
        for sir_id, resource_adapter_configuration in result:
            if resource_adapter_configuration not in adapter_cfg_map:
                adapter_cfg = adapter.getResourceAdapterConfig(
                    resource_adapter_configuration)

                adapter_cfg_map[resource_adapter_configuration] = \
                    adapter_cfg
            else:
                adapter_cfg = adapter_cfg_map[
                    resource_adapter_configuration]

            if adapter_cfg['region'].name not in sir_map:
                sir_map[adapter_cfg['region'].name] = []

            sir_map[adapter_cfg['region'].name].append(sir_id)

        return sir_map

    def __cancel_spot_instances(self, result):
        sir_map = self.__get_spot_instance_request_map(result)

        aws_instance_cache = configparser.ConfigParser()
        aws_instance_cache.read('/opt/tortuga/var/aws-instance.conf')

        # Iterate on map cancelling requests in each region
        for region_name, sir_ids in sir_map.iteritems():
            session = boto3.session.Session(region_name=region_name)

            ec2_conn = session.client('ec2')

            if len(sir_ids) == 1:
                print('Cancelling spot instance request [{0}]'
                      ' in region [{1}]'.format(sir_ids[0], region_name))
            else:
                print('Cancelling {0} spot instance requests in'
                      ' region [{1}]'.format(len(sir_ids), region_name))

            response = ec2_conn.describe_spot_instance_requests(
                SpotInstanceRequestIds=sir_ids)

            # Create list of tuples (sir_id, bool) which indicate if the
            # spot instance request should be terminated
            cancelled_spot_instance_requests = []

            for sir in response['SpotInstanceRequests']:
                # All spot instance requests that are 'open' should be
                # terminated to avoid leaving orphaned Tortuga node records
                cancelled_spot_instance_requests.append(
                    (sir['SpotInstanceRequestId'],
                     self.getOptions().terminate or sir['State'] == 'open'))

            result = ec2_conn.cancel_spot_instance_requests(
                SpotInstanceRequestIds=sir_ids)

            # Delete corresponding node entries
            for sir_id, terminate in cancelled_spot_instance_requests:
                if terminate:
                    node_name = self.__get_associated_node(
                        aws_instance_cache, sir_id)
                    if node_name:
                        print('  - Deleting node [{0}]'.format(node_name))

                        self.nodeApi.deleteNode(node_name)

    def __get_associated_node(self, aws_instance_cache, sir_id): \
            # pylint: disable=no-self-use
        node_name = None

        for node_name in aws_instance_cache.sections():
            if aws_instance_cache.has_option(
                    node_name, 'spot_instance_request') and \
                aws_instance_cache.get(
                    node_name, 'spot_instance_request') == sir_id:
                break
        else:
            return None

        return node_name