# Copyright 2015-2017 Capital One Services, LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Resource Scheduling Offhours
============================

Custodian provides for time based filters, that allow for taking periodic
action on a resource, with resource schedule customization based on tag values.
A common use is offhours scheduling for asgs and instances.

Features
========

- Flexible offhours scheduling with opt-in, opt-out selection, and timezone
  support.
- Resume during offhours support.
- Can be combined with other filters to get a particular set (
  resources with tag, vpc, etc).
- Can be combined with arbitrary actions

Policy Configuration
====================

We provide an `onhour` and `offhour` time filter, each should be used in a
different policy, they support the same configuration options:

 - **weekends**: default true, whether to leave resources off for the weekend
 - **weekend-only**: default false, whether to turn the resource off only on
   the weekend
 - **default_tz**: which timezone to utilize when evaluating time **(REQUIRED)**
 - **tag**: which resource tag name to use for per-resource configuration
   (schedule and timezone overrides and opt-in/opt-out); default is
   ``maid_offhours``.
 - **opt-out**: Determines the behavior for resources which do not have a tag
   matching the one specified for **tag**. Values can be either ``false`` (the
   default) where the policy operates on an opt-in basis and resources must have
   the tag in order to be acted on by the policy, or ``true`` where the policy
   operates on an opt-out basis, and resources without the tag are acted on by
   the policy.
 - **onhour**: the default time to start/run resources, specified as 0-23
 - **offhour**: the default time to stop/suspend resources, specified as 0-23

This example policy overrides most of the defaults for an offhour policy:

.. code-block:: yaml

   policies:
     - name: offhours-stop
       resource: ec2
       filters:
         - type: offhour
           weekends: false
           default_tz: pt
           tag: downtime
           opt-out: true
           onhour: 8
           offhour: 20

Tag Based Configuration
=======================

Resources can use a special tag to override the default configuration on a
per-resource basis. Note that the name of the tag is configurable via the
``tag`` option in the policy; the examples below use the default tag name,
``maid_offhours``.

The value of the tag must be one of the following:

- **(empty)** or **on** - An empty tag value or a value of "on" implies night
  and weekend offhours using the default time zone configured in the policy
  (tz=est if unspecified) and the default onhour and offhour values configured
  in the policy.
- **off** - If offhours is configured to run in opt-out mode, this tag can be
  specified to disable offhours on a given instance. If offhours is configured
  to run in opt-in mode, this tag will have no effect (the resource will still
  be opted out).
- a semicolon-separated string composed of one or more of the following
  components, which override the defaults specified in the policy:

  * ``tz=<timezone>`` to evaluate with a resource-specific timezone, where
    ``<timezone>`` is either one of the supported timezone aliases defined in
    :py:attr:`c7n.filters.offhours.Time.TZ_ALIASES` (such as ``pt``) or the name
    of a geographic timezone identifier in
    [IANA's tzinfo database](https://www.iana.org/time-zones), such as
    ``Americas/Los_Angeles``. *(Note all timezone aliases are
    referenced to a locality to ensure taking into account local daylight
    savings time, if applicable.)*
  * ``off=(time spec)`` and/or ``on=(time spec)`` matching time specifications
    supported by :py:class:`c7n.filters.offhours.ScheduleParser` as described
    in the next section.

ScheduleParser Time Specifications
----------------------------------

Each time specification follows the format ``(days,hours)``. Multiple time
specifications can be combined in square-bracketed lists, i.e.
``[(days,hours),(days,hours),(days,hours)]``.

**Examples**::

    # up mon-fri from 7am-7pm; eastern time
    off=(M-F,19);on=(M-F,7)
    # up mon-fri from 6am-9pm; up sun from 10am-6pm; pacific time
    off=[(M-F,21),(U,18)];on=[(M-F,6),(U,10)];tz=pt

**Possible values**:

    +------------+----------------------+
    | field      | values               |
    +============+======================+
    | days       | M, T, W, H, F, S, U  |
    +------------+----------------------+
    | hours      | 0, 1, 2, ..., 22, 23 |
    +------------+----------------------+

    Days can be specified in a range (ex. M-F).

Policy examples
===============

Turn ec2 instances on and off

.. code-block:: yaml

   policies:
     - name: offhours-stop
       resource: ec2
       filters:
          - type: offhour
       actions:
         - stop

     - name: offhours-start
       resource: ec2
       filters:
         - type: onhour
       actions:
         - start

Here's doing the same with auto scale groups

.. code-block:: yaml

    policies:
      - name: asg-offhours-stop
        resource: asg
        filters:
           - offhour
        actions:
           - suspend
      - name: asg-onhours-start
        resource: asg
        filters:
           - onhour
        actions:
           - resume

Additional policy examples and resource-type-specific information can be seen in
the :ref:`EC2 Offhours <ec2offhours>` and :ref:`ASG Offhours <asgoffhours>`
use cases.

Resume During Offhours
======================

These policies are evaluated hourly; during each run (once an hour),
cloud-custodian will act on **only** the resources tagged for that **exact**
hour. In other words, if a resource has an offhours policy of
stopping/suspending at 23:00 Eastern daily and starting/resuming at 06:00
Eastern daily, and you run cloud-custodian once an hour via Lambda, that
resource will only be stopped once a day sometime between 23:00 and 23:59, and
will only be started once a day sometime between 06:00 and 06:59. If the current
hour does not *exactly* match the hour specified in the policy, nothing will be
done at all.

As a result of this, if custodian stops an instance or suspends an ASG and you
need to start/resume it, you can safely do so manually and custodian won't touch
it again until the next day.

ElasticBeanstalk, EFS and Other Services with Tag Value Restrictions
====================================================================

A number of AWS services have restrictions on the characters that can be used
in tag values, such as `ElasticBeanstalk <http://docs.aws.amazon.com/elasticbean
stalk/latest/dg/using-features.tagging.html>`_ and `EFS <http://docs.aws.amazon.
com/efs/latest/ug/API_Tag.html>`_. In particular, these services do not allow
parenthesis, square brackets, commas, or semicolons, or empty tag values. This
proves to be problematic with the tag-based schedule configuration described
above. The best current workaround is to define a separate policy with a unique
``tag`` name for each unique schedule that you want to use, and then tag
resources with that tag name and a value of ``on``. Note that this can only be
used in opt-in mode, not opt-out.

"""
from __future__ import absolute_import, division, print_function, unicode_literals

# note we have to module import for our testing mocks
import datetime
import logging
from os.path import join

from dateutil import zoneinfo

from c7n.filters import Filter, FilterValidationError
from c7n.utils import type_schema, dumps

log = logging.getLogger('custodian.offhours')


def brackets_removed(u):
    return u.translate({ord('['): None, ord(']'): None})


def parens_removed(u):
    return u.translate({ord('('): None, ord(')'): None})


class Time(Filter):

    schema = {
        'type': 'object',
        'properties': {
            'tag': {'type': 'string'},
            'default_tz': {'type': 'string'},
            'weekends': {'type': 'boolean'},
            'weekends-only': {'type': 'boolean'},
            'opt-out': {'type': 'boolean'},
        }
    }

    time_type = None

    # Defaults and constants
    DEFAULT_TAG = "maid_offhours"
    DEFAULT_TZ = 'et'

    TZ_ALIASES = {
        'pdt': 'America/Los_Angeles',
        'pt': 'America/Los_Angeles',
        'pst': 'America/Los_Angeles',
        'ast': 'America/Phoenix',
        'at': 'America/Phoenix',
        'est': 'America/New_York',
        'edt': 'America/New_York',
        'et': 'America/New_York',
        'cst': 'America/Chicago',
        'cdt': 'America/Chicago',
        'ct': 'America/Chicago',
        'mst': 'America/Denver',
        'mdt': 'America/Denver',
        'mt': 'America/Denver',
        'gmt': 'Etc/GMT',
        'gt': 'Etc/GMT',
        'bst': 'Europe/London',
        'ist': 'Europe/Dublin',
        'cet': 'Europe/Berlin',
        # Technically IST (Indian Standard Time), but that's the same as Ireland
        'it': 'Asia/Kolkata',
        'jst': 'Asia/Tokyo',
        'kst': 'Asia/Seoul',
        'sgt': 'Asia/Singapore',
        'aet': 'Australia/Sydney',
        'brt': 'America/Sao_Paulo'
    }

    def __init__(self, data, manager=None):
        super(Time, self).__init__(data, manager)
        self.default_tz = self.data.get('default_tz', self.DEFAULT_TZ)
        self.weekends = self.data.get('weekends', True)
        self.weekends_only = self.data.get('weekends-only', False)
        self.opt_out = self.data.get('opt-out', False)