def lookup_vcfilter(self, q, name, value): """ Resolve __vcflter lookups :param q: :param name: :param value: :return: """ value = ModelParameter(VCFilter).clean(value) x = value.to_sql(name) try: q[None] += [x] except KeyError: q[None] = [x]
class NotificationGroupApplication(ExtModelApplication): """ NotificationGroup application """ title = _("Notification Group") menu = [_("Setup"), _("Notification Groups")] model = NotificationGroup glyph = "envelope-o" users = ModelInline(NotificationGroupUser) other = ModelInline(NotificationGroupOther) @view( url="^actions/test/$", method=["POST"], access="update", api=True, validate={ "ids": ListOfParameter(element=ModelParameter(NotificationGroup)), "subject": UnicodeParameter(), "body": UnicodeParameter(), }, ) def api_action_test(self, request, ids, subject, body): for g in ids: g.notify(subject=subject, body=body) return "Notification message has been sent"
def get_validator(self, field): """ Returns Parameter instance or None to clean up field :param field: :type field: Field :return: """ from noc.core.model.fields import TagsField, TextArrayField if isinstance(field, BooleanField): return BooleanParameter() elif isinstance(field, IntegerField): return IntParameter() elif isinstance(field, FloatField): return FloatParameter() elif isinstance(field, DateField): return DateParameter() elif isinstance(field, DateTimeField): return DateTimeParameter() elif isinstance(field, TagsField): return TagsParameter(required=not field.null) elif isinstance(field, TextArrayField): return StringListParameter(required=not field.null) elif isinstance(field, related.ForeignKey): self.fk_fields[field.name] = field.remote_field.model return ModelParameter(field.remote_field.model, required=not field.null) else: return None
class TimePatternApplication(ExtModelApplication): """ TimePattern application """ title = _("Time Pattern") menu = [_("Setup"), _("Time Patterns")] model = TimePattern glyph = "clock-o" terms = ModelInline(TimePatternTerm) @view( url="^actions/test/", method=["POST"], access="read", api=True, validate={ "ids": ListOfParameter(element=ModelParameter(TimePattern)), "date": StringParameter(required=True), "time": StringParameter(required=True), }, ) def api_action_test(self, request, ids, date=None, time=None): d = "%sT%s" % (date, time) dt = datetime.datetime.strptime(d, "%Y-%m-%dT%H:%M") return { "ts": dt.isoformat(), "result": [{ "id": p.id, "name": p.name, "result": p.match(dt) } for p in ids], }
class PrefixTableApplication(ExtModelApplication): """ PrefixTable application """ title = _("Prefix Table") menu = [_("Setup"), _("Prefix Tables")] model = PrefixTable prefixes = ModelInline(PrefixTablePrefix) @view( url="^actions/test/$", method=["POST"], access="update", api=True, validate={ "ids": ListOfParameter(element=ModelParameter(PrefixTable)), "ip": IPParameter() }, ) def api_action_test(self, request, ids, ip): return { "ip": ip, "result": [{ "id": pt.id, "name": pt.name, "result": ip in pt } for pt in ids], }
def api_action_group_edit(self, request): validator = DictParameter( attrs={"ids": ListOfParameter(element=ModelParameter(self.model), convert=True)} ) rv = self.deserialize(request.body) try: v = validator.clean(rv) except InterfaceTypeError as e: self.logger.info("Bad request: %r (%s)", request.body, e) return self.render_json( {"status": False, "message": "Bad request", "traceback": str(e)}, status=self.BAD_REQUEST, ) objects = v["ids"] del v["ids"] try: v = self.clean(v) except ValueError as e: return self.render_json( {"status": False, "message": "Bad request", "traceback": str(e)}, status=self.BAD_REQUEST, ) for o in objects: for p in v: setattr(o, p, v[p]) o.save() return "%d records has been updated" % len(objects)
class VRFApplication(ExtModelApplication): """ VRF application """ title = _("VRF") menu = _("VRF") model = VRF query_fields = ["name", "rd", "description"] mrt_config = { "get_vrfs": { "map_script": "get_mpls_vpn", "access": "import" } } def field_row_class(self, o): return o.profile.style.css_class_name if o.profile.style else "" def clean(self, data): if not data.get("vpn_id"): vdata = {"type": "VRF", "name": data["name"], "rd": data.get("rd")} data["vpn_id"] = get_vpn_id(vdata) return super().clean(data) @view( url="^bulk/import/$", method=["POST"], access="import", api=True, validate={ "items": ListOfParameter(element=DictParameter( attrs={ "name": StringParameter(), "rd": RDParameter(), "vrf_group": ModelParameter(model=VRFGroup), "afi_ipv4": BooleanParameter(default=False), "afi_ipv6": BooleanParameter(default=False), "description": StringParameter(required=False), })) }, ) def api_bulk_import(self, request, items): n = 0 for i in items: if not VRF.objects.filter(name=i["name"], rd=i["rd"]).exists(): # Add only new VRF( name=i["name"], vrf_group=i["vrf_group"], rd=i["rd"], afi_ipv4=i["afi_ipv4"], afi_ipv6=i["afi_ipv6"], description=i.get("description"), ).save() n += 1 return {"status": True, "imported": n}
class ASSetApplication(ExtModelApplication): """ ASSet application """ title = "AS Sets" menu = "AS Sets" model = ASSet query_fields = ["name__icontains","description__icontains", "members__icontains"] @view(url="^actions/rpsl/$", method=["POST"], access="read", api=True, validate={ "ids": ListOfParameter(element=ModelParameter(ASSet)) }) def api_action_rpsl(self,request,ids): return "</br></br>".join([o.rpsl.replace("\n", "</br>") for o in ids]) api_action_rpsl.short_description="RPSL for selected objects"
def get_validator(self, field): """ Returns Parameter instance or None to clean up field :param field: :type field: Field :return: """ from noc.core.model.fields import AutoCompleteTagsField if isinstance(field, BooleanField): return BooleanParameter() elif isinstance(field, IntegerField): return IntParameter() elif isinstance(field, FloatField): return FloatParameter() elif isinstance(field, AutoCompleteTagsField): return TagsParameter(required=not field.null) elif isinstance(field, related.ForeignKey): self.fk_fields[field.name] = field.rel.to return ModelParameter(field.rel.to, required=not field.null) else: return None
class PrefixListBuilderApplication(ExtApplication): """ Interactive prefix list builder """ title = "Prefix List Builder" menu = "Prefix List Builder" # implied_permissions = { # "read": ["peer:peeringpoint:lookup"] #} @view(method=["GET"], url=r"^$", access="read", api=True, validate={ "peering_point": ModelParameter(PeeringPoint), "name": UnicodeParameter(required=False), "as_set": UnicodeParameter() }) def api_list(self, request, peering_point, name, as_set): prefixes = WhoisCache.resolve_as_set_prefixes_maxlen(as_set) pl = peering_point.profile.generate_prefix_list(name, prefixes) return {"name": name, "prefix_list": pl, "success": True}
class ManagedObjectApplication(ExtModelApplication): """ ManagedObject application """ title = _("Managed Objects") menu = _("Managed Objects") model = ManagedObject query_condition = "icontains" query_fields = ["name", "description"] secret_fields = {"password", "super_password", "snmp_ro", "snmp_rw"} # Inlines attrs = ModelInline(ManagedObjectAttribute) cfg = RepoInline("config", access="config") extra_permissions = ["alarm", "change_interface"] implied_permissions = { "read": ["inv:networksegment:lookup", "main:handler:lookup"] } diverged_permissions = {"config": "read", "console": "script"} order_map = { "address": " cast_test_to_inet(address) ", "-address": " cast_test_to_inet(address) ", "profile": "CASE %s END" % " ".join([ "WHEN %s='%s' THEN %s" % ("profile", pk, i) for i, pk in enumerate( Profile.objects.filter().order_by("name").values_list("id")) ]), "-profile": "CASE %s END" % " ".join([ "WHEN %s='%s' THEN %s" % ("profile", pk, i) for i, pk in enumerate( Profile.objects.filter().order_by("-name").values_list("id")) ]), "platform": "CASE %s END" % " ".join([ "WHEN %s='%s' THEN %s" % ("platform", pk, i) for i, pk in enumerate(Platform.objects.filter().order_by( "name").values_list("id")) ]), "-platform": "CASE %s END" % " ".join([ "WHEN %s='%s' THEN %s" % ("platform", pk, i) for i, pk in enumerate(Platform.objects.filter().order_by( "-name").values_list("id")) ]), "version": "CASE %s END" % " ".join([ "WHEN %s='%s' THEN %s" % ("version", pk, i) for i, pk in enumerate(Firmware.objects.filter().order_by( "version").values_list("id")) ]), "-version": "CASE %s END" % " ".join([ "WHEN %s='%s' THEN %s" % ("version", pk, i) for i, pk in enumerate(Firmware.objects.filter().order_by( "-version").values_list("id")) ]), } resource_group_fields = [ "static_service_groups", "effective_service_groups", "static_client_groups", "effective_client_groups", ] DISCOVERY_JOBS = [ ("box", "noc.services.discovery.jobs.box.job.BoxDiscoveryJob"), ("periodic", "noc.services.discovery.jobs.periodic.job.PeriodicDiscoveryJob"), ] def field_row_class(self, o): return o.object_profile.style.css_class_name if o.object_profile.style else "" def bulk_field_interface_count(self, data): """ Apply interface_count fields :param data: :return: """ mo_ids = [x["id"] for x in data] if not mo_ids: return data # Collect interface counts r = Interface._get_collection().aggregate([ { "$match": { "managed_object": { "$in": mo_ids }, "type": "physical" } }, { "$group": { "_id": "$managed_object", "total": { "$sum": 1 } } }, ]) ifcount = dict((x["_id"], x["total"]) for x in r) # Apply interface counts for x in data: x["interface_count"] = ifcount.get(x["id"]) or 0 return data def bulk_field_link_count(self, data): """ Apply link_count fields :param data: :return: """ mo_ids = [x["id"] for x in data] if not mo_ids: return data # Collect interface counts r = Link._get_collection().aggregate([ { "$match": { "linked_objects": { "$in": mo_ids } } }, { "$unwind": "$linked_objects" }, { "$group": { "_id": "$linked_objects", "total": { "$sum": 1 } } }, ]) links_count = dict((x["_id"], x["total"]) for x in r) # Apply interface counts for x in data: x["link_count"] = links_count.get(x["id"]) or 0 return data def instance_to_dict(self, o, fields=None): def sg_to_list(items): return [{ "group": x, "group__label": unicode(ResourceGroup.get_by_id(x)) } for x in items] data = super(ManagedObjectApplication, self).instance_to_dict(o, fields) # Expand resource groups fields for fn in self.resource_group_fields: data[fn] = sg_to_list(data.get(fn) or []) return data def clean(self, data): # Clean resource groups for fn in self.resource_group_fields: if fn.startswith("effective_") and fn in data: del data[fn] continue data[fn] = [x["group"] for x in (data.get(fn) or [])] # Clean other return super(ManagedObjectApplication, self).clean(data) def cleaned_query(self, q): if "administrative_domain" in q: ad = AdministrativeDomain.get_nested_ids( int(q["administrative_domain"])) if ad: del q["administrative_domain"] else: ad = None if "selector" in q: s = self.get_object_or_404(ManagedObjectSelector, id=int(q["selector"])) del q["selector"] else: s = None r = super(ManagedObjectApplication, self).cleaned_query(q) if s: r["id__in"] = ManagedObject.objects.filter(s.Q) if ad: r["administrative_domain__in"] = ad return r def get_Q(self, request, query): q = super(ManagedObjectApplication, self).get_Q(request, query) sq = ManagedObject.get_search_Q(query) if sq: q |= sq return q def queryset(self, request, query=None): qs = super(ManagedObjectApplication, self).queryset(request, query) if not request.user.is_superuser: qs = qs.filter(UserAccess.Q(request.user)) qs = qs.exclude(name__startswith="wiping-") return qs @view(url=r"^(?P<id>\d+)/links/$", method=["GET"], access="read", api=True) def api_links(self, request, id): o = self.get_object_or_404(ManagedObject, id=id) if not o.has_access(request.user): return self.response_forbidden("Access denied") # Get links result = [] for link in Link.object_links(o): ifaces = [] r = [] for i in link.interfaces: if i.managed_object.id == o.id: ifaces += [i] else: r += [i] for li, ri in zip(ifaces, r): result += [{ "link_id": str(link.id), "local_interface": str(li.id), "local_interface__label": li.name, "remote_object": ri.managed_object.id, "remote_object__label": ri.managed_object.name, "remote_platform": ri.managed_object.platform.name if ri.managed_object.platform else "", "remote_interface": str(ri.id), "remote_interface__label": ri.name, "discovery_method": link.discovery_method, "local_description": li.description, "remote_description": ri.description, "first_discovered": link.first_discovered.isoformat() if link.first_discovered else None, "last_seen": link.last_seen.isoformat() if link.last_seen else None, }] return result @view(url=r"^(?P<id>\d+)/discovery/$", method=["GET"], access="read", api=True) def api_discovery(self, request, id): from noc.core.scheduler.job import Job o = self.get_object_or_404(ManagedObject, id=id) if not o.has_access(request.user): return self.response_forbidden("Access denied") link_count = defaultdict(int) for link in Link.object_links(o): m = link.discovery_method or "" if "+" in m: m = m.split("+")[0] link_count[m] += 1 r = [{ "name": "ping", "enable_profile": o.object_profile.enable_ping, "status": o.get_status(), "last_run": None, "last_status": None, "next_run": None, "jcls": None, }] for name, jcls in self.DISCOVERY_JOBS: job = Job.get_job_data( "discovery", jcls=jcls, key=o.id, pool=o.pool.name) or {} d = { "name": name, "enable_profile": getattr(o.object_profile, "enable_%s_discovery" % name), "status": job.get(Job.ATTR_STATUS), "last_run": self.to_json(job.get(Job.ATTR_LAST)), "last_status": job.get(Job.ATTR_LAST_STATUS), "next_run": self.to_json(job.get(Job.ATTR_TS)), "jcls": jcls, } r += [d] return r @view( url=r"^actions/set_managed/$", method=["POST"], access="create", api=True, validate={ "ids": ListOfParameter(element=ModelParameter(ManagedObject), convert=True) }, ) def api_action_set_managed(self, request, ids): for o in ids: if not o.has_access(request.user): continue o.is_managed = True o.save() return "Selected objects set to managed state" @view( url=r"^actions/set_unmanaged/$", method=["POST"], access="create", api=True, validate={ "ids": ListOfParameter(element=ModelParameter(ManagedObject), convert=True) }, ) def api_action_set_unmanaged(self, request, ids): for o in ids: if not o.has_access(request.user): continue o.is_managed = False o.save() return "Selected objects set to unmanaged state" @view(url=r"^(?P<id>\d+)/discovery/run/$", method=["POST"], access="change_discovery", api=True) def api_run_discovery(self, request, id): o = self.get_object_or_404(ManagedObject, id=id) if not o.has_access(request.user): return self.response_forbidden("Access denied") r = ujson.loads(request.body).get("names", []) for name, jcls in self.DISCOVERY_JOBS: if name not in r: continue if not getattr(o.object_profile, "enable_%s_discovery" % name): continue # Disabled by profile Job.submit("discovery", jcls, key=o.id, pool=o.pool.name) return {"success": True} @view(url=r"^(?P<id>\d+)/discovery/stop/$", method=["POST"], access="change_discovery", api=True) def api_stop_discovery(self, request, id): o = self.get_object_or_404(ManagedObject, id=id) if not o.has_access(request.user): return self.response_forbidden("Access denied") r = ujson.loads(request.body).get("names", []) for name, jcls in self.DISCOVERY_JOBS: if name not in r: continue if not getattr(o.object_profile, "enable_%s_discovery" % name): continue # Disabled by profile Job.remove("discovery", jcls, key=o.id, pool=o.pool.name) return {"success": True} @view(url=r"^(?P<id>\d+)/interface/$", method=["GET"], access="read", api=True) def api_interface(self, request, id): """ GET interfaces :param managed_object: :return: """ def sorted_iname(s): return sorted(s, key=lambda x: split_alnum(x["name"])) def get_style(i): profile = i.profile if profile: try: return style_cache[profile.id] except KeyError: pass if profile.style: s = profile.style.css_class_name else: s = "" style_cache[profile.id] = s return s else: return "" def get_link(i): link = i.link if not link: return None if link.is_ptp: # ptp o = link.other_ptp(i) label = "%s:%s" % (o.managed_object.name, o.name) elif link.is_lag: # unresolved LAG o = [ ii for ii in link.other(i) if ii.managed_object.id != i.managed_object.id ] label = "LAG %s: %s" % (o[0].managed_object.name, ", ".join( ii.name for ii in o)) else: # Broadcast label = ", ".join("%s:%s" % (ii.managed_object.name, ii.name) for ii in link.other(i)) return {"id": str(link.id), "label": label} # Get object o = self.get_object_or_404(ManagedObject, id=int(id)) if not o.has_access(request.user): return self.response_forbidden("Permission denied") # Physical interfaces # @todo: proper ordering default_state = ResourceState.get_default() style_cache = {} # profile_id -> css_style l1 = [{ "id": str(i.id), "name": i.name, "description": i.description, "status": i.status, "mac": i.mac, "ifindex": i.ifindex, "lag": (i.aggregated_interface.name if i.aggregated_interface else ""), "link": get_link(i), "profile": str(i.profile.id) if i.profile else None, "profile__label": unicode(i.profile) if i.profile else None, "enabled_protocols": i.enabled_protocols, "project": i.project.id if i.project else None, "project__label": unicode(i.project) if i.project else None, "state": i.state.id if i.state else default_state.id, "state__label": unicode(i.state if i.state else default_state), "vc_domain": i.vc_domain.id if i.vc_domain else None, "vc_domain__label": unicode(i.vc_domain) if i.vc_domain else None, "row_class": get_style(i), } for i in Interface.objects.filter(managed_object=o.id, type="physical")] # LAG lag = [{ "id": str(i.id), "name": i.name, "description": i.description, "profile": str(i.profile.id) if i.profile else None, "profile__label": unicode(i.profile) if i.profile else None, "members": [ j.name for j in Interface.objects.filter(managed_object=o.id, aggregated_interface=i.id) ], "row_class": get_style(i), } for i in Interface.objects.filter(managed_object=o.id, type="aggregated")] # L2 interfaces l2 = [{ "name": i.name, "description": i.description, "untagged_vlan": i.untagged_vlan, "tagged_vlans": i.tagged_vlans, } for i in SubInterface.objects.filter(managed_object=o.id, enabled_afi="BRIDGE")] # L3 interfaces q = MQ(enabled_afi="IPv4") | MQ(enabled_afi="IPv6") l3 = [{ "name": i.name, "description": i.description, "ipv4_addresses": i.ipv4_addresses, "ipv6_addresses": i.ipv6_addresses, "enabled_protocols": i.enabled_protocols, "vlan": i.vlan_ids, "vrf": i.forwarding_instance.name if i.forwarding_instance else "", "mac": i.mac, } for i in SubInterface.objects.filter(managed_object=o.id).filter(q)] return { "l1": sorted_iname(l1), "lag": sorted_iname(lag), "l2": sorted_iname(l2), "l3": sorted_iname(l3), } @view(url=r"^(?P<id>\d+)/interface/$", method=["POST"], access="change_interface", api=True) def api_set_interface(self, request, id): def get_or_none(c, v): if not v: return None return c.objects.get(id=v) o = self.get_object_or_404(ManagedObject, id=int(id)) if not o.has_access(request.user): return self.response_forbidden("Access denied") d = ujson.loads(request.body) if "id" in d: i = self.get_object_or_404(Interface, id=d["id"]) if i.managed_object.id != o.id: return self.response_not_found() # Set profile if "profile" in d: p = get_or_none(InterfaceProfile, d["profile"]) i.profile = p if p: i.profile_locked = True # Project if "project" in d: i.project = get_or_none(Project, d["project"]) # State if "state" in d: i.state = get_or_none(ResourceState, d["state"]) # VC Domain if "vc_domain" in d: i.vc_domain = get_or_none(VCDomain, d["vc_domain"]) # i.save() return {"success": True} @view(method=["DELETE"], url=r"^(?P<id>\d+)/?$", access="delete", api=True) def api_delete(self, request, id): """ Override default method :param request: :param id: :return: """ try: o = self.queryset(request).get(id=int(id)) except self.model.DoesNotExist: return self.render_json({ "status": False, "message": "Not found" }, status=self.NOT_FOUND) if not o.has_access(request.user): return self.response_forbidden("Access denied") # Run sa.wipe_managed_object job instead o.name = "wiping-%d" % o.id o.is_managed = False o.description = "Wiping! Do not touch!" o.save() call_later("noc.sa.wipe.managedobject.wipe", o=o.id) return HttpResponse(status=self.DELETED) @view( url=r"^actions/run_discovery/$", method=["POST"], access="launch", api=True, validate={ "ids": ListOfParameter(element=ModelParameter(ManagedObject), convert=True) }, ) def api_action_run_discovery(self, request, ids): d = 0 for o in ids: if not o.has_access(request.user): continue o.run_discovery(delta=d) d += 1 return "Discovery processes has been scheduled" def get_nested_inventory(self, o): rev = o.get_data("asset", "revision") if rev == "None": rev = "" r = { "id": str(o.id), "serial": o.get_data("asset", "serial"), "revision": rev or "", "description": o.model.description, "model": o.model.name, } children = [] for n in o.model.connections: if n.direction == "i": c, r_object, _ = o.get_p2p_connection(n.name) if c is None: children += [{ "id": None, "name": n.name, "leaf": True, "serial": None, "description": "--- EMPTY ---", "model": None, }] else: cc = self.get_nested_inventory(r_object) cc["name"] = n.name children += [cc] elif n.direction == "s": children += [{ "id": None, "name": n.name, "leaf": True, "serial": None, "description": n.description, "model": ", ".join(n.protocols), }] if children: to_expand = "Transceiver" not in o.model.name r["children"] = children r["expanded"] = to_expand else: r["leaf"] = True return r @view(url=r"^(?P<id>\d+)/inventory/$", method=["GET"], access="read", api=True) def api_inventory(self, request, id): o = self.get_object_or_404(ManagedObject, id=id) if not o.has_access(request.user): return self.response_forbidden("Access denied") r = [] for p in o.get_inventory(): c = self.get_nested_inventory(p) c["name"] = p.name or o.name r += [c] return {"expanded": True, "children": r} @view(url=r"^(?P<id>\d+)/confdb/$", method=["GET"], access="config", api=True) def api_confdb(self, request, id): o = self.get_object_or_404(ManagedObject, id=id) if not o.has_access(request.user): return self.response_forbidden("Access denied") cleanup = True if "cleanup" in request.GET: c = request.GET["cleanup"].strip().lower() cleanup = c not in ("no", "false", "0") cdb = o.get_confdb(cleanup=cleanup) return self.render_plain_text(cdb.dump("json"), content_type="text/json") @view( url=r"^(?P<id>\d+)/confdb/$", method=["POST"], validate={ "query": StringParameter(), "cleanup": BooleanParameter(default=True), "dump": BooleanParameter(default=False), }, access="config", api=True, ) def api_confdb_query(self, request, id, query="", cleanup=True, dump=False): o = self.get_object_or_404(ManagedObject, id=id) if not o.has_access(request.user): return self.response_forbidden("Access denied") cdb = o.get_confdb(cleanup=cleanup) try: r = list(cdb.query(query)) result = {"status": True, "result": r} if dump: result["confdb"] = ujson.loads(cdb.dump("json")) except SyntaxError as e: result = {"status": False, "error": str(e)} return result @view(url=r"^(?P<id>\d+)/job_log/(?P<job>\S+)/$", method=["GET"], access="read", api=True) def api_job_log(self, request, id, job): o = self.get_object_or_404(ManagedObject, id=id) if not o.has_access(request.user): return self.response_forbidden("Access denied") # fs = gridfs.GridFS(get_db(), "noc.joblog") key = "discovery-%s-%s" % (job, o.id) d = get_db()["noc.joblog"].find_one({"_id": key}) if d and d["log"]: return self.render_plain_text(zlib.decompress(str(d["log"]))) else: return self.render_plain_text("No data") @view(url=r"^(?P<id>\d+)/interactions/$", method=["GET"], access="interactions", api=True) def api_interactions(self, request, id): o = self.get_object_or_404(ManagedObject, id=id) if not o.has_access(request.user): return self.response_forbidden("Access denied") return [{ "ts": self.to_json(i.timestamp), "op": i.op, "user": i.user, "text": i.text } for i in InteractionLog.objects.filter( object=o.id).order_by("-timestamp")] @view(url=r"^(?P<id>\d+)/scripts/$", method=["GET"], access="script", api=True) def api_scripts(self, request, id): o = self.get_object_or_404(ManagedObject, id=id) if not o.has_access(request.user): return self.response_forbidden("Access denied") r = [] for s in o.scripts: sn = o.profile.name + "." + s script = script_loader.get_script(sn) if not script: self.logger.error("Failed to load script: %s", sn) continue interface = script.interface() ss = { "name": s, "has_input": any(interface.gen_parameters()), "require_input": interface.has_required_params, "form": interface.get_form(), "preview": interface.preview or "NOC.sa.managedobject.scripts.JSONPreview", } r += [ss] return r @view(url=r"^(?P<id>\d+)/scripts/(?P<name>[^/]+)/$", method=["POST"], access="script", api=True) def api_run_script(self, request, id, name): o = self.get_object_or_404(ManagedObject, id=id) if not o.has_access(request.user): return {"error": "Access denied"} if name not in o.scripts: return {"error": "Script not found: %s" % name} params = self.deserialize(request.body) try: result = o.scripts[name](**params) except Exception as e: return {"error": str(e)} return {"result": result} @view(url=r"^(?P<id>\d+)/console/$", method=["POST"], access="console", api=True) def api_console_command(self, request, id): o = self.get_object_or_404(ManagedObject, id=id) if not o.has_access(request.user): return {"error": "Access denied"} if "commands" not in o.scripts: return {"error": "Script not found: commands"} params = self.deserialize(request.body) try: result = o.scripts.commands(**params) except Exception as e: return {"error": str(e)} return {"result": result} @view(url=r"(?P<id>\d+)/caps/$", method=["GET"], access="read", api=True) def api_get_caps(self, request, id): o = self.get_object_or_404(ManagedObject, id=id) if not o.has_access(request.user): return self.response_forbidden("Access denied") r = [] oc = ObjectCapabilities.objects.filter(object=o).first() if oc: for c in oc.caps: r += [{ "capability": c.capability.name, "description": c.capability.description, "type": c.capability.type, "value": c.value, "source": c.source, }] return sorted(r, key=lambda x: x["capability"]) @view(url=r"(?P<id>\d+)/facts/$", method=["GET"], access="read", api=True) def api_get_facts(self, request, id): o = self.get_object_or_404(ManagedObject, id=id) if not o.has_access(request.user): return self.response_forbidden("Access denied") return sorted( ({ "cls": f.cls, "label": f.label, "attrs": [{ "name": a, "value": f.attrs[a] } for a in f.attrs], "introduced": f.introduced.isoformat(), "changed": f.changed.isoformat(), } for f in ObjectFact.objects.filter(object=o.id)), key=lambda x: (x["cls"], x["label"]), ) @view(url=r"(?P<id>\d+)/revalidate/$", method=["POST"], access="read", api=True) def api_revalidate(self, request, id): def revalidate(o): engine = Engine(o) engine.check() return self.response({"status": True}, self.OK) o = self.get_object_or_404(ManagedObject, id=id) if not o.has_access(request.user): return self.response_forbidden("Access denied") return self.submit_slow_op(request, revalidate, o) @view(url=r"(?P<id>\d+)/actions/(?P<action>\S+)/$", method=["POST"], access="action", api=True) def api_action(self, request, id, action): def execute(o, a, args): return a.execute(o, **args) o = self.get_object_or_404(ManagedObject, id=id) if not o.has_access(request.user): return self.response_forbidden("Access denied") a = self.get_object_or_404(Action, name=action) # @todo: Check access body = request.body if body: args = ujson.loads(body) else: args = {} return self.submit_slow_op(request, execute, o, a, args) @view(url=r"^link/fix/(?P<link_id>[0-9a-f]{24})/$", method=["POST"], access="change_link") def api_fix_links(self, request, link_id): def get_mac(arp, ip): for r in arp: if r["ip"] == ip: return r["mac"] return None def get_interface(macs, mac): for m in macs: if m["mac"] == mac: return m["interfaces"][0] return None def error_status(message, *args): self.logger.error(message, *args) return {"status": False, "message": message % args} def success_status(message, *args): self.logger.error(message, *args) return {"status": True, "message": message % args} link = self.get_object_or_404(Link, id=link_id) if len(link.interfaces) != 2: return error_status("Cannot fix link: Not P2P") mo1 = link.interfaces[0].managed_object mo2 = link.interfaces[1].managed_object if mo1.id == mo2.id: return error_status("Cannot fix circular links") # Ping each other self.logger.info("[%s] Pinging %s", mo1.name, mo2.address) r1 = mo1.scripts.ping(address=mo2.address) if not r1["success"]: return error_status("Failed to ping %s", mo2.name) self.logger.info("[%s] Pinging %s", mo2.name, mo1.address) r2 = mo2.scripts.ping(address=mo1.address) if not r2["success"]: return error_status("Failed to ping %s", mo1.name) # Get ARPs mac2 = get_mac(mo1.scripts.get_arp(), mo2.address) if not mac2: return error_status("[%s] ARP cache is not filled properly", mo1.name) self.logger.info("[%s] MAC=%s", mo2.name, mac2) mac1 = get_mac(mo2.scripts.get_arp(), mo1.address) if not mac1: return error_status("[%s] ARP cache is not filled properly", mo2.name) self.logger.info("[%s] MAC=%s", mo1.name, mac1) # Get MACs r1 = mo1.scripts.get_mac_address_table(mac=mac2) self.logger.info("[%s] MACS=%s", mo1.name, r1) r2 = mo2.scripts.get_mac_address_table(mac=mac1) self.logger.info("[%s] MACS=%s", mo2.name, r2) # mo1: Find mo2 i1 = get_interface(r1, mac2) if not i1: return error_status("[%s] Cannot find %s in the MAC address table", mo1.name, mo2.name) # mo2: Find mo1 i2 = get_interface(r2, mac1) if not i1: return error_status("[%s] Cannot find %s in the MAC address table", mo2.name, mo1.name) self.logger.info("%s:%s -- %s:%s", mo1.name, i1, mo2.name, i2) if link.interfaces[0].name == i1 and link.interfaces[1].name == i2: return success_status("Linked properly") # Get interfaces iface1 = mo1.get_interface(i1) if not iface1: return error_status("[%s] Interface not found: %s", mo1.name, i1) iface2 = mo2.get_interface(i2) if not iface2: return error_status("[%s] Interface not found: %s", mo2.name, i2) # Check we can relink if_ids = [i.id for i in link.interfaces] if iface1.id not in if_ids and iface1.is_linked: return error_status("[%s] %s is already linked", mo1.name, i1) if iface2.id not in if_ids and iface2.is_linked: return error_status("[%s] %s is already linked", mo2.name, i2) # Relink self.logger.info("Relinking") link.delete() iface1.link_ptp(iface2, method="macfix") return success_status("Relinked") @view(url=r"^(?P<id>\d+)/cpe/$", method=["GET"], access="read", api=True) def api_cpe(self, request, id): """ GET CPEs :param request: :param id: :return: """ def sorted_iname(s): return sorted(s, key=lambda x: split_alnum(x["name"])) # Get object o = self.get_object_or_404(ManagedObject, id=int(id)) if not o.has_access(request.user): return self.response_forbidden("Permission denied") # CPE # @todo: proper ordering # default_state = ResourceState.get_default() # style_cache = {} # profile_id -> css_style l1 = [ { "global_id": str(c.global_id), "name": c.name or "", "interface": c.interface, "local_id": c.local_id, "serial": c.serial or "", "status": c.status, "description": c.description or "", "address": c.ip or "", "model": c.model or "", "version": c.version or "", "mac": c.mac or "", "location": c.location or "", "distance": str(c.distance) # "row_class": get_style(i) } for c in CPEStatus.objects.filter(managed_object=o.id) ] return {"cpe": sorted_iname(l1)}
class PeerApplication(ExtModelApplication): """ Peers application """ title = "Peers" menu = "Peers" model = Peer query_fields = [ "remote_asn__icontains", "description__icontains", "local_ip__icontains", "local_backup_ip__icontains", "remote_ip__icontains", "remote_backup_ip__icontains" ] def clean(self, data): data = super(PeerApplication, self).clean(data) ## Check address fields if not is_prefix(data["local_ip"]): raise ValueError( "Invalid 'Local IP Address', must be in x.x.x.x/x form or IPv6 prefix" ) if not is_prefix(data["remote_ip"]): raise ValueError( "Invalid 'Remote IP Address', must be in x.x.x.x/x form or IPv6 prefix" ) if "local_backup_ip" in data and data["local_backup_ip"]: if not is_prefix(data["local_backup_ip"]): raise ValueError( "Invalid 'Local Backup IP Address', must be in x.x.x.x/x form or IPv6 prefix" ) if "remote_backup_ip" in data and data["remote_backup_ip"]: if not is_prefix(data["remote_backup_ip"]): raise ValueError( "Invalid 'Remote Backup IP Address', must be in x.x.x.x/x form or IPv6 prefix" ) ## Check no or both backup addresses given has_local_backup = "local_backup_ip" in data and data["local_backup_ip"] has_remote_backup = "remote_backup_ip" in data and data[ "remote_backup_ip"] if has_local_backup and not has_remote_backup: raise ValueError("One of backup addresses given. Set peer address") if not has_local_backup and has_remote_backup: raise ValueError("One of backup addresses given. Set peer address") ## Check all link addresses belongs to one AFI if len( set([ IP.prefix(data[x]).afi for x in [ "local_ip", "remote_ip", "local_backup_ip", "remote_backup_ip" ] if x in data and data[x] ])) > 1: raise ValueError( "All neighboring addresses must have same address family") return data ## ## Change peer status ## def set_peer_status(self, request, queryset, status, message): count = 0 for p in queryset: p.status = status p.save() count += 1 if count == 1: return "1 peer marked as %s" % message else: return "%d peers marked as %s" % (count, message) @view(url="^actions/planned/$", method=["POST"], access="update", api=True, validate={"ids": ListOfParameter(element=ModelParameter(Peer))}) def api_action_planned(self, request, ids): return self.set_peer_status(request, ids, "P", "planned") api_action_planned.short_description = "Mark as planned" @view(url="^actions/active/$", method=["POST"], access="update", api=True, validate={"ids": ListOfParameter(element=ModelParameter(Peer))}) def api_action_active(self, request, ids): return self.set_peer_status(request, ids, "A", "active") api_action_active.short_description = "Mark as active" @view(url="^actions/shutdown/$", method=["POST"], access="update", api=True, validate={"ids": ListOfParameter(element=ModelParameter(Peer))}) def api_action_shutdown(self, request, ids): return self.set_peer_status(request, ids, "S", "shutdown") api_action_shutdown.short_description = "Mark as shutdown"
class VCApplication(ExtModelApplication): """ VC application """ title = _("VC") menu = _("Virtual Circuits") model = VC query_fields = ["name", "description"] query_condition = "icontains" int_query_fields = ["l1", "l2"] implied_permissions = {"read": ["vc:vcdomain:lookup", "main:style:lookup"]} def get_vc_domain_objects(self, vc_domain): return vc_domain.managedobject_set.all() def lookup_vcfilter(self, q, name, value): """ Resolve __vcflter lookups :param q: :param name: :param value: :return: """ value = ModelParameter(VCFilter).clean(value) x = value.to_sql(name) try: q[None] += [x] except KeyError: q[None] = [x] @cachedmethod(key="vc-interface-count-%s") def get_vc_interfaces_count(self, vc_id): vc = VC.get_by_id(vc_id) if not vc: return 0 objects = vc.vc_domain.managedobject_set.values_list("id", flat=True) l1 = vc.l1 n = SubInterface.objects.filter( Q(managed_object__in=objects) & ( Q(untagged_vlan=l1, enabled_afi=["BRIDGE"]) | Q(tagged_vlans=l1, enabled_afi=["BRIDGE"]) | Q(vlan_ids=l1) ) ).count() return n @cachedmethod(key="vc-prefixes-%s") def get_vc_prefixes(self, vc_id): vc = VC.get_by_id(vc_id) if not vc: return [] objects = vc.vc_domain.managedobject_set.values_list("id", flat=True) ipv4 = set() ipv6 = set() # @todo: Exact match on vlan_ids for si in SubInterface.objects.filter( Q(managed_object__in=objects) & Q(vlan_ids=vc.l1) & (Q(enabled_afi=["IPv4"]) | Q(enabled_afi=["IPv6"])) ).only("enabled_afi", "ipv4_addresses", "ipv6_addresses"): if "IPv4" in si.enabled_afi: ipv4.update([IP.prefix(ip).first for ip in si.ipv4_addresses]) if "IPv6" in si.enabled_afi: ipv6.update([IP.prefix(ip).first for ip in si.ipv6_addresses]) p = [str(x.first) for x in sorted(ipv4)] p += [str(x.first) for x in sorted(ipv6)] return p def field_interfaces_count(self, obj): return self.get_vc_interfaces_count(obj.id) def field_prefixes(self, obj): p = self.get_vc_prefixes(obj.id) if p: return ", ".join(p) else: return "-" def field_row_class(self, o): return o.style.css_class_name if o.style else "" @view( url="^find_free/$", method=["GET"], access="read", api=True, validate={"vc_domain": ModelParameter(VCDomain), "vc_filter": ModelParameter(VCFilter)}, ) def api_find_free(self, request, vc_domain, vc_filter, **kwargs): return vc_domain.get_free_label(vc_filter) @view( url="^bulk/import/", method=["POST"], access="import", api=True, validate={ "vc_domain": ModelParameter(VCDomain), "items": ListOfParameter( element=DictParameter( attrs={ "l1": IntParameter(), "l2": IntParameter(), "name": StringParameter(), "description": StringParameter(default=""), } ) ), }, ) def api_bulk_import(self, request, vc_domain, items): n = 0 for i in items: if not VC.objects.filter(vc_domain=vc_domain, l1=i["l1"], l2=i["l2"]).exists(): # Add only not-existing VC( vc_domain=vc_domain, l1=i["l1"], l2=i["l2"], name=i["name"], description=i["description"], ).save() n += 1 return {"status": True, "imported": n} @view(url=r"^(?P<vc_id>\d+)/interfaces/$", method=["GET"], access="read", api=True) def api_interfaces(self, request, vc_id): """ Returns a dict of {untagged: ..., tagged: ...., l3: ...} :param request: :param vc_id: :return: """ vc = self.get_object_or_404(VC, id=int(vc_id)) l1 = vc.l1 # Managed objects in VC domain objects = set(vc.vc_domain.managedobject_set.values_list("id", flat=True)) # Find untagged interfaces si_objects = defaultdict(list) for si in SubInterface.objects.filter( managed_object__in=objects, untagged_vlan=l1, enabled_afi="BRIDGE" ): si_objects[si.managed_object] += [{"name": si.name}] untagged = [ { "managed_object_id": o.id, "managed_object_name": o.name, "interfaces": sorted(si_objects[o], key=lambda x: x["name"]), } for o in si_objects ] # Find tagged interfaces si_objects = defaultdict(list) for si in SubInterface.objects.filter( managed_object__in=objects, tagged_vlans=l1, enabled_afi="BRIDGE" ): si_objects[si.managed_object] += [{"name": si.name}] tagged = [ { "managed_object_id": o.id, "managed_object_name": o.name, "interfaces": sorted(si_objects[o], key=lambda x: x["name"]), } for o in si_objects ] # Find l3 interfaces si_objects = defaultdict(list) for si in SubInterface.objects.filter(managed_object__in=objects, vlan_ids=l1): si_objects[si.managed_object] += [ { "name": si.name, "ipv4_addresses": si.ipv4_addresses, "ipv6_addresses": si.ipv6_addresses, } ] l3 = [ { "managed_object_id": o.id, "managed_object_name": o.name, "interfaces": sorted(si_objects[o], key=lambda x: x["name"]), } for o in si_objects ] # Update caches ic = sum(len(x["interfaces"]) for x in untagged) ic += sum(len(x["interfaces"]) for x in tagged) ic += sum(len(x["interfaces"]) for x in l3) # return { "untagged": sorted(untagged, key=lambda x: x["managed_object_name"]), "tagged": sorted(tagged, key=lambda x: x["managed_object_name"]), "l3": sorted(l3, key=lambda x: x["managed_object_name"]), }
class PrefixListBuilderApplication(ExtApplication): """ Interactive prefix list builder """ title = _("Prefix List Builder") menu = _("Prefix List Builder") @view(method=["GET"], url=r"^$", access="read", api=True, validate={ "peering_point": ModelParameter(PeeringPoint), "name": UnicodeParameter(required=False), "as_set": UnicodeParameter() }) def api_list(self, request, peering_point, name, as_set): if not WhoisCache.has_asset_members(): return { "name": name, "prefix_list": "", "success": False, "message": _("AS-SET members cache is empty. Please update Whois Cache") } if not WhoisCache.has_origin_routes(): return { "name": name, "prefix_list": "", "success": False, "message": _("Origin routes cache is empty. Please update Whois Cache") } if not WhoisCache.has_asset(as_set): return { "name": name, "prefix_list": "", "success": False, "message": _("Unknown AS-SET") } prefixes = WhoisCache.resolve_as_set_prefixes_maxlen(as_set) if not prefixes: return { "name": name, "prefix_list": "", "success": False, "message": _("Cannot resolve AS-SET prefixes") } try: pl = peering_point.profile.get_profile().generate_prefix_list( name, prefixes) except NotImplementedError: return { "name": name, "prefix_list": "", "success": False, "message": _("Prefix-list generator is not implemented for this profile") } return { "name": name, "prefix_list": pl, "success": True, "message": _("Prefix List built") }
class PrefixApplication(ExtModelApplication): """ Prefix application """ title = _("Prefix") model = Prefix def field_row_class(self, o): return o.profile.style.css_class_name if o.profile and o.profile.style else "" def can_create(self, user, obj): return PrefixAccess.user_can_change(user, obj.vrf, obj.afi, obj.prefix) def can_update(self, user, obj): return PrefixAccess.user_can_change(user, obj.vrf, obj.afi, obj.prefix) def can_delete(self, user, obj): return PrefixAccess.user_can_change(user, obj.vrf, obj.afi, obj.prefix) def queryset(self, request, query=None): qs = super(PrefixApplication, self).queryset(request, query=query) return qs.filter(PrefixAccess.read_Q(request.user)) @view( url=r"^(?P<prefix_id>\d+)/rebase/$", method=["POST"], access="rebase", api=True, validate={ "to_vrf": ModelParameter(VRF), "to_prefix": PrefixParameter() }, ) def api_rebase(self, request, prefix_id, to_vrf, to_prefix): prefix = self.get_object_or_404(Prefix, id=int(prefix_id)) try: new_prefix = prefix.rebase(to_vrf, to_prefix) return self.instance_to_dict(new_prefix) except ValueError as e: return self.response_bad_request(str(e)) @view(url=r"^(?P<prefix_id>\d+)/suggest_free/$", method=["GET"], access="read", api=True) def api_suggest_free(self, request, prefix_id): """ Suggest free blocks of different sizes :param request: :param prefix_id: :return: """ prefix = self.get_object_or_404(Prefix, id=int(prefix_id)) suggestions = [] p_mask = int(prefix.prefix.split("/")[1]) free = sorted( IP.prefix(prefix.prefix).iter_free( [pp.prefix for pp in prefix.children_set.all()]), key=attrgetter("mask"), reverse=True, ) # Find smallest free block possible for mask in range(30 if prefix.is_ipv4 else 64, max(p_mask + 1, free[-1].mask) - 1, -1): # Find smallest free block possible for p in free: if p.mask <= mask: suggestions += [{ "prefix": "%s/%d" % (p.address, mask), "size": 2**(32 - mask) if prefix.is_ipv4 else None, }] break return suggestions @view(method=["DELETE"], url=r"^(?P<id>\d+)/recursive/$", access="delete", api=True) def api_delete_recursive(self, request, id): try: o = self.queryset(request).get(**{self.pk: int(id)}) except self.model.DoesNotExist: return self.render_json({ "status": False, "message": "Not found" }, status=self.NOT_FOUND) # Check permissions if not self.can_delete(request.user, o): return self.render_json( { "status": False, "message": "Permission denied" }, status=self.FORBIDDEN) try: o.delete_recursive() except ValueError as e: return self.render_json( { "success": False, "message": "ERROR: %s" % e }, status=self.CONFLICT) return HttpResponse(status=self.DELETED) @view(r"^(?P<id>\d+)/get_path/$", access="read", api=True) def api_get_path(self, request, id): o = self.get_object_or_404(Prefix, id=int(id)) try: path = [Prefix.objects.get(id=ns) for ns in o.get_path()] return { "data": [{ "id": str(p.id), "name": smart_text(p.name), "afi": p.afi } for p in path] } except ValueError as e: return self.response_bad_request(str(e))
class AlarmApplication(ExtApplication): """ fm.alarm application """ title = _("Alarm") menu = _("Alarms") glyph = "exclamation-triangle" implied_permissions = {"launch": ["sa:managedobject:alarm"]} model_map = {"A": ActiveAlarm, "C": ArchivedAlarm} clean_fields = { "managed_object": ModelParameter(ManagedObject), "timestamp": DateTimeParameter(), } ignored_params = ["status", "_dc"] diagnostic_plugin = AlarmPlugin(name="diagnostic", config={}) advanced_filter_params = { "service_profile": "total_services", "subscribers_profile": "total_subscribers", "profile": get_advanced_field, } DEFAULT_ARCH_ALARM = datetime.timedelta( seconds=config.web.api_arch_alarm_limit) rx_oper_splitter = re.compile(r"^(?P<field>\S+)(?P<f_num>\d+)__in") def __init__(self, *args, **kwargs): ExtApplication.__init__(self, *args, **kwargs) from .plugins.base import AlarmPlugin # Load plugins self.plugins = {} for f in os.listdir("services/web/apps/fm/alarm/plugins/"): if not f.endswith(".py") or f == "base.py" or f.startswith("_"): continue mn = "noc.services.web.apps.fm.alarm.plugins.%s" % f[:-3] m = __import__(mn, {}, {}, "*") for on in dir(m): o = getattr(m, on) if (inspect.isclass(o) and issubclass(o, AlarmPlugin) and o.__module__.startswith(mn)): assert o.name self.plugins[o.name] = o(self) def cleaned_query(self, q): q = q.copy() status = q["status"] if "status" in q else "A" for p in self.ignored_params: if p in q: del q[p] for p in ( self.limit_param, self.page_param, self.start_param, self.format_param, self.sort_param, self.query_param, self.only_param, ): if p in q: del q[p] # Extract IN # extjs not working with same parameter name in query for p in list(q): if p.endswith("__in") and self.rx_oper_splitter.match(p): field = self.rx_oper_splitter.match(p).group("field") + "__in" if field not in q: q[field] = [q[p]] else: q[field] += [q[p]] del q[p] # Normalize parameters for p in list(q): qp = p.split("__")[0] if qp in self.clean_fields: q[p] = self.clean_fields[qp].clean(q[p]) # Advanced filter for p in self.advanced_filter_params: params = [] for x in list(q): if x.startswith(p): params += [q[x]] del q[x] if params: af = self.advanced_filter(self.advanced_filter_params[p], params) if "__raw__" in q and "__raw__" in af: # Multiple raw query q["__raw__"].update(af["__raw__"]) del af["__raw__"] q.update(af) # Exclude maintenance if "maintenance" not in q: q["maintenance"] = "hide" if q["maintenance"] == "hide" and status == "A": q["managed_object__nin"] = Maintenance.currently_affected() elif q["maintenance"] == "only" and status == "A": q["managed_object__in"] = Maintenance.currently_affected() del q["maintenance"] if "administrative_domain" in q: if q["administrative_domain"] != "_root_": q["adm_path"] = int(q["administrative_domain"]) q.pop("administrative_domain") if "administrative_domain__in" in q: if "_root_" not in q["administrative_domain__in"]: q["adm_path__in"] = q["administrative_domain__in"] q.pop("administrative_domain__in") if "segment" in q: if q["segment"] != "_root_": q["segment_path"] = bson.ObjectId(q["segment"]) q.pop("segment") if "managedobjectselector" in q: s = SelectorCache.objects.filter( selector=q["managedobjectselector"]).values_list("object") if "managed_object__in" in q: q["managed_object__in"] = list( set(q["managed_object__in"]).intersection(s)) else: q["managed_object__in"] = s q.pop("managedobjectselector") if "cleared_after" in q: q["clear_timestamp__gte"] = datetime.datetime.now( ) - datetime.timedelta(seconds=int(q["cleared_after"])) q.pop("cleared_after") # if "wait_tt" in q: q["wait_tt__exists"] = True q["wait_ts__exists"] = False del q["wait_tt"] # if "collapse" in q: c = q["collapse"] del q["collapse"] if c != "0": q["root__exists"] = False if status == "C": if ("timestamp__gte" not in q and "timestamp__lte" not in q and "escalation_tt__contains" not in q and "managed_object" not in q): q["timestamp__gte"] = datetime.datetime.now( ) - self.DEFAULT_ARCH_ALARM return q def advanced_filter(self, field, params): """ Field: field0=ProfileID,field1=ProfileID:true.... cq - caps query mq - main_query field0=ProfileID - Profile is exists field0=!ProfileID - Profile is not exists field0=ProfileID:true - Summary value equal True field0=ProfileID:2~50 - Summary value many then 2 and less then 50 :param field: Query Field name :param params: Query params :return: """ q = {} c_in = [] c_nin = [] for c in params: if not c: continue if "!" in c: # @todo Добавить исключение (только этот) !ID c_id = c[1:] c_query = "nexists" elif ":" not in c: c_id = c c_query = "exists" else: c_id, c_query = c.split(":", 1) try: if callable(field): field, c_id = field(c_id) c_id = bson.ObjectId(c_id) except bson.errors.InvalidId as e: self.logger.warning(e) continue if "~" in c_query: l, r = c_query.split("~") if not l: cond = {"$lte": int(r)} elif not r: cond = {"$gte": int(l)} else: cond = {"$lte": int(r), "$gte": int(l)} q["__raw__"] = { field: { "$elemMatch": { "profile": c_id, "summary": cond } } } elif c_query == "exists": c_in += [c_id] continue elif c_query == "nexists": c_nin += [c_id] continue else: try: c_query = int(c_query) q["__raw__"] = { field: { "$elemMatch": { "profile": c_id, "summary": int(c_query) } } } except ValueError: q["__raw__"] = { field: { "$elemMatch": { "profile": c_id, "summary": { "$regex": c_query } } } } if c_in: q["%s__profile__in" % field] = c_in if c_nin: q["%s__profile__nin" % field] = c_nin return q def instance_to_dict(self, o, fields=None): s = AlarmSeverity.get_severity(o.severity) n_events = (ActiveEvent.objects.filter(alarms=o.id).count() + ArchivedEvent.objects.filter(alarms=o.id).count()) d = { "id": str(o.id), "status": o.status, "managed_object": o.managed_object.id, "managed_object__label": o.managed_object.name, "administrative_domain": o.managed_object.administrative_domain_id, "administrative_domain__label": o.managed_object.administrative_domain.name, "severity": o.severity, "severity__label": s.name, "alarm_class": str(o.alarm_class.id), "alarm_class__label": o.alarm_class.name, "timestamp": self.to_json(o.timestamp), "subject": o.subject, "events": n_events, "duration": o.duration, "clear_timestamp": self.to_json(o.clear_timestamp) if o.status == "C" else None, "row_class": s.style.css_class_name, "segment__label": o.managed_object.segment.name, "segment": str(o.managed_object.segment.id), "location_1": self.location(o.managed_object.container.id)[0] if o.managed_object.container else "", "location_2": self.location(o.managed_object.container.id)[1] if o.managed_object.container else "", "escalation_tt": o.escalation_tt, "escalation_error": o.escalation_error, "platform": o.managed_object.platform.name if o.managed_object.platform else "", "address": o.managed_object.address, "ack_ts": self.to_json(o.ack_ts), "ack_user": o.ack_user, "summary": self.f_glyph_summary({ "subscriber": SummaryItem.items_to_dict(o.total_subscribers), "service": SummaryItem.items_to_dict(o.total_services), }), "total_objects": sum(x.summary for x in o.total_objects), "total_subscribers": self.f_summary( {"subscriber": SummaryItem.items_to_dict(o.total_subscribers)}), "total_services": self.f_summary( {"service": SummaryItem.items_to_dict(o.total_services)}), "logs": [{ "timestamp": self.to_json(ll.timestamp), "user": ll.source or "NOC", "message": ll.message, } for ll in o.log if getattr(ll, "source", None) ][:config.web.api_alarm_comments_limit], } if fields: d = {k: d[k] for k in fields} return d def queryset(self, request, query=None): """ Filter records for lookup """ status = request.GET.get("status", "A") if status not in self.model_map: raise Exception("Invalid status") model = self.model_map[status] if request.user.is_superuser: return model.objects.filter().read_preference( ReadPreference.SECONDARY_PREFERRED).all() else: return model.objects.filter(adm_path__in=UserAccess.get_domains( request.user), ).read_preference( ReadPreference.SECONDARY_PREFERRED) @view(url=r"^$", access="launch", method=["GET"], api=True) def api_list(self, request): return self.list_data(request, self.instance_to_dict) @view(url=r"^(?P<id>[a-z0-9]{24})/$", method=["GET"], api=True, access="launch") def api_alarm(self, request, id): alarm = get_alarm(id) if not alarm: self.response_not_found() user = request.user d = self.instance_to_dict(alarm) d["body"] = alarm.body d["symptoms"] = alarm.alarm_class.symptoms d["probable_causes"] = alarm.alarm_class.probable_causes d["recommended_actions"] = alarm.alarm_class.recommended_actions d["vars"] = sorted(alarm.vars.items()) d["status"] = alarm.status d["status__label"] = {"A": "Active", "C": "Cleared"}[alarm.status] # Managed object properties mo = alarm.managed_object d["managed_object_address"] = mo.address d["managed_object_profile"] = mo.profile.name d["managed_object_platform"] = mo.platform.name if mo.platform else "" d["managed_object_version"] = mo.version.version if mo.version else "" d["segment"] = mo.segment.name d["segment_id"] = str(mo.segment.id) d["segment_path"] = " | ".join( NetworkSegment.get_by_id(p).name for p in NetworkSegment.get_path(mo.segment)) if mo.container: cp = [] c = mo.container.id while c: try: o = Object.objects.get(id=c) if o.container: cp.insert(0, o.name) c = o.container.id if o.container else None except DoesNotExist: break d["container_path"] = " | ".join(cp) if not self.location(mo.container.id)[0]: d["address_path"] = None else: d["address_path"] = ", ".join(self.location(mo.container.id)) d["tags"] = mo.labels # Log if alarm.log: d["log"] = [{ "timestamp": self.to_json(ll.timestamp), "from_status": ll.from_status, "to_status": ll.to_status, "source": getattr(ll, "source", ""), "message": ll.message, } for ll in alarm.log] # Events events = [] for ec in ActiveEvent, ArchivedEvent: for e in ec.objects.filter(alarms=alarm.id): events += [{ "id": str(e.id), "event_class": str(e.event_class.id), "event_class__label": e.event_class.name, "timestamp": self.to_json(e.timestamp), "status": e.status, "managed_object": e.managed_object.id, "managed_object__label": e.managed_object.name, "subject": e.subject, }] if events: d["events"] = events # Alarms children = self.get_nested_alarms(alarm) if children: d["alarms"] = {"expanded": True, "children": children} # Subscribers if alarm.status == "A": d["subscribers"] = self.get_alarm_subscribers(alarm) d["is_subscribed"] = user in alarm.subscribers # Apply plugins plugins = [] acp = alarm.alarm_class.plugins or [] acp += [self.diagnostic_plugin] for p in acp: if p.name in self.plugins: plugin = self.plugins[p.name] dd = plugin.get_data(alarm, p.config) if "plugins" in dd: plugins += dd["plugins"] del dd["plugins"] d.update(dd) if plugins: d["plugins"] = plugins return d def get_alarm_subscribers(self, alarm): """ JSON-serializable subscribers :param alarm: :return: """ subscribers = [] for u in alarm.subscribers: try: u = User.objects.get(id=u) subscribers += [{ "id": u.id, "name": " ".join([u.first_name, u.last_name]), "login": u.username }] except User.DoesNotExist: pass return subscribers def get_nested_alarms(self, alarm): """ Return nested alarms as a part of NodeInterface :param alarm: :return: """ children = [] for ac in (ActiveAlarm, ArchivedAlarm): for a in ac.objects.filter(root=alarm.id): s = AlarmSeverity.get_severity(a.severity) c = { "id": str(a.id), "subject": a.subject, "alarm_class": str(a.alarm_class.id), "alarm_class__label": a.alarm_class.name, "managed_object": a.managed_object.id, "managed_object__label": a.managed_object.name, "timestamp": self.to_json(a.timestamp), "iconCls": "icon_error", "row_class": s.style.css_class_name, } nc = self.get_nested_alarms(a) if nc: c["children"] = nc c["expanded"] = True else: c["leaf"] = True children += [c] return children @view( url=r"^(?P<id>[a-z0-9]{24})/post/", method=["POST"], api=True, access="launch", validate={"msg": UnicodeParameter()}, ) def api_post(self, request, id, msg): alarm = get_alarm(id) if not alarm: self.response_not_found() alarm.log_message(msg, source=request.user.username) return True @view( url=r"^comment/post/", method=["POST"], api=True, access="launch", validate={ "ids": StringListParameter(required=True), "msg": UnicodeParameter(), }, ) def api_comment_post(self, request, ids, msg): alarms = list(ActiveAlarm.objects.filter(id__in=ids)) alarms += list(ArchivedAlarm.objects.filter(id__in=ids)) if not alarms: self.response_not_found() for alarm in alarms: alarm.log_message(msg, source=request.user.username) return True @view( url=r"^(?P<id>[a-z0-9]{24})/acknowledge/", method=["POST"], api=True, access="acknowledge", validate={ "msg": UnicodeParameter(default=""), }, ) def api_acknowledge(self, request, id, msg=""): alarm = get_alarm(id) if not alarm: return self.response_not_found() if alarm.status != "A": return self.response_not_found() if alarm.ack_ts: return { "status": False, "message": "Already acknowledged by %s" % alarm.ack_user } alarm.acknowledge(request.user, msg) return {"status": True} @view( url=r"^(?P<id>[a-z0-9]{24})/unacknowledge/", method=["POST"], api=True, access="acknowledge", validate={ "msg": UnicodeParameter(default=""), }, ) def api_unacknowledge(self, request, id, msg=""): alarm = get_alarm(id) if not alarm: return self.response_not_found() if alarm.status != "A": return self.response_not_found() if not alarm.ack_ts: return {"status": False, "message": "Already unacknowledged"} alarm.unacknowledge(request.user, msg=msg) return {"status": True} @view(url=r"^(?P<id>[a-z0-9]{24})/subscribe/", method=["POST"], api=True, access="launch") def api_subscribe(self, request, id): alarm = get_alarm(id) if not alarm: return self.response_not_found() if alarm.status == "A": alarm.subscribe(request.user) return self.get_alarm_subscribers(alarm) else: return [] @view(url=r"^(?P<id>[a-z0-9]{24})/unsubscribe/", method=["POST"], api=True, access="launch") def api_unsubscribe(self, request, id): alarm = get_alarm(id) if not alarm: return self.response_not_found() if alarm.status == "A": alarm.unsubscribe(request.user) return self.get_alarm_subscribers(alarm) else: return [] @view( url=r"^(?P<id>[a-z0-9]{24})/clear/", method=["POST"], api=True, access="launch", validate={ "msg": UnicodeParameter(default=""), }, ) def api_clear(self, request, id, msg=""): alarm = get_alarm(id) if not alarm.alarm_class.user_clearable: return {"status": False, "error": "Deny clear alarm by user"} if alarm.status == "A": alarm.clear_alarm("Cleared by %s: %s" % (request.user, msg), source=request.user.username) return True @view( url=r"^(?P<id>[a-z0-9]{24})/set_root/", method=["POST"], api=True, access="launch", validate={"root": StringParameter()}, ) def api_set_root(self, request, id, root): alarm = get_alarm(id) r = get_alarm(root) if not r: return self.response_not_found() alarm.set_root(r) return True @view(url=r"notification/$", method=["GET"], api=True, access="launch") def api_notification(self, request): delta = request.GET.get("delta") n = 0 sound = None volume = 0 if delta: dt = datetime.timedelta(seconds=int(delta)) t0 = datetime.datetime.now() - dt r = list(ActiveAlarm._get_collection().aggregate([ { "$match": { "timestamp": { "$gt": t0 } } }, { "$group": { "_id": "$item", "severity": { "$max": "$severity" } } }, ])) if r: s = AlarmSeverity.get_severity(r[0]["severity"]) if s and s.sound and s.volume: sound = "/ui/pkg/nocsound/%s.mp3" % s.sound volume = float(s.volume) / 100.0 return {"new_alarms": n, "sound": sound, "volume": volume} @classmethod def f_glyph_summary(cls, s, collapse=False): def be_true(p): return True def be_show(p): return p.show_in_summary def get_summary(d, profile): v = [] if hasattr(profile, "show_in_summary"): show_in_summary = be_show else: show_in_summary = be_true for p, c in d.items(): pv = profile.get_by_id(p) if pv and show_in_summary(pv): if collapse and c < 2: badge = "" else: badge = '<span class="x-display-tag">%s</span>' % c order = getattr(pv, "display_order", 100) v += [( (order, -c), '<i class="%s" title="%s"></i>%s' % (pv.glyph, pv.name, badge), )] return "<span class='x-summary'>%s</span>" % "".join( i[1] for i in sorted(v, key=operator.itemgetter(0))) if not isinstance(s, dict): return "" r = [] if "subscriber" in s: from noc.crm.models.subscriberprofile import SubscriberProfile r += [get_summary(s["subscriber"], SubscriberProfile)] if "service" in s: from noc.sa.models.serviceprofile import ServiceProfile r += [get_summary(s["service"], ServiceProfile)] r = [x for x in r if x] return "".join(r) @view(url=r"^(?P<id>[a-z0-9]{24})/escalate/", method=["GET"], api=True, access="escalate") def api_escalation_alarm(self, request, id): alarm = get_alarm(id) if alarm.status == "A": AlarmEscalation.watch_escalations(alarm) return {"status": True} else: return { "status": False, "error": "The alarm is not active at the moment" } def location(self, id): """ Return geo address for Managed Objects """ def chunkIt(seq, num): avg = len(seq) / float(num) out = [] last = 0.0 while last < len(seq): out.append(seq[int(last):int(last + avg)]) last += avg return out location = [] address = Object.get_by_id(id).get_address_text() if address: for res in address.split(","): adr = normalize_division(smart_text(res).strip().lower()) if None in adr and "" in adr: continue if None in adr: location += [adr[1].title().strip()] else: location += [" ".join(adr).title().strip()] res = chunkIt(location, 2) location_1 = ", ".join(res[0]) location_2 = ", ".join(res[1]) return [location_1, location_2] return ["", ""] @classmethod def f_summary(cls, s): def be_true(p): return True def be_show(p): return p.show_in_summary def get_summary(d, profile): v = [] if hasattr(profile, "show_in_summary"): show_in_summary = be_show else: show_in_summary = be_true for p, c in sorted(d.items(), key=lambda x: -x[1]): pv = profile.get_by_id(p) if pv and show_in_summary(pv): v += [{ "profile": str(pv.id), "glyph": pv.glyph, "display_order": pv.display_order, "profile__label": pv.name, "summary": c, }] return v if not isinstance(s, dict): return "" r = [] if "subscriber" in s: from noc.crm.models.subscriberprofile import SubscriberProfile r += get_summary(s["subscriber"], SubscriberProfile) if "service" in s: from noc.sa.models.serviceprofile import ServiceProfile r += get_summary(s["service"], ServiceProfile) r = [x for x in r if x] return r def bulk_field_isinmaintenance(self, data): if not data: return data if data[0]["status"] == "A": mtc = set(Maintenance.currently_affected()) for x in data: x["isInMaintenance"] = x["managed_object"] in mtc else: mos = set([x["managed_object"] for x in data]) mtc = {} for mo in list(mos): interval = [] for ao in AffectedObjects._get_collection().find( {"affected_objects.object": { "$eq": mo }}, { "_id": 0, "maintenance": 1 }): m = Maintenance.get_by_id(ao["maintenance"]) interval += [(m.start, m.stop)] if interval: mtc[mo] = interval for x in data: if x["managed_object"] in mtc: left, right = list(zip(*mtc[x["managed_object"]])) x["isInMaintenance"] = bisect.bisect( right, dateutil.parser.parse(x["timestamp"]).replace( tzinfo=None)) != bisect.bisect( left, dateutil.parser.parse( x["clear_timestamp"]).replace(tzinfo=None)) else: x["isInMaintenance"] = False return data @view(url=r"profile_lookup/$", access="launch", method=["GET"], api=True) def api_profile_lookup(self, request): r = [] for model, short_type, field_id in ( (ServiceProfile, _("Service"), "total_services"), (SubscriberProfile, _("Subscribers"), "total_subscribers"), ): # "%s|%s" % (field_id, r += [{ "id": str(o.id), "type": short_type, "display_order": o.display_order, "icon": o.glyph, "label": o.name, } for o in model.objects.all() if getattr(o, "show_in_summary", True)] return r
class AlarmApplication(ExtApplication): """ fm.alarm application """ title = "Alarm" menu = "Alarms" glyph = "exclamation-triangle" implied_permissions = { "launch": ["sa:managedobject:alarm"] } model_map = { "A": ActiveAlarm, "C": ArchivedAlarm } clean_fields = { "managed_object": ModelParameter(ManagedObject), "timestamp": DateTimeParameter() } ignored_params = ["status", "_dc"] def __init__(self, *args, **kwargs): ExtApplication.__init__(self, *args, **kwargs) from plugins.base import AlarmPlugin # Load plugins self.plugins = {} for f in os.listdir("fm/apps/alarm/plugins/"): if (not f.endswith(".py") or f == "base.py" or f.startswith("_")): continue mn = "noc.fm.apps.alarm.plugins.%s" % f[:-3] m = __import__(mn, {}, {}, "*") for on in dir(m): o = getattr(m, on) if (inspect.isclass(o) and issubclass(o, AlarmPlugin) and o.__module__.startswith(mn)): assert o.name self.plugins[o.name] = o(self) def cleaned_query(self, q): q = q.copy() for p in self.ignored_params: if p in q: del q[p] for p in ( self.limit_param, self.page_param, self.start_param, self.format_param, self.sort_param, self.query_param, self.only_param): if p in q: del q[p] # Normalize parameters for p in q: qp = p.split("__")[0] if qp in self.clean_fields: q[p] = self.clean_fields[qp].clean(q[p]) if "administrative_domain" in q: a = AdministrativeDomain.objects.get(id = q["administrative_domain"]) q["managed_object__in"] = a.managedobject_set.values_list("id", flat=True) q.pop("administrative_domain") if "managedobjectselector" in q: s = SelectorCache.objects.filter(selector = q["managedobjectselector"]).values_list("object") if "managed_object__in" in q: q["managed_object__in"] = list(set(q["managed_object__in"]).intersection(s)) else: q["managed_object__in"] = s q.pop("managedobjectselector") # if "collapse" in q: c = q["collapse"] del q["collapse"] if c != "0": q["root__exists"] = False return q def instance_to_dict(self, o, fields=None): s = AlarmSeverity.get_severity(o.severity) n_events = (ActiveEvent.objects.filter(alarms=o.id).count() + ArchivedEvent.objects.filter(alarms=o.id).count()) d = { "id": str(o.id), "status": o.status, "managed_object": o.managed_object.id, "managed_object__label": o.managed_object.name, "administrative_domain": o.managed_object.administrative_domain_id, "administrative_domain__label": o.managed_object.administrative_domain.name, "severity": o.severity, "severity__label": s.name, "alarm_class": str(o.alarm_class.id), "alarm_class__label": o.alarm_class.name, "timestamp": self.to_json(o.timestamp), "subject": o.subject, "events": n_events, "duration": o.duration, "row_class": s.style.css_class_name } if fields: d = dict((k, d[k]) for k in fields) return d def queryset(self, request, query=None): """ Filter records for lookup """ status = request.GET.get("status", "A") if status not in self.model_map: raise Exception("Invalid status") model = self.model_map[status] return model.objects.all() @view(url=r"^$", access="launch", method=["GET"], api=True) def api_list(self, request): return self.list_data(request, self.instance_to_dict) @view(url=r"^(?P<id>[a-z0-9]{24})/$", method=["GET"], api=True, access="launch") def api_alarm(self, request, id): alarm = get_alarm(id) if not alarm: self.response_not_found() user = request.user lang = "en" d = self.instance_to_dict(alarm) d["body"] = alarm.body d["symptoms"] = alarm.alarm_class.symptoms d["probable_causes"] = alarm.alarm_class.probable_causes d["recommended_actions"] = alarm.alarm_class.recommended_actions d["vars"] = sorted(alarm.vars.items()) d["status"] = alarm.status d["status__label"] = { "A": "Active", "C": "Cleared" }[alarm.status] # Managed object properties mo = alarm.managed_object d["managed_object_address"] = mo.address d["managed_object_profile"] = mo.profile_name d["managed_object_platform"] = mo.platform d["managed_object_version"] = mo.get_attr("version") # Log if alarm.log: d["log"] = [ { "timestamp": self.to_json(l.timestamp), "from_status": l.from_status, "to_status": l.to_status, "message": l.message } for l in alarm.log ] # Events events = [] for ec in ActiveEvent, ArchivedEvent: for e in ec.objects.filter(alarms=alarm.id): events += [{ "id": str(e.id), "event_class": str(e.event_class.id), "event_class__label": e.event_class.name, "timestamp": self.to_json(e.timestamp), "status": e.status, "managed_object": e.managed_object.id, "managed_object__label": e.managed_object.name, "subject": e.subject }] if events: d["events"] = events # Alarms children = self.get_nested_alarms(alarm) if children: d["alarms"] = { "expanded": True, "children": children } # Subscribers if alarm.status == "A": d["subscribers"] = self.get_alarm_subscribers(alarm) d["is_subscribed"] = user in alarm.subscribers # Apply plugins if alarm.alarm_class.plugins: plugins = [] for p in alarm.alarm_class.plugins: if p.name in self.plugins: plugin = self.plugins[p.name] dd = plugin.get_data(alarm, p.config) if "plugins" in dd: plugins += dd["plugins"] del dd["plugins"] d.update(dd) if plugins: d["plugins"] = plugins return d def get_alarm_subscribers(self, alarm): """ JSON-serializable subscribers :param alarm: :return: """ subscribers = [] for u in alarm.subscribers: try: u = User.objects.get(id=u) subscribers += [{ "id": u.id, "name": " ".join([u.first_name, u.last_name]), "login": u.username }] except User.DoesNotExist: pass return subscribers def get_nested_alarms(self, alarm): """ Return nested alarms as a part of NodeInterface :param alarm: :return: """ children = [] for ac in (ActiveAlarm, ArchivedAlarm): for a in ac.objects.filter(root=alarm.id): s = AlarmSeverity.get_severity(a.severity) c = { "id": str(a.id), "subject": a.subject, "alarm_class": str(a.alarm_class.id), "alarm_class__label": a.alarm_class.name, "managed_object": a.managed_object.id, "managed_object__label": a.managed_object.name, "timestamp": self.to_json(a.timestamp), "iconCls": "icon_error", "row_class": s.style.css_class_name } nc = self.get_nested_alarms(a) if nc: c["children"] = nc c["expanded"] = True else: c["leaf"] = True children += [c] return children @view(url=r"^(?P<id>[a-z0-9]{24})/post/", method=["POST"], api=True, access="launch", validate={"msg": UnicodeParameter()}) def api_post(self, request, id, msg): alarm = get_alarm(id) if not alarm: self.response_not_found() alarm.log_message("%s: %s" % (request.user.username, msg)) return True @view(url=r"^(?P<id>[a-z0-9]{24})/subscribe/", method=["POST"], api=True, access="launch") def api_subscribe(self, request, id): alarm = get_alarm(id) if not alarm: return self.response_not_found() if alarm.status == "A": alarm.subscribe(request.user) return self.get_alarm_subscribers(alarm) else: return [] @view(url=r"^(?P<id>[a-z0-9]{24})/unsubscribe/", method=["POST"], api=True, access="launch") def api_unsubscribe(self, request, id): alarm = get_alarm(id) if not alarm: return self.response_not_found() if alarm.status == "A": alarm.unsubscribe(request.user) return self.get_alarm_subscribers(alarm) else: return [] @view(url=r"^(?P<id>[a-z0-9]{24})/clear/", method=["POST"], api=True, access="launch") def api_clear(self, request, id): alarm = get_alarm(id) if alarm.status == "A": alarm.clear_alarm("Cleared by %s" % request.user) return True @view(url=r"^(?P<id>[a-z0-9]{24})/set_root/", method=["POST"], api=True, access="launch", validate={"root": StringParameter()}) def api_set_root(self, request, id, root): alarm = get_alarm(id) r = get_alarm(root) if not r: return self.response_not_found() alarm.set_root(r) return True
class AlarmApplication(ExtApplication): """ fm.alarm application """ title = _("Alarm") menu = _("Alarms") glyph = "exclamation-triangle" implied_permissions = {"launch": ["sa:managedobject:alarm"]} model_map = {"A": ActiveAlarm, "C": ArchivedAlarm} clean_fields = { "managed_object": ModelParameter(ManagedObject), "timestamp": DateTimeParameter() } ignored_params = ["status", "_dc"] diagnostic_plugin = AlarmPlugin(name="diagnostic", config={}) DEFAULT_ARCH_ALARM = datetime.timedelta( seconds=config.web.api_arch_alarm_limit) def __init__(self, *args, **kwargs): ExtApplication.__init__(self, *args, **kwargs) from .plugins.base import AlarmPlugin # Load plugins self.plugins = {} for f in os.listdir("services/web/apps/fm/alarm/plugins/"): if (not f.endswith(".py") or f == "base.py" or f.startswith("_")): continue mn = "noc.services.web.apps.fm.alarm.plugins.%s" % f[:-3] m = __import__(mn, {}, {}, "*") for on in dir(m): o = getattr(m, on) if (inspect.isclass(o) and issubclass(o, AlarmPlugin) and o.__module__.startswith(mn)): assert o.name self.plugins[o.name] = o(self) def cleaned_query(self, q): q = q.copy() status = q["status"] if "status" in q else "A" for p in self.ignored_params: if p in q: del q[p] for p in (self.limit_param, self.page_param, self.start_param, self.format_param, self.sort_param, self.query_param, self.only_param): if p in q: del q[p] # Normalize parameters for p in q: qp = p.split("__")[0] if qp in self.clean_fields: q[p] = self.clean_fields[qp].clean(q[p]) # Exclude maintenance if "maintenance" not in q: q["maintenance"] = "hide" if q["maintenance"] == "hide": q["managed_object__nin"] = Maintenance.currently_affected() elif q["maintenance"] == "only": q["managed_object__in"] = Maintenance.currently_affected() del q["maintenance"] if "administrative_domain" in q: q["adm_path"] = int(q["administrative_domain"]) q.pop("administrative_domain") if "segment" in q: q["segment_path"] = bson.ObjectId(q["segment"]) q.pop("segment") if "managedobjectselector" in q: s = SelectorCache.objects.filter( selector=q["managedobjectselector"]).values_list("object") if "managed_object__in" in q: q["managed_object__in"] = list( set(q["managed_object__in"]).intersection(s)) else: q["managed_object__in"] = s q.pop("managedobjectselector") if "cleared_after" in q: q["clear_timestamp__gte"] = datetime.datetime.now( ) - datetime.timedelta(seconds=int(q["cleared_after"])) q.pop("cleared_after") # if "wait_tt" in q: q["wait_tt__exists"] = True q["wait_ts__exists"] = False del q["wait_tt"] # if "collapse" in q: c = q["collapse"] del q["collapse"] if c != "0": q["root__exists"] = False if status == "C": if ("timestamp__gte" not in q and "timestamp__lte" not in q and "escalation_tt__contains" not in q and "managed_object" not in q): q["timestamp__gte"] = datetime.datetime.now( ) - self.DEFAULT_ARCH_ALARM return q def instance_to_dict(self, o, fields=None): s = AlarmSeverity.get_severity(o.severity) n_events = (ActiveEvent.objects.filter(alarms=o.id).count() + ArchivedEvent.objects.filter(alarms=o.id).count()) mtc = o.managed_object.id in Maintenance.currently_affected() if o.status == "C": # For archived alarms mtc = Maintenance.objects.filter( start__lte=o.clear_timestamp, stop__lte=o.timestamp, affected_objects__in=[ MaintenanceObject(object=o.managed_object) ]).count() > 0 d = { "id": str(o.id), "status": o.status, "managed_object": o.managed_object.id, "managed_object__label": o.managed_object.name, "administrative_domain": o.managed_object.administrative_domain_id, "administrative_domain__label": o.managed_object.administrative_domain.name, "severity": o.severity, "severity__label": s.name, "alarm_class": str(o.alarm_class.id), "alarm_class__label": o.alarm_class.name, "timestamp": self.to_json(o.timestamp), "subject": o.subject, "events": n_events, "duration": o.duration, "clear_timestamp": self.to_json(o.clear_timestamp) if o.status == "C" else None, "row_class": s.style.css_class_name, "segment__label": o.managed_object.segment.name, "segment": str(o.managed_object.segment.id), "location_1": self.location(o.managed_object.container.id)[0] if o.managed_object.container else "", "location_2": self.location(o.managed_object.container.id)[1] if o.managed_object.container else "", "escalation_tt": o.escalation_tt, "escalation_error": o.escalation_error, "platform": o.managed_object.platform.name if o.managed_object.platform else "", "address": o.managed_object.address, "isInMaintenance": mtc, "summary": self.f_glyph_summary({ "subscriber": SummaryItem.items_to_dict(o.total_subscribers), "service": SummaryItem.items_to_dict(o.total_services) }), "total_objects": sum(x.summary for x in o.total_objects) } if fields: d = dict((k, d[k]) for k in fields) return d def queryset(self, request, query=None): """ Filter records for lookup """ status = request.GET.get("status", "A") if status not in self.model_map: raise Exception("Invalid status") model = self.model_map[status] if request.user.is_superuser: return model.objects.filter( read_preference=ReadPreference.SECONDARY_PREFERRED).all() else: return model.objects.filter( adm_path__in=UserAccess.get_domains(request.user), read_preference=ReadPreference.SECONDARY_PREFERRED) @view(url=r"^$", access="launch", method=["GET"], api=True) def api_list(self, request): return self.list_data(request, self.instance_to_dict) @view(url=r"^(?P<id>[a-z0-9]{24})/$", method=["GET"], api=True, access="launch") def api_alarm(self, request, id): alarm = get_alarm(id) if not alarm: self.response_not_found() user = request.user d = self.instance_to_dict(alarm) d["body"] = alarm.body d["symptoms"] = alarm.alarm_class.symptoms d["probable_causes"] = alarm.alarm_class.probable_causes d["recommended_actions"] = alarm.alarm_class.recommended_actions d["vars"] = sorted(alarm.vars.items()) d["status"] = alarm.status d["status__label"] = {"A": "Active", "C": "Cleared"}[alarm.status] # Managed object properties mo = alarm.managed_object d["managed_object_address"] = mo.address d["managed_object_profile"] = mo.profile.name d["managed_object_platform"] = mo.platform.name if mo.platform else "" d["managed_object_version"] = mo.version.version if mo.version else "" d["segment"] = mo.segment.name d["segment_id"] = str(mo.segment.id) d["segment_path"] = " | ".join( NetworkSegment.get_by_id(p).name for p in NetworkSegment.get_path(mo.segment)) if mo.container: cp = [] c = mo.container.id while c: try: o = Object.objects.get(id=c) if o.container: cp.insert(0, o.name) c = o.container.id if o.container else None except DoesNotExist: break d["container_path"] = " | ".join(cp) if not self.location(mo.container.id)[0]: d["address_path"] = None else: d["address_path"] = ", ".join(self.location(mo.container.id)) d["tags"] = mo.tags # Log if alarm.log: d["log"] = [{ "timestamp": self.to_json(l.timestamp), "from_status": l.from_status, "to_status": l.to_status, "message": l.message } for l in alarm.log] # Events events = [] for ec in ActiveEvent, ArchivedEvent: for e in ec.objects.filter(alarms=alarm.id): events += [{ "id": str(e.id), "event_class": str(e.event_class.id), "event_class__label": e.event_class.name, "timestamp": self.to_json(e.timestamp), "status": e.status, "managed_object": e.managed_object.id, "managed_object__label": e.managed_object.name, "subject": e.subject }] if events: d["events"] = events # Alarms children = self.get_nested_alarms(alarm) if children: d["alarms"] = {"expanded": True, "children": children} # Subscribers if alarm.status == "A": d["subscribers"] = self.get_alarm_subscribers(alarm) d["is_subscribed"] = user in alarm.subscribers # Apply plugins plugins = [] acp = alarm.alarm_class.plugins or [] acp += [self.diagnostic_plugin] for p in acp: if p.name in self.plugins: plugin = self.plugins[p.name] dd = plugin.get_data(alarm, p.config) if "plugins" in dd: plugins += dd["plugins"] del dd["plugins"] d.update(dd) if plugins: d["plugins"] = plugins return d def get_alarm_subscribers(self, alarm): """ JSON-serializable subscribers :param alarm: :return: """ subscribers = [] for u in alarm.subscribers: try: u = User.objects.get(id=u) subscribers += [{ "id": u.id, "name": " ".join([u.first_name, u.last_name]), "login": u.username }] except User.DoesNotExist: pass return subscribers def get_nested_alarms(self, alarm): """ Return nested alarms as a part of NodeInterface :param alarm: :return: """ children = [] for ac in (ActiveAlarm, ArchivedAlarm): for a in ac.objects.filter(root=alarm.id): s = AlarmSeverity.get_severity(a.severity) c = { "id": str(a.id), "subject": a.subject, "alarm_class": str(a.alarm_class.id), "alarm_class__label": a.alarm_class.name, "managed_object": a.managed_object.id, "managed_object__label": a.managed_object.name, "timestamp": self.to_json(a.timestamp), "iconCls": "icon_error", "row_class": s.style.css_class_name } nc = self.get_nested_alarms(a) if nc: c["children"] = nc c["expanded"] = True else: c["leaf"] = True children += [c] return children @view(url=r"^(?P<id>[a-z0-9]{24})/post/", method=["POST"], api=True, access="launch", validate={"msg": UnicodeParameter()}) def api_post(self, request, id, msg): alarm = get_alarm(id) if not alarm: self.response_not_found() alarm.log_message("%s: %s" % (request.user.username, msg)) return True @view(url=r"^(?P<id>[a-z0-9]{24})/subscribe/", method=["POST"], api=True, access="launch") def api_subscribe(self, request, id): alarm = get_alarm(id) if not alarm: return self.response_not_found() if alarm.status == "A": alarm.subscribe(request.user) return self.get_alarm_subscribers(alarm) else: return [] @view(url=r"^(?P<id>[a-z0-9]{24})/unsubscribe/", method=["POST"], api=True, access="launch") def api_unsubscribe(self, request, id): alarm = get_alarm(id) if not alarm: return self.response_not_found() if alarm.status == "A": alarm.unsubscribe(request.user) return self.get_alarm_subscribers(alarm) else: return [] @view(url=r"^(?P<id>[a-z0-9]{24})/clear/", method=["POST"], api=True, access="launch") def api_clear(self, request, id): alarm = get_alarm(id) if alarm.status == "A": alarm.clear_alarm("Cleared by %s" % request.user) return True @view(url=r"^(?P<id>[a-z0-9]{24})/set_root/", method=["POST"], api=True, access="launch", validate={"root": StringParameter()}) def api_set_root(self, request, id, root): alarm = get_alarm(id) r = get_alarm(root) if not r: return self.response_not_found() alarm.set_root(r) return True @view(url="notification/$", method=["GET"], api=True, access="launch") def api_notification(self, request): delta = request.GET.get("delta") n = 0 sound = None volume = 0 if delta: dt = datetime.timedelta(seconds=int(delta)) t0 = datetime.datetime.now() - dt r = list(ActiveAlarm._get_collection().aggregate([{ "$match": { "timestamp": { "$gt": t0 } } }, { "$group": { "_id": "$item", "severity": { "$max": "$severity" } } }])) if r: s = AlarmSeverity.get_severity(r[0]["severity"]) if s and s.sound and s.volume: sound = "/ui/pkg/nocsound/%s.mp3" % s.sound volume = float(s.volume) / 100.0 return {"new_alarms": n, "sound": sound, "volume": volume} @classmethod def f_glyph_summary(cls, s, collapse=False): def be_true(p): return True def be_show(p): return p.show_in_summary def get_summary(d, profile): v = [] if hasattr(profile, "show_in_summary"): show_in_summary = be_show else: show_in_summary = be_true for p, c in sorted(d.items(), key=lambda x: -x[1]): pv = profile.get_by_id(p) if pv and show_in_summary(pv): if collapse and c < 2: badge = "" else: badge = " <span class=\"x-display-tag\">%s</span>" % c v += [ "<i class=\"%s\" title=\"%s\"></i>%s" % (pv.glyph, pv.name, badge) ] return "<span class='x-summary'>%s</span>" % " ".join(v) if not isinstance(s, dict): return "" r = [] if "subscriber" in s: from noc.crm.models.subscriberprofile import SubscriberProfile r += [get_summary(s["subscriber"], SubscriberProfile)] if "service" in s: from noc.sa.models.serviceprofile import ServiceProfile r += [get_summary(s["service"], ServiceProfile)] r = [x for x in r if x] return " ".join(r) @view(url=r"^(?P<id>[a-z0-9]{24})/escalate/", method=["GET"], api=True, access="escalate") def api_escalation_alarm(self, request, id): alarm = get_alarm(id) if alarm.status == "A": AlarmEscalation.watch_escalations(alarm) return {'status': True} else: return { 'status': False, 'error': 'The alarm is not active at the moment' } def location(self, id): """ Return geo address for Managed Objects """ def chunkIt(seq, num): avg = len(seq) / float(num) out = [] last = 0.0 while last < len(seq): out.append(seq[int(last):int(last + avg)]) last += avg return out location = [] address = Object.get_by_id(id).get_address_text() if address: for res in address.split(","): adr = normalize_division(res.strip().decode("utf-8").lower()) if None in adr and "" in adr: continue if None in adr: location += [adr[1].title().strip()] else: location += [' '.join(adr).title().strip()] res = chunkIt(location, 2) location_1 = ", ".join(res[0]) location_2 = ", ".join(res[1]) return [location_1, location_2] else: return ["", ""]
class RunCommandsApplication(ExtApplication): title = _("Run Commands") menu = [_("Run Commands")] implied_permissions = {"launch": ["sa:objectlist:read"]} @view(url=r"^form/snippet/(?P<snippet_id>\d+)/$", method=["GET"], access="launch", api=True) def api_form_snippet(self, request, snippet_id): snippet = self.get_object_or_404(CommandSnippet, id=int(snippet_id)) r = [] vars = snippet.vars for k in vars: cfg = { "name": k, "fieldLabel": k, "allowBlank": not vars[k].get("required", False) } t = vars[k].get("type") if t == "int": cfg["xtype"] = "numberfield" else: cfg["xtype"] = "textfield" r += [cfg] return r @view(url=r"^form/action/(?P<action_id>[0-9a-f]{24})/$", method=["GET"], access="launch", api=True) def api_form_action(self, request, action_id): action = self.get_object_or_404(Action, id=action_id) r = [] for p in action.params: cfg = { "name": p.name, "fieldLabel": p.description or p.name, "allowBlank": not p.is_required, } if p.type == "int": cfg["xtype"] = "numberfield" else: cfg["xtype"] = "textfield" r += [cfg] return r @view( url=r"^render/snippet/(?P<snippet_id>\d+)/$", method=["POST"], validate={ "objects": ListOfParameter(element=ModelParameter(ManagedObject)), "config": DictParameter(), }, access="launch", api=True, ) def api_render_snippet(self, request, snippet_id, objects, config): snippet = self.get_object_or_404(CommandSnippet, id=int(snippet_id)) r = {} for mo in objects: config["object"] = mo r[mo.id] = snippet.expand(config) return r @view( url=r"^render/action/(?P<action_id>[0-9a-f]{24})/$", method=["POST"], validate={ "objects": ListOfParameter(element=ModelParameter(ManagedObject)), "config": DictParameter(), }, access="launch", api=True, ) def api_render_action(self, request, action_id, objects, config): action = self.get_object_or_404(Action, id=action_id) r = {} for mo in objects: r[mo.id] = action.expand(mo, **config) return r
class InterfaceAppplication(ExtApplication): """ inv.interface application """ title = _("Interfaces") menu = _("Interfaces") mrt_config = { "get_mac": { "map_script": "get_mac_address_table", "timeout": config.script.timeout, "access": "get_mac", } } implied_permissions = { "get_mac": [ "inv:inv:read", "inv:interface:view", "sa:managedobject:lookup", "sa:managedobject:read", ] } @view(url=r"^(?P<managed_object>\d+)/$", method=["GET"], access="view", api=True) def api_get_interfaces(self, request, managed_object): """ GET interfaces :param managed_object: :return: """ def sorted_iname(s): return list(sorted(s, key=lambda x: alnum_key(x["name"]))) def get_style(i): profile = i.profile if profile: try: return style_cache[profile.id] except KeyError: pass if profile.style: s = profile.style.css_class_name else: s = "" style_cache[profile.id] = s return s else: return "" def get_link(i): link = i.link if not link: return None if link.is_ptp: # ptp o = link.other_ptp(i) label = "%s:%s" % (o.managed_object.name, o.name) elif link.is_lag: # unresolved LAG o = [ ii for ii in link.other(i) if ii.managed_object.id != i.managed_object.id ] label = "LAG %s: %s" % (o[0].managed_object.name, ", ".join( ii.name for ii in o)) else: # Broadcast label = ", ".join("%s:%s" % (ii.managed_object.name, ii.name) for ii in link.other(i)) return {"id": str(link.id), "label": label} # Get object o = self.get_object_or_404(ManagedObject, id=int(managed_object)) if not o.has_access(request.user): return self.response_forbidden("Permission denied") # Physical interfaces # @todo: proper ordering default_state = ResourceState.get_default() style_cache = {} # profile_id -> css_style l1 = [{ "id": str(i.id), "name": i.name, "description": i.description, "mac": i.mac, "ifindex": i.ifindex, "lag": (i.aggregated_interface.name if i.aggregated_interface else ""), "link": get_link(i), "profile": str(i.profile.id) if i.profile else None, "profile__label": smart_text(i.profile) if i.profile else None, "enabled_protocols": i.enabled_protocols, "project": i.project.id if i.project else None, "project__label": smart_text(i.project) if i.project else None, "state": i.state.id if i.state else default_state.id, "state__label": smart_text(i.state if i.state else default_state), "vc_domain": i.vc_domain.id if i.vc_domain else None, "vc_domain__label": smart_text(i.vc_domain) if i.vc_domain else None, "row_class": get_style(i), } for i in Interface.objects.filter(managed_object=o.id, type="physical")] # LAG lag = [{ "id": str(i.id), "name": i.name, "description": i.description, "members": [ j.name for j in Interface.objects.filter(managed_object=o.id, aggregated_interface=i.id) ], "profile": str(i.profile.id) if i.profile else None, "profile__label": smart_text(i.profile) if i.profile else None, "enabled_protocols": i.enabled_protocols, "project": i.project.id if i.project else None, "project__label": smart_text(i.project) if i.project else None, "state": i.state.id if i.state else default_state.id, "state__label": smart_text(i.state if i.state else default_state), "vc_domain": i.vc_domain.id if i.vc_domain else None, "vc_domain__label": smart_text(i.vc_domain) if i.vc_domain else None, "row_class": get_style(i), } for i in Interface.objects.filter(managed_object=o.id, type="aggregated")] # L2 interfaces l2 = [{ "name": i.name, "description": i.description, "untagged_vlan": i.untagged_vlan, "tagged_vlans": i.tagged_vlans, } for i in SubInterface.objects.filter(managed_object=o.id, enabled_afi="BRIDGE")] # L3 interfaces q = Q(enabled_afi="IPv4") | Q(enabled_afi="IPv6") l3 = [{ "name": i.name, "description": i.description, "ipv4_addresses": i.ipv4_addresses, "ipv6_addresses": i.ipv6_addresses, "enabled_protocols": i.enabled_protocols, "vlan": i.vlan_ids, "vrf": i.forwarding_instance.name if i.forwarding_instance else "", } for i in SubInterface.objects.filter(managed_object=o.id).filter(q)] return { "l1": sorted_iname(l1), "lag": sorted_iname(lag), "l2": sorted_iname(l2), "l3": sorted_iname(l3), } @view( url=r"^link/$", method=["POST"], validate={ "type": StringParameter(choices=["ptp"]), "interfaces": ListOfParameter(element=DocumentParameter(Interface)), }, access="link", api=True, ) def api_link(self, request, type, interfaces): if type == "ptp": if len(interfaces) == 2: interfaces[0].link_ptp(interfaces[1]) return {"status": True} else: raise ValueError("Invalid interfaces length") return {"status": False} @view(url=r"^unlink/(?P<iface_id>[0-9a-f]{24})/$", method=["POST"], access="link", api=True) def api_unlink(self, request, iface_id): i = Interface.objects.filter(id=iface_id).first() if not i: return self.response_not_found() try: i.unlink() return {"status": True, "msg": "Unlinked"} except ValueError as why: return {"status": False, "msg": str(why)} @view(url=r"^unlinked/(?P<object_id>\d+)/$", method=["GET"], access="link", api=True) def api_unlinked(self, request, object_id): def get_label(i): if i.description: return "%s (%s)" % (i.name, i.description) else: return i.name o = self.get_object_or_404(ManagedObject, id=int(object_id)) r = [{ "id": str(i.id), "label": get_label(i) } for i in Interface.objects.filter(managed_object=o.id, type="physical").order_by("name") if not i.link] return list(sorted(r, key=lambda x: alnum_key(x["label"]))) @view( url=r"^l1/(?P<iface_id>[0-9a-f]{24})/change_profile/$", validate={"profile": DocumentParameter(InterfaceProfile)}, method=["POST"], access="profile", api=True, ) def api_change_profile(self, request, iface_id, profile): i = Interface.objects.filter(id=iface_id).first() if not i: return self.response_not_found() if i.profile != profile: i.profile = profile i.profile_locked = True i.save() return True @view( url=r"^l1/(?P<iface_id>[0-9a-f]{24})/change_state/$", validate={"state": ModelParameter(ResourceState)}, method=["POST"], access="profile", api=True, ) def api_change_state(self, request, iface_id, state): i = Interface.objects.filter(id=iface_id).first() if not i: return self.response_not_found() if i.state != state: i.state = state i.save() return True @view( url=r"^l1/(?P<iface_id>[0-9a-f]{24})/change_project/$", validate={"project": ModelParameter(Project, required=False)}, method=["POST"], access="profile", api=True, ) def api_change_project(self, request, iface_id, project): i = Interface.objects.filter(id=iface_id).first() if not i: return self.response_not_found() if i.project != project: i.project = project i.save() return True @view( url=r"^l1/(?P<iface_id>[0-9a-f]{24})/change_vc_domain/$", validate={"vc_domain": ModelParameter(VCDomain, required=False)}, method=["POST"], access="profile", api=True, ) def api_change_vc_domain(self, request, iface_id, vc_domain): i = Interface.objects.filter(id=iface_id).first() if not i: return self.response_not_found() if i.vc_domain != vc_domain: i.vc_domain = vc_domain i.save() return True
class UserProfileApplication(ExtApplication): """ main.userprofile application """ title = "User Profile" implied_permissions = {"launch": ["main:timepattern:lookup"]} @view(url="^$", method=["GET"], access=PermitLogged(), api=True) def api_get(self, request): user = request.user try: profile = user.get_profile() language = profile.preferred_language theme = profile.theme preview_theme = profile.preview_theme contacts = [{ "time_pattern": c.time_pattern.id, "time_pattern__label": c.time_pattern.name, "notification_method": c.notification_method, "params": c.params } for c in profile.userprofilecontact_set.all()] except UserProfile.DoesNotExist: language = None theme = None preview_theme = None contacts = [] return { "username": user.username, "name": (" ".join([x for x in (user.first_name, user.last_name) if x])).strip(), "email": user.email, "preferred_language": language or "en", "theme": theme or "gray", "preview_theme": preview_theme or "midnight", "contacts": contacts } @view(url="^$", method=["POST"], access=PermitLogged(), api=True, validate={ "preferred_language": StringParameter(choices=[x[0] for x in LANGUAGES]), "theme": StringParameter(), "preview_theme": StringParameter(), "contacts": ListOfParameter(element=DictParameter( attrs={ "time_pattern": ModelParameter(TimePattern), "notification_method": StringParameter(choices=[ x[0] for x in USER_NOTIFICATION_METHOD_CHOICES ]), "params": StringParameter() })) }) def api_save(self, request, preferred_language, theme, preview_theme, contacts): user = request.user try: profile = user.get_profile() except UserProfile.DoesNotExist: profile = UserProfile(user=user) profile.preferred_language = preferred_language profile.theme = theme profile.preview_theme = preview_theme profile.save() # Setup contacts for c in profile.userprofilecontact_set.all(): c.delete() for c in contacts: UserProfileContact(user_profile=profile, time_pattern=c["time_pattern"], notification_method=c["notification_method"], params=c["params"]).save() # Setup language request.session["django_lang"] = preferred_language return True
"profile": DocumentParameter(InterfaceProfile) }, method=["POST"], access="profile", api=True) def api_change_profile(self, request, iface_id, profile): i = Interface.objects.filter(id=iface_id).first() if not i: return self.response_not_found() if i.profile != profile: i.profile = profile i.profile_locked = True i.save() return True @view(url="^l1/(?P<iface_id>[0-9a-f]{24})/change_state/$", validate={ "state": ModelParameter(ResourceState) }, method=["POST"], access="profile", api=True) def api_change_state(self, request, iface_id, state): i = Interface.objects.filter(id=iface_id).first() if not i: return self.response_not_found() if i.state != state: i.state = state i.save() return True @view(url="^l1/(?P<iface_id>[0-9a-f]{24})/change_project/$", validate={ "project": ModelParameter(Project, required=False) },
class ManagedObjectApplication(ExtModelApplication): """ ManagedObject application """ title = "Managed Objects" menu = "Managed Objects" model = ManagedObject query_condition = "icontains" query_fields = ["name", "description", "address"] # Inlines attrs = ModelInline(ManagedObjectAttribute) cfg = RepoInline("config") extra_permissions = ["alarm", "change_interface"] mrt_config = { "console": { "access": "console", "map_script": "commands", "timeout": 60 } } def field_platform(self, o): return o.platform def field_row_class(self, o): return o.object_profile.style.css_class_name if o.object_profile.style else "" def field_interface_count(self, o): return Interface.objects.filter(managed_object=o.id, type="physical").count() def field_link_count(self, o): return Link.object_links_count(o) def queryset(self, request, query=None): qs = super(ManagedObjectApplication, self).queryset(request, query) if not request.user.is_superuser: qs = qs.filter(UserAccess.Q(request.user)) qs = qs.exclude(name__startswith="wiping-") return qs @view(url="^(?P<id>\d+)/links/$", method=["GET"], access="read", api=True) def api_links(self, request, id): o = self.get_object_or_404(ManagedObject, id=id) if not o.has_access(request.user): return self.response_forbidden("Access denied") # Get links result = [] for link in Link.object_links(o): l = [] r = [] for i in link.interfaces: if i.managed_object.id == o.id: l += [i] else: r += [i] for li, ri in zip(l, r): result += [{ "id": str(link.id), "local_interface": str(li.id), "local_interface__label": li.name, "remote_object": ri.managed_object.id, "remote_object__label": ri.managed_object.name, "remote_interface": str(ri.id), "remote_interface__label": ri.name, "discovery_method": link.discovery_method, "commited": True, "local_description": li.description, "remote_description": ri.description }] # Get pending links q = MQ(local_object=o.id) | MQ(remote_object=o.id) for link in PendingLinkCheck.objects.filter(q): if link.local_object.id == o.id: ro = link.remote_object lin = link.local_interface rin = link.remote_interface else: ro = link.local_object lin = link.remote_interface rin = link.local_interface li = Interface.objects.filter(managed_object=o.id, name=lin).first() if not li: continue ri = Interface.objects.filter(managed_object=ro.id, name=rin).first() if not ri: continue result += [{ "id": str(link.id), "local_interface": str(li.id), "local_interface__label": li.name, "remote_object": ro.id, "remote_object__label": ro.name, "remote_interface": str(ri.id), "remote_interface__label": ri.name, "discovery_method": link.method, "commited": False, "local_description": li.description, "remote_description": ri.description }] return result @view(url="^link/approve/$", method=["POST"], access="change_link", api=True) def api_link_approve(self, request): d = json_decode(request.raw_post_data) plc = self.get_object_or_404(PendingLinkCheck, id=d.get("link")) li = Interface.objects.filter( managed_object=plc.local_object.id, name=plc.local_interface ).first() if not li: return { "success": False, "error": "Interface not found: %s:%s" % ( plc.local_object.name, plc.local_interface) } ri = Interface.objects.filter( managed_object=plc.remote_object.id, name=plc.remote_interface ).first() if not ri: return { "success": False, "error": "Interface not found: %s:%s" % ( plc.remote_object.name, plc.remote_interface) } li.link_ptp(ri, method=plc.method + "+manual") plc.delete() return { "success": True } @view(url="^link/reject/$", method=["POST"], access="change_link", api=True) def api_link_reject(self, request): d = json_decode(request.raw_post_data) plc = self.get_object_or_404(PendingLinkCheck, id=d.get("link")) plc.delete() return { "success": True } def check_mrt_access(self, request, name): # @todo: Check object's access return super(ManagedObjectApplication, self).check_mrt_access(request, name) @view(url="^(?P<id>\d+)/discovery/$", method=["GET"], access="read", api=True) def api_discovery(self, request, id): o = self.get_object_or_404(ManagedObject, id=id) if not o.has_access(request.user): return self.response_forbidden("Access denied") link_count = defaultdict(int) for link in Link.object_links(o): m = link.discovery_method or "" if "+" in m: m = m.split("+")[0] link_count[m] += 1 r = [{ "name": "ping", "enable_profile": o.object_profile.enable_ping, "status": o.get_status(), "last_run": None, "last_status": None, "next_run": None, "link_count": None }] for name in get_active_discovery_methods(): job = get_job("inv.discovery", name, o.id) or {} if name.endswith("_discovery"): lcmethod = name[:-10] else: lcmethod = None d = { "name": name, "enable_profile": getattr(o.object_profile, "enable_%s" % name), "status": job.get("s"), "last_run": self.to_json(job.get("last")), "last_status": job.get("ls"), "next_run": self.to_json(job.get("ts")), "link_count": link_count.get(lcmethod, "") } r += [d] return r @view(url="^actions/set_managed/$", method=["POST"], access="create", api=True, validate={ "ids": ListOfParameter(element=ModelParameter(ManagedObject), convert=True) }) def api_action_set_managed(self, request, ids): for o in ids: if not o.has_access(request.user): continue o.is_managed = True o.save() return "Selected objects set to managed state" @view(url="^actions/set_unmanaged/$", method=["POST"], access="create", api=True, validate={ "ids": ListOfParameter(element=ModelParameter(ManagedObject), convert=True) }) def api_action_set_unmanaged(self, request, ids): for o in ids: if not o.has_access(request.user): continue o.is_managed = False o.save() return "Selected objects set to unmanaged state" @view(url="^(?P<id>\d+)/discovery/run/$", method=["POST"], access="change_discovery", api=True) def api_run_discovery(self, request, id): o = self.get_object_or_404(ManagedObject, id=id) if not o.has_access(request.user): return self.response_forbidden("Access denied") r = json_decode(request.raw_post_data).get("names", []) d = 0 for name in get_active_discovery_methods(): cfg = "enable_%s" % name if getattr(o.object_profile, cfg) and name in r: start_schedule("inv.discovery", name, o.id) refresh_schedule("inv.discovery", name, o.id, delta=d) d += 1 return { "success": True } @view(url="^(?P<id>\d+)/discovery/stop/$", method=["POST"], access="change_discovery", api=True) def api_stop_discovery(self, request, id): o = self.get_object_or_404(ManagedObject, id=id) if not o.has_access(request.user): return self.response_forbidden("Access denied") r = json_decode(request.raw_post_data).get("names", []) d = 0 for name in get_active_discovery_methods(): cfg = "enable_%s" % name if getattr(o.object_profile, cfg) and name in r: stop_schedule("inv.discovery", name, o.id) d += 1 return { "success": True } @view(url="^(?P<id>\d+)/interface/$", method=["GET"], access="read", api=True) def api_interface(self, request, id): """ GET interfaces :param managed_object: :return: """ def sorted_iname(s): return sorted(s, key=lambda x: split_alnum(x["name"])) def get_style(i): profile = i.profile if profile: try: return style_cache[profile.id] except KeyError: pass if profile.style: s = profile.style.css_class_name else: s = "" style_cache[profile.id] = s return s else: return "" def get_link(i): link = i.link if not link: return None if link.is_ptp: # ptp o = link.other_ptp(i) label = "%s:%s" % (o.managed_object.name, o.name) elif link.is_lag: # unresolved LAG o = [ii for ii in link.other(i) if ii.managed_object.id != i.managed_object.id] label = "LAG %s: %s" % (o[0].managed_object.name, ", ".join(ii.name for ii in o)) else: # Broadcast label = ", ".join( "%s:%s" % (ii.managed_object.name, ii.name) for ii in link.other(i)) return { "id": str(link.id), "label": label } # Get object o = self.get_object_or_404(ManagedObject, id=int(id)) if not o.has_access(request.user): return self.response_forbidden("Permission denied") # Physical interfaces # @todo: proper ordering default_state = ResourceState.get_default() style_cache = {} ## profile_id -> css_style l1 = [ { "id": str(i.id), "name": i.name, "description": i.description, "mac": i.mac, "ifindex": i.ifindex, "lag": (i.aggregated_interface.name if i.aggregated_interface else ""), "link": get_link(i), "profile": str(i.profile.id) if i.profile else None, "profile__label": unicode(i.profile) if i.profile else None, "enabled_protocols": i.enabled_protocols, "project": i.project.id if i.project else None, "project__label": unicode(i.project) if i.project else None, "state": i.state.id if i.state else default_state.id, "state__label": unicode(i.state if i.state else default_state), "vc_domain": i.vc_domain.id if i.vc_domain else None, "vc_domain__label": unicode(i.vc_domain) if i.vc_domain else None, "row_class": get_style(i) } for i in Interface.objects.filter( managed_object=o.id, type="physical") ] # LAG lag = [ { "id": str(i.id), "name": i.name, "description": i.description, "profile": str(i.profile.id) if i.profile else None, "profile__label": unicode(i.profile) if i.profile else None, "members": [j.name for j in Interface.objects.filter( managed_object=o.id, aggregated_interface=i.id)], "row_class": get_style(i) } for i in Interface.objects.filter(managed_object=o.id, type="aggregated") ] # L2 interfaces l2 = [ { "name": i.name, "description": i.description, "untagged_vlan": i.untagged_vlan, "tagged_vlans": i.tagged_vlans } for i in SubInterface.objects.filter(managed_object=o.id, enabled_afi="BRIDGE") ] # L3 interfaces q = MQ(enabled_afi="IPv4") | MQ(enabled_afi="IPv6") l3 = [ { "name": i.name, "description": i.description, "ipv4_addresses": i.ipv4_addresses, "ipv6_addresses": i.ipv6_addresses, "enabled_protocols": i.enabled_protocols, "vlan": i.vlan_ids, "vrf": i.forwarding_instance.name if i.forwarding_instance else "", "mac": i.mac } for i in SubInterface.objects.filter(managed_object=o.id).filter(q) ] return { "l1": sorted_iname(l1), "lag": sorted_iname(lag), "l2": sorted_iname(l2), "l3": sorted_iname(l3) } @view(url="^(?P<id>\d+)/interface/$", method=["POST"], access="change_interface", api=True) def api_set_interface(self, request, id): def get_or_none(c, v): if not v: return None return c.objects.get(id=v) o = self.get_object_or_404(ManagedObject, id=int(id)) if not o.has_access(request.user): return self.response_forbidden("Access denied") d = json_decode(request.raw_post_data) if "id" in d: i = self.get_object_or_404(Interface, id=d["id"]) if i.managed_object.id != o.id: return self.response_not_found() # Set profile if "profile" in d: p = get_or_none(InterfaceProfile, d["profile"]) i.profile = p if p: i.profile_locked = True # Project if "project" in d: i.project = get_or_none(Project, d["project"]) # State if "state" in d: i.state = get_or_none(ResourceState, d["state"]) # VC Domain if "vc_domain" in d: i.vc_domain = get_or_none(VCDomain, d["vc_domain"]) # i.save() return { "success": True } @view(method=["DELETE"], url="^(?P<id>\d+)/?$", access="delete", api=True) def api_delete(self, request, id): """ Override default method :param request: :param id: :return: """ try: o = self.queryset(request).get(id=int(id)) except self.model.DoesNotExist: return self.render_json({ "status": False, "message": "Not found" }, status=self.NOT_FOUND) if not o.has_access(request.user): return self.response_forbidden("Access denied") # Run sa.wipe_managed_object job instead o.name = "wiping-%d" % o.id o.is_managed = False o.description = "Wiping! Do not touch!" o.save() submit_job("main.jobs", "sa.wipe_managedobject", key=o.id) return HttpResponse(status=self.DELETED) @view(url="^actions/run_discovery/$", method=["POST"], access="launch", api=True, validate={ "ids": ListOfParameter(element=ModelParameter(ManagedObject), convert=True) }) def api_action_run_discovery(self, request, ids): for o in ids: if not o.has_access(request.user): continue d = 0 for name in get_active_discovery_methods(): cfg = "enable_%s" % name if getattr(o.object_profile, cfg): refresh_schedule( "inv.discovery", name, o.id, delta=d) d += 1 return "Discovery processes has been scheduled" def get_nested_inventory(self, o): rev = o.get_data("asset", "revision") if rev == "None": rev = "" r = { "id": str(o.id), "serial": o.get_data("asset", "serial"), "revision": rev or "", "description": o.model.description, "model": o.model.name } children = [] for n in o.model.connections: if n.direction == "i": c, r_object, _ = o.get_p2p_connection(n.name) if c is None: children += [{ "id": None, "name": n.name, "leaf": True, "serial": None, "description": "--- EMPTY ---", "model": None }] else: cc = self.get_nested_inventory(r_object) cc["name"] = n.name children += [cc] elif n.direction == "s": children += [{ "id": None, "name": n.name, "leaf": True, "serial": None, "description": n.description, "model": ", ".join(n.protocols) }] if children: to_expand = "Transceiver" not in o.model.name r["children"] = children r["expanded"] = to_expand else: r["leaf"] = True return r @view(url="^(?P<id>\d+)/inventory/$", method=["GET"], access="read", api=True) def api_inventory(self, request, id): o = self.get_object_or_404(ManagedObject, id=id) if not o.has_access(request.user): return self.response_forbidden("Access denied") r = [] for p in o.get_inventory(): c = self.get_nested_inventory(p) c["name"] = p.name or o.name r += [c] return { "expanded": True, "children": r } @view(url="^(?P<id>\d+)/job_log/(?P<job>[a-zA-Z0-9_]+)/$", method=["GET"], access="read", api=True) def api_job_log(self, request, id, job): o = self.get_object_or_404(ManagedObject, id=id) if not o.has_access(request.user): return self.response_forbidden("Access denied") if not hasattr(self, "discovery_log_jobs"): # Read config self.discovery_log_jobs = None config = SafeConfigParser() config.read("etc/noc-discovery.conf") if config.has_section("main") and config.has_option("main", "log_jobs"): p = config.get("main", "log_jobs") if os.path.isdir(p): self.discovery_log_jobs = p if self.discovery_log_jobs: p = os.path.join(self.discovery_log_jobs, job, id) if os.path.exists(p): with open(p) as f: return self.render_plain_text(f.read()) return self.render_plain_text("No data!") @view(url="^(?P<id>\d+)/interactions/$", method=["GET"], access="interactions", api=True) def api_interactions(self, request, id): o = self.get_object_or_404(ManagedObject, id=id) if not o.has_access(request.user): return self.response_forbidden("Access denied") return [{ "ts": self.to_json(i.timestamp), "op": i.op, "user": i.user, "text": i.text } for i in InteractionLog.objects.filter(object=o.id).order_by("-timestamp")] @view(url="^(?P<id>\d+)/scripts/$", method=["GET"], access="script", api=True) def api_scripts(self, request, id): o = self.get_object_or_404(ManagedObject, id=id) if not o.has_access(request.user): return self.response_forbidden("Access denied") r = [] for s in sorted(o.profile.scripts): script = o.profile.scripts[s] interface = script.implements[0] ss = { "name": s, "has_input": any(interface.gen_parameters()), "require_input": interface.has_required_params, "form": interface.get_form(), "preview": interface.preview or "NOC.sa.managedobject.scripts.JSONPreview" } r += [ss] return r @view(url="^(?P<id>\d+)/scripts/(?P<name>[^/]+)/$", method=["POST"], access="script", api=True) def api_run_script(self, request, id, name): o = self.get_object_or_404(ManagedObject, id=id) if not o.has_access(request.user): return self.response_forbidden("Access denied") if name not in o.profile.scripts: return self.response_not_found("Script not found: %s" % name) task = ReduceTask.create_task( o, "pyrule:mrt_result", {}, name, {}, None) return task.id @view(url="^(?P<id>\d+)/scripts/(?P<name>[^/]+)/(?P<task>\d+)/$", method=["GET"], access="script", api=True) def api_get_script_result(self, request, id, name, task): o = self.get_object_or_404(ManagedObject, id=id) if not o.has_access(request.user): return self.response_forbidden("Access denied") if name not in o.profile.scripts: return self.response_not_found("Script not found: %s" % name) t = self.get_object_or_404(ReduceTask, id=int(task)) try: r = t.get_result(block=False) except ReduceTask.NotReady: # Not ready return { "ready": False, "max_timeout": (t.stop_time - datetime.datetime.now()).seconds, "result": None } # Return result return { "ready": True, "max_timeout": 0, "result": r[0] } @view(url="(?P<id>\d+)/caps/$", method=["GET"], access="read", api=True) def api_get_caps(self, request, id): o = self.get_object_or_404(ManagedObject, id=id) if not o.has_access(request.user): return self.response_forbidden("Access denied") r = [] oc = ObjectCapabilities.objects.filter(object=o).first() if oc: for c in oc.caps: r += [{ "capability": c.capability.name, "description": c.capability.description, "type": c.capability.type, "discovered_value": c.discovered_value, "local_value": c.local_value, "value": c.local_value if c.local_value is not None else c.discovered_value }] return sorted(r, key=lambda x: x["capability"]) @view(url="(?P<id>\d+)/facts/$", method=["GET"], access="read", api=True) def api_get_facts(self, request, id): o = self.get_object_or_404(ManagedObject, id=id) if not o.has_access(request.user): return self.response_forbidden("Access denied") return sorted( ( { "cls": f.cls, "label": f.label, "attrs": [ { "name": a, "value": f.attrs[a] } for a in f.attrs ], "introduced": f.introduced.isoformat(), "changed": f.changed.isoformat() } for f in ObjectFact.objects.filter(object=o.id)), key=lambda x: (x["cls"], x["label"])) @view(url="(?P<id>\d+)/revalidate/$", method=["POST"], access="read", api=True) def api_revalidate(self, request, id): def revalidate(o): engine = Engine(o) engine.check() return self.response({"status": True}, self.OK) o = self.get_object_or_404(ManagedObject, id=id) if not o.has_access(request.user): return self.response_forbidden("Access denied") return self.submit_slow_op(request, revalidate, o) @view(url="(?P<id>\d+)/actions/(?P<action>\S+)/$", method=["POST"], access="action", api=True) def api_action(self, request, id, action): def execute(o, a, args): return a.execute(o, **args) o = self.get_object_or_404(ManagedObject, id=id) if not o.has_access(request.user): return self.response_forbidden("Access denied") a = self.get_object_or_404(Action, name=action) # @todo: Check access body = request.raw_post_data if body: args = json_decode(body) else: args = {} return self.submit_slow_op(request, execute, o, a, args)
class UserProfileApplication(ExtApplication): """ main.userprofile application """ title = _("User Profile") implied_permissions = {"launch": ["main:timepattern:lookup"]} @view(url="^$", method=["GET"], access=PermitLogged(), api=True) def api_get(self, request): user = request.user language = user.preferred_language contacts = [{ "time_pattern": c.time_pattern.id, "time_pattern__label": c.time_pattern.name, "notification_method": c.notification_method, "params": c.params, } for c in UserContact.objects.filter(user=user)] return { "username": user.username, "name": (" ".join([x for x in (user.first_name, user.last_name) if x])).strip(), "email": user.email, "preferred_language": language or "en", "contacts": contacts, "groups": [g.name for g in user.groups.all().order_by("name")], } @view( url="^$", method=["POST"], access=PermitLogged(), api=True, validate={ "preferred_language": StringParameter(choices=[x[0] for x in LANGUAGES]), "contacts": ListOfParameter(element=DictParameter( attrs={ "time_pattern": ModelParameter(TimePattern), "notification_method": StringParameter(choices=[ x[0] for x in USER_NOTIFICATION_METHOD_CHOICES ]), "params": StringParameter(), })), }, ) def api_save(self, request, preferred_language, contacts): user = request.user user.preferred_language = preferred_language user.save() # Setup contacts UserContact.objects.filter(user=user).delete() for c in contacts: UserContact( user=user, time_pattern=c["time_pattern"], notification_method=c["notification_method"], params=c["params"], ).save() return True
def __init__(self, *args, **kwargs): super(ExtDocApplication, self).__init__(*args, **kwargs) self.pk = "id" # @todo: detect properly self.has_uuid = False # Prepare field converters self.clean_fields = self.clean_fields.copy() # name -> Parameter for name, f in six.iteritems(self.model._fields): if isinstance(f, BooleanField): self.clean_fields[name] = BooleanParameter() elif isinstance(f, GeoPointField): self.clean_fields[name] = GeoPointParameter() elif isinstance(f, ForeignKeyField): self.clean_fields[f.name] = ModelParameter(f.document_type, required=f.required) elif isinstance(f, ListField): if isinstance(f.field, EmbeddedDocumentField): self.clean_fields[f.name] = ListOfParameter( element=EmbeddedDocumentParameter(f.field.document_type) ) elif isinstance(f, ReferenceField): dt = f.document_type_obj if dt == "self": dt = self.model self.clean_fields[f.name] = DocumentParameter(dt, required=f.required) if f.primary_key: self.pk = name if name == "uuid": self.has_uuid = True # if not self.query_fields: self.query_fields = [ "%s__%s" % (n, self.query_condition) for n, f in six.iteritems(self.model._fields) if f.unique and isinstance(f, StringField) ] self.unique_fields = [n for n, f in six.iteritems(self.model._fields) if f.unique] # Install JSON API call when necessary self.json_collection = self.model._meta.get("json_collection") if ( self.has_uuid and hasattr(self.model, "to_json") and not hasattr(self, "api_to_json") and not hasattr(self, "api_json") ): self.add_view( "api_json", self._api_to_json, url=r"^(?P<id>[0-9a-f]{24})/json/$", method=["GET"], access="read", api=True, ) self.add_view( "api_share_info", self._api_share_info, url=r"^(?P<id>[0-9a-f]{24})/share_info/$", method=["GET"], access="read", api=True, ) if self.json_collection: self.bulk_fields += [self._bulk_field_is_builtin] # Find field_* and populate custom fields self.custom_fields = {} for fn in [n for n in dir(self) if n.startswith("field_")]: h = getattr(self, fn) if callable(h): self.custom_fields[fn[6:]] = h
class EventApplication(ExtApplication): """ fm.event application """ title = _("Events") menu = _("Events") icon = "icon_find" model_map = {"A": ActiveEvent, "F": FailedEvent, "S": ArchivedEvent} clean_fields = { "managed_object": ModelParameter(ManagedObject), "timestamp": DateTimeParameter(), } ignored_params = ["status", "_dc"] def __init__(self, *args, **kwargs): ExtApplication.__init__(self, *args, **kwargs) from .plugins.base import EventPlugin # Load plugins self.plugins = {} for f in os.listdir("services/web/apps/fm/event/plugins/"): if not f.endswith(".py") or f == "base.py" or f.startswith("_"): continue mn = "noc.services.web.apps.fm.event.plugins.%s" % f[:-3] m = __import__(mn, {}, {}, "*") for on in dir(m): o = getattr(m, on) if ( inspect.isclass(o) and issubclass(o, EventPlugin) and o.__module__.startswith(mn) ): assert o.name self.plugins[o.name] = o(self) def cleaned_query(self, q): q = q.copy() for p in self.ignored_params: if p in q: del q[p] for p in ( self.limit_param, self.page_param, self.start_param, self.format_param, self.sort_param, self.query_param, self.only_param, ): if p in q: del q[p] # Normalize parameters for p in q: qp = p.split("__")[0] if qp in self.clean_fields: q[p] = self.clean_fields[qp].clean(q[p]) if "administrative_domain" in q: a = AdministrativeDomain.objects.get(id=q["administrative_domain"]) q["managed_object__in"] = a.managedobject_set.values_list("id", flat=True) q.pop("administrative_domain") if "managedobjectselector" in q: s = SelectorCache.objects.filter(selector=q["managedobjectselector"]).values_list( "object" ) if "managed_object__in" in q: q["managed_object__in"] = list(set(q["managed_object__in"]).intersection(s)) else: q["managed_object__in"] = s q.pop("managedobjectselector") return q def instance_to_dict(self, o, fields=None): row_class = None if o.status in ("A", "S"): subject = o.subject repeats = o.repeats duration = o.duration n_alarms = len(o.alarms) if n_alarms: row_class = AlarmSeverity.get_severity_css_class_name(get_severity(o.alarms)) else: subject = None repeats = None duration = None n_alarms = None d = { "id": str(o.id), "status": o.status, "managed_object": o.managed_object.id, "managed_object__label": o.managed_object.name, "administrative_domain": o.managed_object.administrative_domain_id, "administrative_domain__label": o.managed_object.administrative_domain.name, "event_class": str(o.event_class.id) if o.status in ("A", "S") else None, "event_class__label": o.event_class.name if o.status in ("A", "S") else None, "timestamp": self.to_json(o.timestamp), "subject": subject, "repeats": repeats, "duration": duration, "alarms": n_alarms, "row_class": row_class, } if fields: d = dict((k, d[k]) for k in fields) return d def queryset(self, request, query=None): """ Filter records for lookup """ status = request.GET.get("status", "A") if status not in self.model_map: raise Exception("Invalid status") model = self.model_map[status] return model.objects @view(url=r"^$", access="launch", method=["GET"], api=True) def api_list(self, request): return self.list_data(request, self.instance_to_dict) @view(url=r"^(?P<id>[a-z0-9]{24})/$", method=["GET"], api=True, access="launch") def api_event(self, request, id): event = get_event(id) if not event: return self.response_not_found() d = self.instance_to_dict(event) dd = dict( (v, None) for v in ( "body", "symptoms", "probable_causes", "recommended_actions", "log", "vars", "resolved_vars", "raw_vars", ) ) if event.status in ("A", "S"): dd["body"] = event.body dd["symptoms"] = event.event_class.symptoms dd["probable_causes"] = event.event_class.probable_causes dd["recommended_actions"] = event.event_class.recommended_actions # Fill vars left = set(event.vars) vars = [] for ev in event.event_class.vars: if ev.name in event.vars: vars += [(ev.name, event.vars[ev.name], ev.description)] left.remove(ev.name) vars += [(v, event.vars[v], None) for v in sorted(left)] dd["vars"] = vars # Fill resolved vars vars = [] is_trap = event.raw_vars.get("source") == "SNMP Trap" for v in sorted(event.resolved_vars): desc = None if is_trap and "::" in v: desc = MIB.get_description(v) vars += [(v, event.resolved_vars[v], desc)] dd["resolved_vars"] = vars dd["raw_vars"] = sorted(event.raw_vars.items()) # Managed object properties mo = event.managed_object d["managed_object_address"] = mo.address d["managed_object_profile"] = mo.profile.name d["managed_object_platform"] = mo.platform.name if mo.platform else "" d["managed_object_version"] = mo.version.version if mo.version else "" d["segment"] = mo.segment.name d["segment_id"] = str(mo.segment.id) d["tags"] = mo.tags # Log if event.log: dd["log"] = [ { "timestamp": self.to_json(l.timestamp), "from_status": l.from_status, "to_status": l.to_status, "message": l.message, } for l in event.log ] # d.update(dd) # Get alarms if event.status in ("A", "S"): alarms = [] for a_id in event.alarms: a = get_alarm(a_id) if not a: continue if a.opening_event == event.id: role = "O" elif a.closing_event == event.id: role = "C" else: role = "" alarms += [ { "id": str(a.id), "status": a.status, "alarm_class": str(a.alarm_class.id), "alarm_class__label": a.alarm_class.name, "subject": a.subject, "role": role, "timestamp": self.to_json(a.timestamp), } ] d["alarms"] = alarms # Apply plugins if event.status in ("A", "S") and event.event_class.plugins: plugins = [] for p in event.event_class.plugins: if p.name in self.plugins: plugin = self.plugins[p.name] dd = plugin.get_data(event, p.config) if "plugins" in dd: plugins += dd["plugins"] del dd["plugins"] d.update(dd) if plugins: d["plugins"] = plugins elif event.status == "F": # Enable traceback plugin for failed events d["traceback"] = event.traceback d["plugins"] = [("NOC.fm.event.plugins.Traceback", {})] return d @view( url=r"^(?P<id>[a-z0-9]{24})/post/", method=["POST"], api=True, access="launch", validate={"msg": UnicodeParameter()}, ) def api_post(self, request, id, msg): event = get_event(id) if not event: self.response_not_found() event.log_message("%s: %s" % (request.user.username, msg)) return True rx_parse_log = re.compile("^Classified as '(.+?)'.+$") @view(url=r"^(?P<id>[a-z0-9]{24})/json/$", method=["GET"], api=True, access="launch") def api_json(self, request, id): event = get_event(id) if not event: self.response_not_found() # Get event class e_class = None if event.status in ("A", "S"): for l in event.log: match = self.rx_parse_log.match(l.message) if match: e_class = match.group(1) r = ["["] r += [" {"] r += [' "profile": "%s",' % json_escape(event.managed_object.profile.name)] if e_class: r += [' "event_class__name": "%s",' % e_class] r += [' "raw_vars": {'] rr = [] for k in event.raw_vars: if k in ("collector", "severity", "facility"): continue rr += [' "%s": "%s"' % (json_escape(k), json_escape(str(event.raw_vars[k])))] r += [",\n".join(rr)] r += [" }"] r += [" }"] r += ["]"] return "\n".join(r) @view(url=r"^(?P<id>[a-z0-9]{24})/reclassify/$", method=["POST"], api=True, access="launch") def api_reclassify(self, request, id): event = get_event(id) if not event: self.response_not_found() if event.status == "N": return False event.mark_as_new( "Event reclassification has been requested " "by user %s" % request.user.username ) return True
class GetNowApplication(ExtApplication): """ sa.getnow application """ title = "Get Now" menu = "Get Now" icon = "icon_monitor" ignored_params = [ "status", "_dc", "managed_object", "profile_name", "administrative_domain" ] clean_fields = {"managed_object": ModelParameter(ManagedObject)} @view("^$", method=["GET"], access="read", api=True) def api_list(self, request): return self.list_data(request, self.instance_to_dict) def queryset(self, request, query=None): """ Filter records for lookup """ get_request_data = request.GET qs = DiscoveryJob.objects.filter( jcls='config_discovery').order_by('status') if 'managed_object' in get_request_data: qs = qs.filter(object=int(get_request_data['managed_object'])) if 'profile_name' in get_request_data: ids = ManagedObject.objects.filter( profile_name=get_request_data['profile_name']).values_list( 'id', flat=True) qs = qs.filter(object__in=ids) if 'administrative_domain' in get_request_data: ids = ManagedObject.objects.filter( administrative_domain=get_request_data['administrative_domain'] ) qs = qs.filter(object__in=ids) return qs def cleaned_query(self, q): to_clean_up = (self.limit_param, self.page_param, self.start_param, self.format_param, self.sort_param, self.query_param, self.only_param) q = q.copy() for p in self.ignored_params: if p in q: del q[p] for p in to_clean_up: if p in q: del q[p] for p in q: qp = p.split("__")[0] if qp in self.clean_fields: q[p] = self.clean_fields[qp].form_clean(q[p]) return q def instance_to_dict(self, o, fields=None): last_success = humanize_distance(o.ts) if o.ts else '--' mo = ManagedObject.objects.get(id=o.object) last_update = mo.config.get_revisions(reverse=True) if last_update: last_update = humanize_distance(last_update[0].ts) return { 'id': str(mo.id), 'name': mo.name, 'profile_name': mo.profile_name, 'last_success': last_success, 'status': o.status, 'last_status': o.last_status, 'last_update': last_update if last_update else None }