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)
diff --git a/cli/main.py b/cli/main.py
index ec84bf5..b3d012d 100755
--- a/cli/main.py
+++ b/cli/main.py
@@ -17,28 +17,28 @@
 import argparse
 import os
 import readline
+import sys
 from optparse import make_option
 from time import sleep, time
 
-import sys
-from consul import Consul
 import grpc
 import requests
 from cmd2 import Cmd, options
+from consul import Consul
 from google.protobuf.empty_pb2 import Empty
 from simplejson import dumps
 
 from cli.device import DeviceCli
+from cli.alarm_filters import AlarmFiltersCli
 from cli.logical_device import LogicalDeviceCli
-from cli.table import TablePrinter, print_pb_list_as_table
+from cli.table import print_pb_list_as_table
 from voltha.core.flow_decomposer import *
 from voltha.protos import third_party
 from voltha.protos import voltha_pb2
 from voltha.protos.openflow_13_pb2 import FlowTableUpdate, FlowGroupTableUpdate
 
 _ = third_party
-from cli.utils import pb2dict, dict2line
-
+from cli.utils import pb2dict
 
 defs = dict(
     # config=os.environ.get('CONFIG', './cli.yml'),
@@ -57,8 +57,8 @@
 (to exit type quit or hit Ctrl-D)
 """
 
-class VolthaCli(Cmd):
 
+class VolthaCli(Cmd):
     prompt = 'voltha'
     history_file_name = '.voltha_cli_history'
 
@@ -213,14 +213,14 @@
 
     def device_ids(self, force_refresh=False):
         if force_refresh or self.device_ids is None or \
-                (time() - self.device_ids_cache_ts) > 1:
+                        (time() - self.device_ids_cache_ts) > 1:
             self.device_ids_cache = [d.id for d in self.get_devices()]
             self.device_ids_cache_ts = time()
         return self.device_ids_cache
 
     def logical_device_ids(self, force_refresh=False):
         if force_refresh or self.logical_device_ids is None or \
-                (time() - self.logical_device_ids_cache_ts) > 1:
+                        (time() - self.logical_device_ids_cache_ts) > 1:
             self.logical_device_ids_cache = [d.id for d
                                              in self.get_logical_devices()]
             self.logical_device_ids_cache_ts = time()
@@ -254,7 +254,7 @@
 
     @options([
         make_option('-t', '--device-type', action="store", dest='device_type',
-                     help="Device type", default='simulated_olt'),
+                    help="Device type", default='simulated_olt'),
         make_option('-m', '--mac-address', action='store', dest='mac_address',
                     default='00:0c:e2:31:40:00'),
         make_option('-i', '--ip-address', action='store', dest='ip_address'),
@@ -364,20 +364,23 @@
                       self.voltha_sim_rest)
         sub.cmdloop()
 
+    def do_alarm_filters(self, line):
+        sub = AlarmFiltersCli(self.get_channel)
+        sub.cmdloop()
+
 
 class TestCli(VolthaCli):
-
     def __init__(self, history, get_channel, voltha_grpc, voltha_sim_rest):
         VolthaCli.__init__(self, voltha_grpc, voltha_sim_rest)
         self.history = history
         self.get_channel = get_channel
         self.prompt = '(' + self.colorize(self.colorize('test', 'cyan'),
-            'bold') + ') '
+                                          'bold') + ') '
 
     def get_device(self, device_id, depth=0):
         stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
         res = stub.GetDevice(voltha_pb2.ID(id=device_id),
-                             metadata=(('get-depth', str(depth)), ))
+                             metadata=(('get-depth', str(depth)),))
         return res
 
     def do_arrive_onus(self, line):
@@ -523,7 +526,6 @@
         stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
 
         for uni_port_no, c_vid in unis:
-
             # Controller-bound flows
             stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
                 id=logical_device_id,
@@ -649,7 +651,7 @@
                 group_id=2,
                 buckets=[
                     ofp.ofp_bucket(actions=[pop_vlan(), output(unis[0][0])])
-#                    ofp.ofp_bucket(actions=[pop_vlan(), output(unis[1][0])])
+                    #                    ofp.ofp_bucket(actions=[pop_vlan(), output(unis[1][0])])
                 ]
             )
         ))
diff --git a/voltha/adapters/interface.py b/voltha/adapters/interface.py
index 24a4085..fcba8af 100644
--- a/voltha/adapters/interface.py
+++ b/voltha/adapters/interface.py
@@ -298,7 +298,7 @@
         :return: None
         """
 
-    def submit_alarm(alarm_event_msg):
+    def submit_alarm(device_id, alarm_event_msg):
         """
         Submit an alarm on behalf of the OLT and its adapter.
         :param alarm_event_msg: A protobuf message of AlarmEvent type.
diff --git a/voltha/adapters/maple_olt/maple_olt.py b/voltha/adapters/maple_olt/maple_olt.py
index 583aafa..7d08414 100644
--- a/voltha/adapters/maple_olt/maple_olt.py
+++ b/voltha/adapters/maple_olt/maple_olt.py
@@ -328,7 +328,7 @@
                 context=alarm_data,
                 raised_ts = ts)
 
-            self.adapter_agent.submit_alarm(alarm_event)
+            self.adapter_agent.submit_alarm(self.device_id, alarm_event)
 
         except Exception as e:
             log.exception('failed-to-submit-alarm', e=e)
@@ -789,7 +789,7 @@
                     context=alarm_data,
                     raised_ts = ts)
 
-                self.adapter_agent.submit_alarm(alarm_event)
+                self.adapter_agent.submit_alarm(device_id, alarm_event)
 
             except Exception as e:
                 log.exception('failed-to-submit-alarm', e=e)
diff --git a/voltha/adapters/ponsim_olt/ponsim_olt.py b/voltha/adapters/ponsim_olt/ponsim_olt.py
index 26283af..c05d7fd 100644
--- a/voltha/adapters/ponsim_olt/ponsim_olt.py
+++ b/voltha/adapters/ponsim_olt/ponsim_olt.py
@@ -171,7 +171,7 @@
                 context=current_context
             )
 
-            self.adapter.adapter_agent.submit_alarm(alarm_event)
+            self.adapter.adapter_agent.submit_alarm(self.device.id, alarm_event)
 
         except Exception as e:
             log.exception('failed-to-send-alarm', e=e)
diff --git a/voltha/adapters/simulated_olt/simulated_olt.py b/voltha/adapters/simulated_olt/simulated_olt.py
index 01d448f..6ed10a9 100644
--- a/voltha/adapters/simulated_olt/simulated_olt.py
+++ b/voltha/adapters/simulated_olt/simulated_olt.py
@@ -765,7 +765,7 @@
                     description=description,
                     context=current_context)
 
-                self.adapter_agent.submit_alarm(alarm_event)
+                self.adapter_agent.submit_alarm(device_id, alarm_event)
 
             except Exception as e:
                 log.exception('failed-to-submit-alarm', e=e)
diff --git a/voltha/core/adapter_agent.py b/voltha/core/adapter_agent.py
index 06688ed..7ef9a69 100644
--- a/voltha/core/adapter_agent.py
+++ b/voltha/core/adapter_agent.py
@@ -18,9 +18,8 @@
 Agent to play gateway between CORE and an individual adapter.
 """
 from uuid import uuid4
-import arrow
-import re
 
+import arrow
 import structlog
 from google.protobuf.json_format import MessageToJson
 from scapy.packet import Packet
@@ -31,16 +30,14 @@
 from common.frameio.frameio import hexify
 from voltha.adapters.interface import IAdapterAgent
 from voltha.protos import third_party
-from voltha.protos.device_pb2 import Device, Port
-from voltha.protos.events_pb2 import KpiEvent, AlarmEvent, AlarmEventType, \
-    AlarmEventSeverity, AlarmEventState, AlarmEventCategory
+from voltha.core.flow_decomposer import OUTPUT
 from voltha.protos.device_pb2 import Device, Port, PmConfigs
+from voltha.protos.events_pb2 import AlarmEvent, AlarmEventType, \
+    AlarmEventSeverity, AlarmEventState, AlarmEventCategory
 from voltha.protos.events_pb2 import KpiEvent
 from voltha.protos.voltha_pb2 import DeviceGroup, LogicalDevice, \
-    LogicalPort, AdminState, OperStatus, ConnectStatus
+    LogicalPort, AdminState, OperStatus, AlarmFilterRuleKey
 from voltha.registry import registry
-from voltha.core.flow_decomposer import OUTPUT
-import sys
 
 
 @implementer(IAdapterAgent)
@@ -180,7 +177,7 @@
         return self.update_flows_incrementally(
             device, flow_changes, group_changes)
 
-    #def update_pm_collection(self, device, pm_collection_config):
+    # def update_pm_collection(self, device, pm_collection_config):
     #    return self.adapter.update_pm_collection(device, pm_collection_config)
 
 
@@ -216,7 +213,7 @@
         # we run the update through the device_agent so that the change
         # does not loop back to the adapter unnecessarily
         device_agent = self.core.get_device_agent(device_pm_config.id)
-        device_agent.update_device_pm_config(device_pm_config,init)
+        device_agent.update_device_pm_config(device_pm_config, init)
 
     def update_adapter_pm_config(self, device_id, device_pm_config):
         device = self.get_device(device_id)
@@ -271,7 +268,6 @@
             self._make_up_to_date('/devices/{}/ports'.format(device_id),
                                   port.port_no, port)
 
-
     def enable_all_ports(self, device_id):
         """
         Re-enable all ports on that device, i.e. change the admin status to
@@ -361,7 +357,6 @@
 
         return logical_device
 
-
     def delete_logical_device(self, logical_device):
         """
         This will remove the logical device as well as all logical ports
@@ -379,7 +374,6 @@
         # callbacks' if present
         self._remove_node('/logical_devices', logical_device.id)
 
-
     def receive_packet_out(self, logical_device_id, ofp_packet_out):
 
         def get_port_out(opo):
@@ -563,10 +557,43 @@
             context=context
         )
 
-    def submit_alarm(self, alarm_event_msg):
+    def filter_alarm(self, device_id, alarm_event):
+        alarm_filters = self.root_proxy.get('/alarm_filters')
+
+        rule_values = {
+            'id': alarm_event.id,
+            'type': AlarmEventType.AlarmEventType.Name(alarm_event.type),
+            'category': AlarmEventCategory.AlarmEventCategory.Name(alarm_event.category),
+            'severity': AlarmEventSeverity.AlarmEventSeverity.Name(alarm_event.severity),
+            'resource_id': alarm_event.resource_id,
+            'device_id': device_id
+        }
+
+        for alarm_filter in alarm_filters:
+            if alarm_filter.rules:
+                exclude = True
+                for rule in alarm_filter.rules:
+                    self.log.debug("compare-alarm-event",
+                                   key=AlarmFilterRuleKey.AlarmFilterRuleKey.Name(rule.key),
+                                   actual=rule_values[AlarmFilterRuleKey.AlarmFilterRuleKey.Name(rule.key).lower()],
+                                   expected=rule.value.lower())
+                    exclude = exclude and \
+                              (rule_values[AlarmFilterRuleKey.AlarmFilterRuleKey.Name(
+                                  rule.key).lower()] == rule.value.lower())
+                    if not exclude:
+                        break
+
+                if exclude:
+                    self.log.info("filtered-alarm-event", alarm=alarm_event)
+                    return True
+
+        return False
+
+    def submit_alarm(self, device_id, alarm_event_msg):
         try:
             assert isinstance(alarm_event_msg, AlarmEvent)
-            self.event_bus.publish('alarms', alarm_event_msg)
+            if not self.filter_alarm(device_id, alarm_event_msg):
+                self.event_bus.publish('alarms', alarm_event_msg)
 
         except Exception as e:
             self.log.exception('failed-alarm-submission',
diff --git a/voltha/core/global_handler.py b/voltha/core/global_handler.py
index b42a554..7df3f29 100644
--- a/voltha/core/global_handler.py
+++ b/voltha/core/global_handler.py
@@ -503,3 +503,57 @@
             context)
 
 
+    @twisted_async
+    def CreateAlarmFilter(self, request, context):
+        log.info('grpc-request', request=request)
+        # TODO dispatching to local instead of passing it to leader
+        return self.dispatcher.dispatch(
+            self.instance_id,
+            VolthaLocalServiceStub,
+            'CreateAlarmFilter',
+            request,
+            context)
+
+    @twisted_async
+    def GetAlarmFilter(self, request, context):
+        log.warning('temp-limited-implementation')
+        # TODO dispatching to local instead of collecting all
+        return self.dispatcher.dispatch(
+            self.instance_id,
+            VolthaLocalServiceStub,
+            'GetAlarmFilter',
+            request,
+            context)
+
+    @twisted_async
+    def UpdateAlarmFilter(self, request, context):
+        log.info('grpc-request', request=request)
+        # TODO dispatching to local instead of passing it to leader
+        return self.dispatcher.dispatch(
+            self.instance_id,
+            VolthaLocalServiceStub,
+            'UpdateAlarmFilter',
+            request,
+            context)
+
+    @twisted_async
+    def DeleteAlarmFilter(self, request, context):
+        log.info('grpc-request', request=request)
+        # TODO dispatching to local instead of passing it to leader
+        return self.dispatcher.dispatch(
+            self.instance_id,
+            VolthaLocalServiceStub,
+            'DeleteAlarmFilter',
+            request,
+            context)
+
+    @twisted_async
+    def ListAlarmFilters(self, request, context):
+        log.warning('temp-limited-implementation')
+        # TODO dispatching to local instead of collecting all
+        return self.dispatcher.dispatch(
+            self.instance_id,
+            VolthaLocalServiceStub,
+            'ListAlarmFilters',
+            Empty(),
+            context)
diff --git a/voltha/core/local_handler.py b/voltha/core/local_handler.py
index 4a0fde8..13e8b68 100644
--- a/voltha/core/local_handler.py
+++ b/voltha/core/local_handler.py
@@ -28,7 +28,8 @@
     add_VolthaLocalServiceServicer_to_server, VolthaLocalServiceServicer, \
     VolthaInstance, Adapters, LogicalDevices, LogicalDevice, Ports, \
     LogicalPorts, Devices, Device, DeviceType, \
-    DeviceTypes, DeviceGroups, DeviceGroup, AdminState, OperStatus, ChangeEvent
+    DeviceTypes, DeviceGroups, DeviceGroup, AdminState, OperStatus, ChangeEvent, \
+    AlarmFilter, AlarmFilters
 from voltha.protos.device_pb2 import PmConfigs
 from voltha.registry import registry
 
@@ -588,3 +589,87 @@
         assert isinstance(port_status, ofp_port_status)
         event = ChangeEvent(id=device_id, port_status=port_status)
         self.core.change_event_queue.put(event)
+
+
+    @twisted_async
+    def ListAlarmFilters(self, request, context):
+        try:
+            filters = self.root.get('/alarm_filters')
+            return AlarmFilters(filters=filters)
+        except KeyError:
+            context.set_code(StatusCode.NOT_FOUND)
+            return AlarmFilters()
+
+    @twisted_async
+    def GetAlarmFilter(self, request, context):
+        if '/' in request.id:
+            context.set_details(
+                'Malformed alarm filter id \'{}\''.format(request.id))
+            context.set_code(StatusCode.INVALID_ARGUMENT)
+            return AlarmFilter()
+
+        try:
+            alarm_filter = self.root.get('/alarm_filters/{}'.format(request.id))
+            return alarm_filter
+        except KeyError:
+            context.set_details(
+                'Alarm filter \'{}\' not found'.format(request.id))
+            context.set_code(StatusCode.NOT_FOUND)
+            return AlarmFilter()
+
+    @twisted_async
+    def DeleteAlarmFilter(self, request, context):
+        if '/' in request.id:
+            context.set_details(
+                'Malformed alarm filter id \'{}\''.format(request.id))
+            context.set_code(StatusCode.INVALID_ARGUMENT)
+            return Empty()
+
+        try:
+            self.root.remove('/alarm_filters/{}'.format(request.id))
+        except KeyError:
+            context.set_code(StatusCode.NOT_FOUND)
+
+        return Empty()
+
+    @twisted_async
+    def CreateAlarmFilter(self, request, context):
+        log.info('grpc-request', request=request)
+
+        try:
+            assert isinstance(request, AlarmFilter)
+            alarm_filter = request
+            assert alarm_filter.id == '', 'Alarm filter to be created cannot have id yet'
+
+        except AssertionError, e:
+            context.set_details(e.message)
+            context.set_code(StatusCode.INVALID_ARGUMENT)
+            return AlarmFilter()
+
+        # fill additional data
+        alarm_filter.id = uuid4().hex[:12]
+
+        # add device to tree
+        self.root.add('/alarm_filters', alarm_filter)
+
+        return request
+
+    @twisted_async
+    def UpdateAlarmFilter(self, request, context):
+        if '/' in request.id:
+            context.set_details(
+                'Malformed alarm filter id \'{}\''.format(request.id))
+            context.set_code(StatusCode.INVALID_ARGUMENT)
+            return AlarmFilter()
+
+        try:
+            assert isinstance(request, AlarmFilter)
+            alarm_filter = self.root.get('/alarm_filters/{}'.format(request.id))
+            self.root.update('/alarm_filters/{}'.format(request.id), request)
+
+            return request
+        except KeyError:
+            context.set_details(
+                'Alarm filter \'{}\' not found'.format(request.id))
+            context.set_code(StatusCode.NOT_FOUND)
+            return AlarmFilter()
diff --git a/voltha/protos/events.proto b/voltha/protos/events.proto
index 0c5ac04..c6d8979 100644
--- a/voltha/protos/events.proto
+++ b/voltha/protos/events.proto
@@ -133,4 +133,4 @@
 
     // Key/Value storage for extra information that may give context to the alarm
     map<string, string> context = 11;
-}
+}
\ No newline at end of file
diff --git a/voltha/protos/voltha.proto b/voltha/protos/voltha.proto
index 703f62b..d871d8a 100644
--- a/voltha/protos/voltha.proto
+++ b/voltha/protos/voltha.proto
@@ -39,6 +39,32 @@
     repeated DeviceGroup items = 1;
 }
 
+
+message AlarmFilterRuleKey {
+    enum AlarmFilterRuleKey {
+        id = 0;
+        type = 1;
+        severity = 2;
+        resource_id = 3;
+        category = 4;
+        device_id = 5;
+    }
+}
+
+message AlarmFilterRule {
+    AlarmFilterRuleKey.AlarmFilterRuleKey key = 1;
+    string value = 2;
+}
+message AlarmFilter {
+    string id = 1 [(access) = READ_ONLY];
+
+    repeated AlarmFilterRule rules = 2;
+}
+
+message AlarmFilters {
+    repeated AlarmFilter filters = 1;
+}
+
 // Top-level (root) node for a Voltha Instance
 message VolthaInstance {
     option (yang_message_rule) = CREATE_BOTH_GROUPING_AND_CONTAINER;
@@ -60,6 +86,8 @@
     repeated DeviceType device_types = 14 [(child_node) = {key: "id"}];
 
     repeated DeviceGroup device_groups = 15 [(child_node) = {key: "id"}];
+
+    repeated AlarmFilter alarm_filters = 16 [(child_node) = {key: "id"}];
 }
 
 message VolthaInstances {
@@ -303,6 +331,37 @@
         };
     }
 
+    rpc CreateAlarmFilter(AlarmFilter) returns(AlarmFilter) {
+        option (google.api.http) = {
+            post: "/api/v1/alarm_filters"
+            body: "*"
+        };
+    }
+
+    rpc GetAlarmFilter(ID) returns(AlarmFilter) {
+        option (google.api.http) = {
+            get: "/api/v1/alarm_filters/{id}"
+        };
+    }
+
+    rpc UpdateAlarmFilter(AlarmFilter) returns(AlarmFilter) {
+        option (google.api.http) = {
+            put: "/api/v1/alarm_filters/{id}"
+            body: "*"
+        };
+    }
+
+    rpc DeleteAlarmFilter(ID) returns(google.protobuf.Empty) {
+        option (google.api.http) = {
+            delete: "/api/v1/alarm_filters/{id}"
+        };
+    }
+
+    rpc ListAlarmFilters(google.protobuf.Empty) returns(AlarmFilters) {
+        option (google.api.http) = {
+            get: "/api/v1/alarm_filters"
+        };
+    }
 }
 
 /*
@@ -536,4 +595,35 @@
         // This does not have an HTTP representation
     }
 
+    rpc CreateAlarmFilter(AlarmFilter) returns(AlarmFilter) {
+        option (google.api.http) = {
+            post: "/api/v1/local/alarm_filters"
+            body: "*"
+        };
+    }
+
+    rpc GetAlarmFilter(ID) returns(AlarmFilter) {
+        option (google.api.http) = {
+            get: "/api/v1/local/alarm_filters/{id}"
+        };
+    }
+
+    rpc UpdateAlarmFilter(AlarmFilter) returns(AlarmFilter) {
+        option (google.api.http) = {
+            put: "/api/v1/local/alarm_filters/{id}"
+            body: "*"
+        };
+    }
+
+    rpc DeleteAlarmFilter(ID) returns(google.protobuf.Empty) {
+        option (google.api.http) = {
+            delete: "/api/v1/local/alarm_filters/{id}"
+        };
+    }
+
+    rpc ListAlarmFilters(google.protobuf.Empty) returns(AlarmFilters) {
+        option (google.api.http) = {
+            get: "/api/v1/local/alarm_filters"
+        };
+    }
 }