VOLTHA Alarms: Added filtering capabilities
- Filters are constructed based on 1 or more rules as key:value pairs
- A key:value rule uses the available alarm attributes
- NBI and cli commands were added

Amendment:
- Moved filter protos to voltha proto to fix possible circular dependency

Change-Id: Ic72125e9d35135d75be175638341b0c08fd97f95
diff --git a/cli/alarm_filters.py b/cli/alarm_filters.py
new file mode 100644
index 0000000..e607cf1
--- /dev/null
+++ b/cli/alarm_filters.py
@@ -0,0 +1,238 @@
+#!/usr/bin/env python
+#
+# Copyright 2017 the original author or authors.
+#
+# 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.
+#
+
+"""
+Alarm filter CLI commands
+"""
+from optparse import make_option, OptionValueError
+
+from cmd2 import Cmd, options
+from google.protobuf.empty_pb2 import Empty
+
+from cli.table import print_pb_list_as_table
+from voltha.protos import third_party
+from voltha.protos import voltha_pb2
+from voltha.protos.events_pb2 import AlarmEventType, AlarmEventSeverity, AlarmEventCategory
+
+_ = third_party
+
+
+class AlarmFiltersCli(Cmd):
+    def __init__(self, get_channel):
+        Cmd.__init__(self)
+        self.get_channel = get_channel
+        self.prompt = '(' + self.colorize(
+            self.colorize('alarm_filters', 'red'), 'bold') + ') '
+
+    def cmdloop(self):
+        self._cmdloop()
+
+    def help_show(self):
+        self.poutput(
+'''
+Display the list of configured filters.
+
+Valid options:
+
+-i FILTER_ID | --filter-id=FILTER_ID                Display the filter rules for a specific filter id (OPTIONAL)
+
+'''
+        )
+
+    @options([
+        make_option('-i', '--filter-id', action="store", dest='filter_id')
+    ])
+    def do_show(self, line, opts):
+        stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
+
+        if not opts.filter_id:
+            result = stub.ListAlarmFilters(Empty())
+            print_pb_list_as_table("Alarm Filters:", result.filters, {}, self.poutput)
+        else:
+            result = stub.GetAlarmFilter(voltha_pb2.ID(id=opts.filter_id))
+            print_pb_list_as_table("Rules for Filter ID = {}:".format(opts.filter_id),
+                                   result.rules, {}, self.poutput)
+
+    @staticmethod
+    def construct_rule(raw_rule):
+        rule = dict()
+
+        rule_kv = raw_rule.strip().split(':')
+
+        if len(rule_kv) == 2:
+            rule['key'] = rule_kv[0].lower()
+            rule['value'] = rule_kv[1].lower()
+        else:
+            raise OptionValueError("Error: A rule must be a colon separated key/value pair")
+
+        return rule
+
+    def parse_filter_rules(option, opt_str, value, parser):
+        rules = getattr(parser.values, option.dest)
+        if rules is None:
+            rules = list()
+            rules.append(AlarmFiltersCli.construct_rule(value))
+
+            for arg in parser.rargs:
+                if (arg[:2] == "--" and len(arg) > 2) or (arg[:1] == "-" and len(arg) > 1 and arg[1] != "-"):
+                    break
+                else:
+                    rules.append(AlarmFiltersCli.construct_rule(arg))
+
+            setattr(parser.values, option.dest, rules)
+        else:
+            raise OptionValueError('Warning: The filter rule option can only be specified once')
+
+    def help_create(self):
+        types = list(
+            k for k, v in
+            AlarmEventType.DESCRIPTOR.enum_values_by_name.items())
+        categories = list(
+            k for k, v in
+            AlarmEventCategory.DESCRIPTOR.enum_values_by_name.items())
+        severities = list(
+            k for k, v in
+            AlarmEventSeverity.DESCRIPTOR.enum_values_by_name.items())
+
+        alarm_types = types
+        alarm_categories = categories
+        alarm_severities = severities
+
+        usage = '''
+Create a new alarm filter.
+
+Valid options:
+
+-r rule:value ... | --filter-rules rule:value ...   Specify one or more filter rules as key/value pairs (REQUIRED)
+
+Valid rule keys and expected values:
+
+id          : Identifier of an incoming alarm
+type        : Type of an incoming alarm {}
+category    : Category of an incoming alarm {}
+severity    : Severity of an incoming alarm {}
+resource_id : Resource identifier of an incoming alarm
+device_id   : Device identifier of an incoming alarm
+
+Example:
+
+# Filter any alarm that matches the following criteria
+
+create -r type:environment severity:indeterminate
+create -r device_id:754f9dcbe4a6
+
+'''.format(alarm_types, alarm_categories, alarm_severities)
+
+        self.poutput(usage)
+
+    @options([
+        make_option('-r', '--filter-rules', help='<key>:<value>...', action="callback",
+                    callback=parse_filter_rules, type='string', dest='filter_rules'),
+    ])
+    def do_create(self, line, opts):
+        if opts.filter_rules:
+            stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
+            result = stub.CreateAlarmFilter(voltha_pb2.AlarmFilter(rules=opts.filter_rules))
+            print_pb_list_as_table("Rules for Filter ID = {}:".format(result.id),
+                                   result.rules, {}, self.poutput)
+
+    def help_delete(self):
+        self.poutput(
+'''
+Delete a specific alarm filter entry.
+
+Valid options:
+
+-i FILTER_ID | --filter-id=FILTER_ID                Display the filter rules for a specific filter id (REQUIRED)
+
+'''
+        )
+
+    @options([
+        make_option('-i', '--filter-id', action="store", dest='filter_id')
+    ])
+    def do_delete(self, line, opts):
+        if not opts.filter_id:
+            self.poutput(self.colorize('Error: ', 'red') + 'Specify ' + \
+                         self.colorize(self.colorize('"filter id"', 'blue'),
+                                       'bold') + ' to update')
+            return
+
+        stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
+        stub.DeleteAlarmFilter(voltha_pb2.ID(id=opts.filter_id))
+
+    def help_update(self):
+        types = list(
+            k for k, v in
+            AlarmEventType.DESCRIPTOR.enum_values_by_name.items())
+        categories = list(
+            k for k, v in
+            AlarmEventCategory.DESCRIPTOR.enum_values_by_name.items())
+        severities = list(
+            k for k, v in
+            AlarmEventSeverity.DESCRIPTOR.enum_values_by_name.items())
+
+        alarm_types = types
+        alarm_categories = categories
+        alarm_severities = severities
+
+        usage = '''
+Update the filter rules for an existing alarm filter.
+
+Valid options:
+
+-i FILTER_ID | --filter-id=FILTER_ID                Indicate the alarm filter identifier to update (REQUIRED)
+-r rule:value ... | --filter-rules rule:value ...   Specify one or more filter rules as key/value pairs (REQUIRED)
+
+Valid rule keys and expected values:
+
+id          : Identifier of an incoming alarm
+type        : Type of an incoming alarm {}
+category    : Category of an incoming alarm {}
+severity    : Severity of an incoming alarm {}
+resource_id : Resource identifier of an incoming alarm
+device_id   : Device identifier of an incoming alarm
+
+Example:
+
+# Filter any alarm that matches the following criteria
+
+update -i 9da115b900bc -r type:environment severity:indeterminate resource_id:1554b0517a07
+
+'''.format(alarm_types, alarm_categories, alarm_severities)
+
+        self.poutput(usage)
+
+    @options([
+        make_option('-r', '--filter-rules', help='<key>:<value>...', action="callback",
+                    callback=parse_filter_rules, type='string', dest='filter_rules'),
+        make_option('-i', '--filter-id', action="store", dest='filter_id')
+    ])
+    def do_update(self, line, opts):
+        if not opts.filter_id:
+            self.poutput(self.colorize('Error: ', 'red') + 'Specify ' + \
+                         self.colorize(self.colorize('"filter id"', 'blue'),
+                                       'bold') + ' to update')
+            return
+
+        if opts.filter_rules:
+            stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
+            result = stub.UpdateAlarmFilter(
+                voltha_pb2.AlarmFilter(id=opts.filter_id, rules=opts.filter_rules)
+            )
+            print_pb_list_as_table("Rules for Filter ID = {}:".format(result.id),
+                                   result.rules, {}, self.poutput)