def _accounts(self):
        def get_session_for_account(cross_account_role, aws_account):
            # get a token for the cross account role and use it to create a session
            try:
                session_name = "{}-scheduler-{}".format(
                    self._service.service_name, aws_account)
                # assume a role
                token = self._sts.assume_role_with_retries(
                    RoleArn=cross_account_role, RoleSessionName=session_name)
                credentials = token["Credentials"]
                # create a session using the assumed role credentials
                return boto3.Session(
                    aws_access_key_id=credentials["AccessKeyId"],
                    aws_secret_access_key=credentials["SecretAccessKey"],
                    aws_session_token=credentials["SessionToken"])
            except Exception as ex:
                self._logger.error(
                    ERR_ASSUMING_ROLE.format(cross_account_role, aws_account,
                                             str(ex)))
                return None

        # keep track of accounts processed
        accounts_done = []

        # return session for lambda account if processing instances in that account
        if self._configuration.schedule_lambda_account:
            accounts_done.append(self._lambda_account)
            yield as_namedtuple(
                "Account", {
                    "session": boto3.Session(),
                    "name": self._lambda_account,
                    "role": None
                })

        # iterate through cross account roles
        for role in self._configuration.cross_account_roles:

            # get the account
            role_elements = role.split(":")
            if len(role_elements) < 5:
                self._logger.error(ERR_INVALID_ARN, role)
                continue

            # test if account already processed
            account = role_elements[4]
            if account in accounts_done:
                self._logger.warning(WARN_DUPLICATE_ACCOUNT, account, role)
                continue

            # get a session for the role
            session = get_session_for_account(role, account)
            if session is not None:
                yield as_namedtuple("Account", {
                    "session": session,
                    "name": account,
                    "role": role
                })
示例#2
0
def test_get_desired_state_and_type_2(mocker):
    instance = {}
    schedule = InstanceSchedule(name='test-1',
                                periods={},
                                timezone='UTC',
                                override_status=None,
                                description=None,
                                use_metrics=None,
                                stop_new_instances=None,
                                schedule_dt=None,
                                use_maintenance_window=True,
                                ssm_maintenance_window=True,
                                enforced=False,
                                hibernate=False,
                                retain_running=False)
    instance['maintenance_window'] = None
    instance["account"] = 'test'
    instance["region"] = 'us-east-1'
    instance["service"] = 'ec2'
    instance["id"] = 'ut12y21232u'
    inst = as_namedtuple('ec2' + "Instance", instance, excludes=["tags"])
    ec2_service = Ec2Service()
    scheduler_configuration = {}
    scheduler = InstanceScheduler(ec2_service, scheduler_configuration)
    mocker.patch.object(scheduler, '_logger')
    inst_state, inst_type = scheduler.get_desired_state_and_type(
        schedule, inst)
    assert inst_state == 'stopped'
    def _scheduled_instances_in_region(self, account, region):

        # use service strategy to get a list of instances that can be scheduled for that service
        for instance in self._service.get_schedulable_instances({
                schedulers.PARAM_SESSION:
                account.session,
                schedulers.PARAM_ACCOUNT:
                account.name,
                schedulers.PARAM_ROLE:
                account.role,
                schedulers.PARAM_REGION:
                region,
                schedulers.PARAM_CONFIG:
                self._configuration,
                schedulers.PARAM_LOGGER:
                self._logger,
                schedulers.PARAM_CONTEXT:
                self._context
        }):
            instance["account"] = account.name
            instance["region"] = region
            instance["service"] = self._service.service_name
            instance["instance_str"] = self._instance_display_str(
                instance["id"], instance["name"])
            inst = as_namedtuple(self._service.service_name + "Instance",
                                 instance,
                                 excludes=["tags"])
            yield inst
    def _transform_returned_resource(self, client, resource, resource_name, tags, tags_as_dict, use_tuple, **kwargs):
        name = ""
        data = ""
        if resource_name == OBJECT_VERSIONS:
            if "Versions" in resource:
                name = "Versions"
                data = resource["Versions"]
            elif "DeleteMarkers" in resource:
                name = "DeleteMarkers"
                data = resource["DeleteMarkers"]
        else:
            name = resource_name
            data = resource

        if resource_name == OBJECTS:
            data["Bucket"] = kwargs.get("Bucket")

        if tags:
            resource["Tags"] = self._get_tags_for_resource(client, resource, resource_name)

        if "ResponseMetadata" in resource:
            resource.pop("ResponseMetadata")

        if tags_as_dict:
            self._convert_tags_to_dictionaries(data)
        if use_tuple:
            return as_namedtuple(name, data, deep=True, namefunc=self._tuple_name_func, exludes=self._tuple_excludes)
        else:
            return resource
示例#5
0
    def describe(self, as_tuple=None, **kwargs):
        """
        This method is to retrieve a pseudo UTC time resource, method parameters are only used signature compatibility
        :param as_tuple: Set to true to return results as immutable named dictionaries instead of dictionaries
        :return: Pseudo time resource
        """
        def use_tuple():
            return (as_tuple is not None and as_tuple) or (as_tuple is None
                                                           and self._as_tuple)

        region = kwargs.get("region")
        result = {
            "Time": datetime.datetime.now(pytz.timezone("UTC")),
            "AwsAccount": self.aws_account,
            "Region": region if region else boto3.Session().region_name
        }

        return [as_namedtuple("Time", result)] if use_tuple() else [result]
    def _transform_returned_resource(self, client, resource, resource_name,
                                     tags, tags_as_dict, use_tuple, **kwargs):
        """
        This method takes the resource from the boto "describe" method and transforms them into the requested
        output format of the service class describe function
        :param client: boto client for the service that can be used to retrieve additional attributes, eg tags
        :param resource: The resource returned from the boto call
        :param resource_name: The name of the resource type
        :param tags: Set true true if the tags must be retrieved for this resource
        :param tags_as_dict: Set to true to convert the tags into Python dictionaries
        :param use_tuple: Set to true to return the resources as un-mutable named tuples instead of dictionaries
        :param kwargs: Additional service specific arguments for the transformation
        :return: The transformed service resource
        """

        # get tags for the resource
        if tags:
            if self._resources_with_tags is None:
                raise Exception("Service {} does not support tags".format(
                    self.service_name))
            if resource_name not in self._resources_with_tags:
                raise Exception(
                    "Resource {} for service {} does not support tags".format(
                        resource_name, self.service_name))
            resource["Tags"] = self._get_tags_for_resource(
                client, resource, resource_name)

        # convert tags to dictionaries
        if tags_as_dict:
            resource = self._convert_tags_to_dictionaries(resource)

        # convert resource to named tuple
        if use_tuple:
            return as_namedtuple(resource_name,
                                 resource,
                                 deep=True,
                                 namefunc=self._tuple_name_func,
                                 exludes=self._tuple_excludes)
        else:
            return resource
    def calculate_schedule_usage_for_period(self,
                                            schedule_name,
                                            start_dt,
                                            stop_dt=None,
                                            logger=None):

        result = {}

        def running_seconds(startdt, stopdt):
            return max(int((stopdt - startdt).total_seconds()), 60)

        def running_hours(startdt, stopdt):
            return int(((stopdt - startdt).total_seconds() - 1) / 3600) + 1

        def make_period(started_dt, stopped_dt):
            running_period = ({
                "begin":
                started_dt,
                "end":
                stopped_dt,
                "billing_hours":
                running_hours(started_dt, stopped_dt),
                "billing_seconds":
                running_seconds(started_dt, stopped_dt)
            })
            return running_period

        self._logger = logger

        stop = stop_dt or start_dt
        if start_dt > stop:
            raise ValueError(ERR_STOP_MUST_BE_LATER_OR_EQUAL_TO_START)

        dt = start_dt if isinstance(start_dt, datetime) else datetime(
            start_dt.year, start_dt.month, start_dt.day)

        config_data = ConfigDynamodbAdapter(self._table.name).config

        while dt <= stop:

            self._configuration = SchedulerConfigBuilder(
                logger=self._logger).build(config_data)
            conf = configuration.SchedulerConfigBuilder(self._logger).build(
                config=config_data, dt=dt)
            schedule = conf.get_schedule(schedule_name)

            timeline = {dt.replace(hour=0, minute=0)}
            for p in schedule.periods:
                begintime = p["period"].begintime
                endtime = p["period"].endtime
                if begintime is None and endtime is None:
                    timeline.add(dt.replace(hour=0, minute=0))
                    timeline.add(dt.replace(hour=23, minute=59))
                else:
                    if begintime:
                        timeline.add(
                            dt.replace(hour=begintime.hour,
                                       minute=begintime.minute))
                    if endtime:
                        timeline.add(
                            dt.replace(hour=endtime.hour,
                                       minute=endtime.minute))

            running_periods = {}
            started = None
            starting_period = None
            current_state = None
            inst = as_namedtuple("Instance", {
                "instance_str": "instance",
                "allow_resize": False
            })
            for tm in sorted(list(timeline)):
                desired_state, instance_type, period = schedule.get_desired_state(
                    inst, self._logger, tm, False)

                if current_state != desired_state:
                    if desired_state == InstanceSchedule.STATE_RUNNING:
                        started = tm
                        current_state = InstanceSchedule.STATE_RUNNING
                        starting_period = period
                    elif desired_state == InstanceSchedule.STATE_STOPPED:
                        stopped = tm
                        desired_state_with_adj_check, _, __ = schedule.get_desired_state(
                            inst, self._logger, tm, True)
                        if desired_state_with_adj_check == InstanceSchedule.STATE_RUNNING:
                            stopped += timedelta(minutes=1)
                        if current_state == InstanceSchedule.STATE_RUNNING:
                            current_state = InstanceSchedule.STATE_STOPPED
                            running_periods[starting_period] = (make_period(
                                started, stopped))

            if current_state == InstanceSchedule.STATE_RUNNING:
                stopped = dt.replace(hour=23, minute=59) + timedelta(minutes=1)
                running_periods[starting_period] = (make_period(
                    started, stopped))

            result[str(dt.date())] = {
                "running_periods":
                running_periods,
                "billing_seconds":
                sum([
                    running_periods[ps]["billing_seconds"]
                    for ps in running_periods
                ]),
                "billing_hours":
                sum([
                    running_periods[ph]["billing_hours"]
                    for ph in running_periods
                ])
            }

            dt += timedelta(days=1)

        return {"schedule": schedule_name, "usage": result}
示例#8
0
    def get_usage(self, start_dt, stop_dt=None, instance=None, logger=None):
        """
           Get running periods for a schedule in a period
           :param instance: instance id
           :param start_dt: start date of the period, None is today
           :param stop_dt: end date of the period, None is today
           :param logger: logger for output of scheduling logic
           :return: dictionary containing the periods in the specified in which instances are running as well as the % saving
           in running hours
           """
        result = {}

        def running_seconds(startdt, stopdt):
            return max(int((stopdt - startdt).total_seconds()), 60)

        def running_hours(startdt, stopdt):
            return int(((stopdt - startdt).total_seconds() - 1) / 3600) + 1

        def make_period(started_dt, stopped_dt, inst_type):
            running_period = ({
                "begin":
                started_dt,
                "end":
                stopped_dt,
                "billing_hours":
                running_hours(started_dt, stopped_dt),
                "billing_seconds":
                running_seconds(started_dt, stopped_dt)
            })
            if inst_type is not None:
                running_period["instancetype"] = inst_type
            return running_period

        self._logger = logger

        stop = stop_dt or start_dt
        if start_dt > stop:
            raise ValueError(ERR_STOP_MUST_BE_LATER_OR_EQUAL_TO_START)

        dt = start_dt if isinstance(start_dt, datetime) else datetime(
            start_dt.year, start_dt.month, start_dt.day)
        while dt <= stop:

            timeline = {dt.replace(hour=0, minute=0)}
            for p in self.periods:
                begintime = p["period"].begintime
                endtime = p["period"].endtime
                if begintime is None and endtime is None:
                    timeline.add(dt.replace(hour=0, minute=0))
                    timeline.add(dt.replace(hour=23, minute=59))
                else:
                    if begintime:
                        timeline.add(
                            dt.replace(hour=begintime.hour,
                                       minute=begintime.minute))
                    if endtime:
                        timeline.add(
                            dt.replace(hour=endtime.hour,
                                       minute=endtime.minute))

            running_periods = {}
            started = None
            starting_period = None
            current_state = None
            instance_type = None
            inst = instance or as_namedtuple("Instance", {
                "instance_str": "instance",
                "allow_resize": False
            })
            for tm in sorted(list(timeline)):
                desired_state, instance_type, period = self.get_desired_state(
                    inst, self._logger, tm)

                if current_state != desired_state:
                    if desired_state == InstanceSchedule.STATE_RUNNING:
                        started = tm
                        current_state = InstanceSchedule.STATE_RUNNING
                        starting_period = period
                    elif desired_state == InstanceSchedule.STATE_STOPPED:
                        stopped = tm
                        if current_state == InstanceSchedule.STATE_RUNNING:
                            current_state = InstanceSchedule.STATE_STOPPED
                            running_periods[starting_period] = (make_period(
                                started, stopped, instance_type))

            if current_state == InstanceSchedule.STATE_RUNNING:
                stopped = dt.replace(hour=23, minute=59) + timedelta(minutes=1)
                running_periods[starting_period] = (make_period(
                    started, stopped, instance_type))

            result[str(dt.date())] = {
                "running_periods":
                running_periods,
                "billing_seconds":
                sum([
                    running_periods[ps]["billing_seconds"]
                    for ps in running_periods
                ]),
                "billing_hours":
                sum([
                    running_periods[ph]["billing_hours"]
                    for ph in running_periods
                ])
            }

            dt += timedelta(days=1)

        return {"schedule": self.name, "usage": result}