Add module and states for gnocchi

Initial version of the module allows to create/delete/update/list
archive policies and rules.
Also added client states supporting rules and policies creation

Change-Id: I7341dfb26a39275e1a9b55f7a49fd2ace9584612
Related-Prod: https://mirantis.jira.com/browse/PROD-20719
diff --git a/_states/gnocchiv1.py b/_states/gnocchiv1.py
new file mode 100644
index 0000000..5cadd65
--- /dev/null
+++ b/_states/gnocchiv1.py
@@ -0,0 +1,238 @@
+# -*- coding: utf-8 -*-
+'''
+Managing entities in Gnocchi
+===================================
+'''
+# Import python libs
+import logging
+
+# Import Gnocchi libs
+def __virtual__():
+    return 'gnocchiv1' if 'gnocchiv1.archive_policy_list' in __salt__ else False
+
+
+log = logging.getLogger(__name__)
+
+
+def _gnocchiv1_call(fname, *args, **kwargs):
+    return __salt__['gnocchiv1.{}'.format(fname)](*args, **kwargs)
+
+
+def archive_policy_present(name, cloud_name, definition, **kwargs):
+    """
+    Creates an archive policy
+
+    This state checks if an archive policy is present and, if not, creates an
+    archive policy with specified name, definition, aggregation methods and
+    back_window.
+
+    :param name: name of the archive policy
+    :param cloud_name: name of the cloud in cloud.yaml
+    :param definition: List of dictionaries. Every dictionary may contain:
+            :param granularity: String indicating policy's granularity e.g. '0:00:01'
+            :param points: Integer indicating policy's points e.g. 10
+            :param: timespan parameters of archive policy. e.g. '0:00:10'
+    :param aggregation_methods: List of aggregation methods used in archive policy
+    :param back_window: Integer specifies the number of coarsest periods to keep
+    """
+    try:
+        archive_policy = _gnocchiv1_call(
+            'archive_policy_read', name, cloud_name=cloud_name
+        )
+    except Exception as e:
+        if 'NotFound' in repr(e):
+            try:
+                resp = _gnocchiv1_call(
+                    'archive_policy_create', name=name, cloud_name=cloud_name,
+                    definition=definition, **kwargs
+                )
+            except Exception as e:
+                log.error('Gnocchi archive policy create failed with {}'.format(e))
+                return _create_failed(name, 'archive_policy')
+            return _created(name, 'archive_policy', resp)
+        else:
+            raise
+    if archive_policy:
+        # TODO: Implement and design archive policy update procedure
+        return _no_changes(name, 'archive_policy')
+    else:
+        return _find_failed(name, 'archive_policy')
+
+
+def archive_policy_absent(name, cloud_name):
+    try:
+        archive_policy = _gnocchiv1_call(
+            'archive_policy_read', name, cloud_name=cloud_name
+        )
+    except Exception as e:
+        if 'NotFound' in repr(e):
+            return _absent(name, 'archive_policy')
+    if archive_policy:
+        try:
+            resp = _gnocchiv1_call(
+                    'archive_policy_delete', name, cloud_name=cloud_name
+                )
+        except Exception as e:
+            log.error('Archive policy delete failed with {}'.format(e))
+            return _delete_failed(name, 'archive_policy')
+        return _deleted(name, 'archive_policy')
+    else:
+        return _find_failed(name, 'archive_policy')
+
+
+def archive_policy_rule_present(name, cloud_name, archive_policy_name, metric_pattern):
+    """
+    Creates an archive policy rule
+
+    This state checks if an archive policy rule is present and, if not, creates an
+    archive policy rule with specified name, archive policy name, and metric_pattern.
+
+    :param name: name of the archive policy rule
+    :param cloud_name: name of the cloud in cloud.yaml
+    :param archive_policy_name: name of archive policy for specified rule
+    :param metric_pattern: pattern for metrics classified by the rule
+    """
+    try:
+        archive_policy_rule = _gnocchiv1_call(
+            'archive_policy_rule_read', name, cloud_name=cloud_name
+        )
+    except Exception as e:
+        if 'NotFound' in repr(e):
+            try:
+                resp = _gnocchiv1_call(
+                    'archive_policy_rule_create', name=name, cloud_name=cloud_name,
+                    archive_policy_name=archive_policy_name, metric_pattern=metric_pattern,
+                )
+            except Exception as e:
+                log.error('Gnocchi archive policy rule create failed with {}'.format(e))
+                return _create_failed(name, 'archive_policy_rule')
+            return _created(name, 'archive_policy_rule', resp)
+        else:
+            raise
+    if archive_policy_rule:
+        # Currently Gnocchi API doesn't allow to update properties in rules,
+        # but they can be recreated.
+        if ((archive_policy_rule['archive_policy_name'] != archive_policy_name) or
+            (archive_policy_rule['metric_pattern'] != metric_pattern)):
+            try:
+                resp = _gnocchiv1_call(
+                    'archive_policy_rule_delete', name, cloud_name=cloud_name
+                )
+                resp = _gnocchiv1_call(
+                    'archive_policy_rule_create', name=name, cloud_name=cloud_name,
+                    archive_policy_name=archive_policy_name, metric_pattern=metric_pattern
+                )
+            except Exception as e:
+                log.error('Archive policy rule update failed with {}'.format(e))
+                return _update_failed(name, 'archive_policy_rule')
+            return _updated(name, 'archive_policy_rule', resp)
+        else:
+            return _no_changes(name, 'archive_policy_rule')
+    else:
+        return _find_failed(name, 'archive_policy_rule')
+
+def archive_policy_rule_absent(name, cloud_name):
+    try:
+        archive_policy_rule = _gnocchiv1_call(
+            'archive_policy_rule_read', name, cloud_name=cloud_name
+        )
+    except Exception as e:
+        if 'NotFound' in repr(e):
+            return _absent(name, 'archive_policy_rule')
+    if archive_policy_rule:
+        try:
+            resp = _gnocchiv1_call(
+                    'archive_policy_rule_delete', name, cloud_name=cloud_name
+                )
+        except Exception as e:
+            log.error('Archive policy rule delete failed with {}'.format(e))
+            return _delete_failed(name, 'archive_policy_rule')
+        return _deleted(name, 'archive_policy_rule')
+    else:
+        return _find_failed(name, 'archive_policy_rule')
+
+
+def _created(name, resource, resource_definition):
+    changes_dict = {
+        'name': name,
+        'changes': resource_definition,
+        'result': True,
+        'comment': '{}{} created'.format(resource, name)
+    }
+    return changes_dict
+
+
+def _updated(name, resource, resource_definition):
+    changes_dict = {
+        'name': name,
+        'changes': resource_definition,
+        'result': True,
+        'comment': '{}{} updated'.format(resource, name)
+    }
+    return changes_dict
+
+
+def _no_changes(name, resource):
+    changes_dict = {
+        'name': name,
+        'changes': {},
+        'result': True,
+        'comment': '{}{} is in desired state'.format(resource, name)
+    }
+    return changes_dict
+
+
+def _deleted(name, resource):
+    changes_dict = {
+        'name': name,
+        'changes': {},
+        'result': True,
+        'comment': '{}{} removed'.format(resource, name)
+    }
+    return changes_dict
+
+
+def _absent(name, resource):
+    changes_dict = {'name': name,
+                    'changes': {},
+                    'comment': '{0} {1} not present'.format(resource, name),
+                    'result': True}
+    return changes_dict
+
+
+def _delete_failed(name, resource):
+    changes_dict = {'name': name,
+                    'changes': {},
+                    'comment': '{0} {1} failed to delete'.format(resource,
+                                                                 name),
+                    'result': False}
+    return changes_dict
+
+
+def _create_failed(name, resource):
+    changes_dict = {'name': name,
+                    'changes': {},
+                    'comment': '{0} {1} failed to create'.format(resource,
+                                                                 name),
+                    'result': False}
+    return changes_dict
+
+
+def _update_failed(name, resource):
+    changes_dict = {'name': name,
+                    'changes': {},
+                    'comment': '{0} {1} failed to update'.format(resource,
+                                                                 name),
+                    'result': False}
+    return changes_dict
+
+
+def _find_failed(name, resource):
+    changes_dict = {
+        'name': name,
+        'changes': {},
+        'comment': '{0} {1} found multiple {0}'.format(resource, name),
+        'result': False,
+    }
+    return changes_dict
+