diff --git a/inv/models/interface.py b/inv/models/interface.py index 612e6f53f59dd66eea9164b15eb9fee2994db5a4..5c450cd281e256fc6b31c27104260626f237dfed 100644 --- a/inv/models/interface.py +++ b/inv/models/interface.py @@ -8,7 +8,7 @@ # Python modules import datetime import logging -from typing import Optional +from typing import Optional, Iterable, List # Third-party modules from mongoengine.document import Document @@ -19,16 +19,20 @@ from mongoengine.fields import ( ListField, DateTimeField, ReferenceField, + ObjectIdField, ) from pymongo import ReadPreference # NOC Modules from noc.config import config from noc.core.mongo.fields import ForeignKeyField, PlainReferenceField +from noc.core.resourcegroup.decorator import resourcegroup from noc.sa.models.managedobject import ManagedObject from noc.sa.interfaces.base import MACAddressParameter from noc.sa.interfaces.igetinterfaces import IGetInterfaces from noc.main.models.resourcestate import ResourceState +from noc.main.models.regexplabel import RegexpLabel +from noc.main.models.label import Label from noc.project.models.project import Project from noc.vc.models.vcdomain import VCDomain from noc.sa.models.service import Service @@ -51,6 +55,8 @@ logger = logging.getLogger(__name__) @on_delete @change +@resourcegroup +@Label.model @on_delete_check( ignore=[ ("inv.Link", "interfaces"), @@ -112,6 +118,14 @@ class Interface(Document): nri_name = StringField() # service = ReferenceField(Service) + # Resource groups + static_service_groups = ListField(ObjectIdField()) + effective_service_groups = ListField(ObjectIdField()) + static_client_groups = ListField(ObjectIdField()) + effective_client_groups = ListField(ObjectIdField()) + # Labels + labels = ListField(StringField()) + effective_labels = ListField(StringField()) PROFILE_LINK = "profile" @@ -384,7 +398,39 @@ class Interface(Document): return self.profile return InterfaceProfile.get_default_profile() + @classmethod + def can_set_label(cls, label): + # return Label.get_effective_setting(label, setting="enable_sensor") + return False + + @classmethod + def iter_effective_labels(cls, instance: "Interface") -> Iterable[List[str]]: + yield list(instance.labels or []) + # if instance.profile.labels: + # yield list(instance.profile.labels) + yield RegexpLabel.get_effective_labels("interface_name", instance.name) + yield RegexpLabel.get_effective_labels("interface_description", instance.name) + if instance.managed_object: + yield list(instance.managed_object.effective_labels) + if instance.is_linked: + yield ["noc::interface::linked::="] + for si in instance.parent.subinterface_set.filter(enabled_afi__in=["BRIDGE", "IPv4"]): + if si.tagged_vlans: + lazy_tagged_vlans_labels = list( + VCFilter.iter_lazy_labels(si.tagged_vlans, "tagged") + ) + yield Label.ensure_labels(lazy_tagged_vlans_labels, enable_interface=True) + if si.untagged_vlan: + lazy_untagged_vlans_labels = list( + VCFilter.iter_lazy_labels(si.tagged_vlans, "untagged") + ) + yield Label.ensure_labels(lazy_untagged_vlans_labels, enable_interface=True) + if si.ipv4_addresses: + yield list(PrefixTable.iter_lazy_labels(si.ipv4_addresses[0])) + # Avoid circular references from noc.sa.models.servicesummary import ServiceSummary from .link import Link +from noc.main.models.prefixtable import PrefixTable +from noc.vc.models.vcfilter import VCFilter diff --git a/main/models/label.py b/main/models/label.py index 6d3dcc4b928fdec6591db521d142cc84d3062a50..2519a788f1a0d1e51bb46a36ede03d26ead2b046 100644 --- a/main/models/label.py +++ b/main/models/label.py @@ -68,6 +68,7 @@ class Label(Document): enable_resourcegroup = BooleanField() enable_sensor = BooleanField() enable_sensorprofile = BooleanField() + enable_interface = BooleanField() # enable_subscriber = BooleanField() enable_subscriberprofile = BooleanField() @@ -302,6 +303,7 @@ class Label(Document): is_autogenerated=False, enable_managedobject=False, enable_slaprobe=False, + enable_interface=False, bg_color1=0xFFFFFF, fg_color1=0x000000, bg_color2=0xFFFFFF, @@ -332,6 +334,8 @@ class Label(Document): settings["enable_managedobject"] = enable_managedobject if enable_slaprobe: settings["enable_slaprobe"] = enable_slaprobe + if enable_interface: + settings["enable_interface"] = enable_interface if bg_color1 and bg_color1 != 0xFFFFFF: settings["bg_color1"] = bg_color1 if fg_color1: @@ -347,7 +351,9 @@ class Label(Document): Label(**settings).save() @classmethod - def ensure_labels(cls, labels: List[str], enable_managedobject=False) -> List[str]: + def ensure_labels( + cls, labels: List[str], enable_managedobject=False, enable_interface=False + ) -> List[str]: """ Yields all scopes :return: @@ -358,6 +364,7 @@ class Label(Document): is_autogenerated=True, is_protected=False, enable_managedobject=enable_managedobject, + enable_interface=enable_interface, ) return labels @@ -463,7 +470,7 @@ class Label(Document): return m_cls @classmethod - def match_labels(cls, category, allowed_op: Set = None): + def match_labels(cls, category, allowed_op: Set = None, matched_scopes: Set = None): """ Decorator to denote models with labels. Ensure wildcard and contains labels @@ -487,20 +494,25 @@ class Label(Document): document=None, category=category, allowed_op=allowed_op, + matched_scopes=matched_scopes, *args, **kwargs, ): instance = instance or document + matched_scopes = matched_scopes or {""} # Ensure matches ops = MATCH_OPS if allowed_op: ops = MATCH_OPS.intersection(allowed_op) for op in ops: - Label.ensure_label( - f"noc::{category}::{instance.name}::{op}", - is_protected=False, - is_autogenerated=True, - ) + for matched_scope in matched_scopes: + if matched_scope: + matched_scope += "::" + Label.ensure_label( + f"noc::{category}::{instance.name}::{matched_scope}{op}", + is_protected=False, + is_autogenerated=True, + ) def inner(m_cls): # Install handlers diff --git a/ui/web/main/label/Application.js b/ui/web/main/label/Application.js index d8df67ec4e0aa5f783fce35ec3bb25817c87b836..59a60edf4e3da7900f5cf475c6217976d4c3d450 100644 --- a/ui/web/main/label/Application.js +++ b/ui/web/main/label/Application.js @@ -94,6 +94,9 @@ Ext.define("NOC.main.label.Application", { if (item.data.enable_sensor) { r.push(__("Sensor")); } + if (item.data.enable_interface) { + r.push(__("Interface")); + } if (item.data.enable_subscriber) { r.push(__("Subscriber")); } @@ -324,6 +327,11 @@ Ext.define("NOC.main.label.Application", { name: "enable_slaprofile", xtype: "checkbox", boxLabel: __("SLA Profile") + }, + { + name: "enable_interface", + xtype: "checkbox", + boxLabel: __("Interface") } ] }, diff --git a/ui/web/main/label/Model.js b/ui/web/main/label/Model.js index 1843d366d48141aee0bf5ca036107599a2aea82b..863448454c346bdac02294267fea4581aec0973c 100644 --- a/ui/web/main/label/Model.js +++ b/ui/web/main/label/Model.js @@ -122,6 +122,10 @@ Ext.define("NOC.main.label.Model", { name: "enable_sensorprofile", type: "boolean" }, + { + name: "enable_interface", + type: "boolean" + }, { name: "enable_subscriber", type: "boolean" diff --git a/vc/models/vcfilter.py b/vc/models/vcfilter.py index 22c45c9965075f50b003c21073a6ae3547e50bca..84296b8bd59580848af85c02d9e97eb00c2d4fd4 100644 --- a/vc/models/vcfilter.py +++ b/vc/models/vcfilter.py @@ -9,6 +9,8 @@ import re from threading import Lock import operator +from collections import defaultdict +from typing import List, Optional # Third-party modules from django.db import models @@ -18,9 +20,13 @@ import cachetools from noc.core.model.base import NOCModel from noc.core.model.decorator import on_delete_check from noc.main.models.label import Label +from noc.core.text import ranges_to_list rx_vc_filter = re.compile(r"^\s*\d+\s*(-\d+\s*)?(,\s*\d+\s*(-\d+)?)*$") id_lock = Lock() +match_lock = Lock() + +MATCHED_SCOPES = {"untagged", "tagged"} @Label.match_labels(category="vcfilter") @@ -44,6 +50,7 @@ class VCFilter(NOCModel): description = models.TextField("Description", null=True, blank=True) _id_cache = cachetools.TTLCache(maxsize=100, ttl=60) + _match_cache = cachetools.TTLCache(maxsize=10, ttl=30) @classmethod @cachetools.cachedmethod(operator.attrgetter("_id_cache"), lock=lambda _: id_lock) @@ -120,3 +127,31 @@ class VCFilter(NOCModel): return "TRUE" else: return "(%s)" % " OR ".join(s) + + @classmethod + @cachetools.cachedmethod(operator.attrgetter("_match_cache"), lock=lambda _: match_lock) + def get_matcher(cls): + r = defaultdict(list) + for vc in VCFilter.objects.filter(): + r[frozenset(ranges_to_list(vc.expression))] += [vc.name] + return r + + @classmethod + def iter_lazy_labels(cls, vcs: List[int], match_scope: Optional[str] = None): + if match_scope and match_scope not in MATCHED_SCOPES: + return + vcs = set(vcs) + match_scope = match_scope or "" + if match_scope: + match_scope += "::" + match_expressions = cls.get_matcher() + for expr, vc_name in match_expressions.items(): + r = vcs.intersection(expr) + if r and vcs == expr: + yield f"noc::vcfilter::{vc_name[0]}::{match_scope}=" + if r and not vcs - expr: + yield f"noc::vcfilter::{vc_name[0]}::{match_scope}>" + if r and not expr - vcs: + yield f"noc::vcfilter::{vc_name[0]}::{match_scope}<" + if r: + yield f"noc::vcfilter::{vc_name[0]}::{match_scope}&"