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 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 UnknownModelApplication(ExtDocApplication): """ UnknownModel application """ title = _("Unknown Models") menu = _("Unknown Models") model = UnknownModel query_condition = "icontains" query_fields = [ "vendor", "managed_object", "platform", "part_no", "description" ] @view( url="^actions/remove/$", method=["POST"], access="launch", api=True, validate={ "ids": ListOfParameter(element=DocumentParameter(UnknownModel), convert=True) }, ) def api_action_run_discovery(self, request, ids): UnknownModel.objects.filter(id__in=[x.id for x in ids]) return _("Cleaned")
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], }
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 ConnectionRuleApplication(ExtDocApplication): """ ConnectionRule application """ title = _("Connection Rules") menu = [_("Setup"), _("Connection Rules")] model = ConnectionRule query_fields = ["name__icontains", "description__icontains"] @view( url="^actions/json/$", method=["POST"], access="read", validate={ "ids": ListOfParameter(element=DocumentParameter(ConnectionRule), convert=True) }, api=True, ) def api_action_json(self, request, ids): r = [o.json_data for o in ids] s = to_json(r, order=["name", "description"]) return {"data": s}
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}
def test_listof_parameter(): assert ListOfParameter(element=IntParameter()).clean([1, 2, 3]) == [1, 2, 3] assert ListOfParameter(element=IntParameter()).clean([1, 2, "3"]) == [1, 2, 3] with pytest.raises(InterfaceTypeError): ListOfParameter(element=IntParameter()).clean([1, 2, "x"]) assert ListOfParameter(element=StringParameter()).clean( [1, 2, 3, "x"]) == ["1", "2", "3", "x"] assert ListOfParameter(element=StringParameter(), default=[]).clean(None) == [] assert ListOfParameter(element=StringParameter(), default=[1, 2, 3]).clean(None) == ["1", "2", "3"] assert ListOfParameter( element=[StringParameter(), IntParameter()]).clean([ ("a", 1), ("b", "2") ]) == [["a", 1], ["b", 2]] with pytest.raises(InterfaceTypeError): assert ListOfParameter( element=[StringParameter(), IntParameter()]).clean([("a", 1), ("b", "x")])
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 test_listof_parameter(raw, config, expected): assert ListOfParameter(**config).clean(raw) == expected
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 InvApplication(ExtApplication): """ inv.inv application """ title = _("Inventory") menu = _("Inventory") # Undeletable nodes UNDELETABLE = { # Global Lost&Found "b0fae773-b214-4edf-be35-3468b53b03f2" } def __init__(self, *args, **kwargs): ExtApplication.__init__(self, *args, **kwargs) # Load plugins from .plugins.base import InvPlugin self.plugins = {} for f in os.listdir("services/web/apps/inv/inv/plugins/"): if not f.endswith(".py") or f == "base.py" or f.startswith("_"): continue mn = "noc.services.web.apps.inv.inv.plugins.%s" % f[:-3] m = __import__(mn, {}, {}, "*") for on in dir(m): o = getattr(m, on) if inspect.isclass(o) and issubclass( o, InvPlugin) and o.__module__.startswith(mn): assert o.name self.plugins[o.name] = o(self) def get_plugin_data(self, name): return {"name": name, "xtype": self.plugins[name].js} @view("^node/$", method=["GET"], access="read", api=True) def api_node(self, request): children = [] if request.GET and "node" in request.GET: container = request.GET["node"] if is_objectid(container): container = Object.get_by_id(container) if not container: return self.response_not_found() children = [ (o.name, o) for o in Object.objects.filter(container=container.id) ] # Collect inner connections children += [ (name, o) for name, o, _ in container.get_inner_connections() ] elif container == "root": cmodels = [ d["_id"] for d in ObjectModel._get_collection().find( {"data.container.container": True}, {"_id": 1}) ] children = [(o.name, o) for o in Object.objects.filter(__raw__={ "container": None, "model": { "$in": cmodels } })] else: return self.response_bad_request() r = [] # Build node interface for name, o in children: m_plugins = o.model.plugins or [] disabled_plugins = set(p[1:] for p in m_plugins if p.startswith("-")) n = { "id": str(o.id), "name": name, "plugins": [], "can_add": bool(o.get_data("container", "container")), "can_delete": str(o.model.uuid) not in self.UNDELETABLE, } if o.get_data("container", "container") or o.has_inner_connections(): # n["expanded"] = Object.objects.filter(container=o.id).count() == 1 n["expanded"] = False else: n["leaf"] = True if o.get_data("rack", "units"): n["plugins"] += [self.get_plugin_data("rack")] if o.model.connections: n["plugins"] += [self.get_plugin_data("inventory")] if o.get_data("geopoint", "layer"): n["plugins"] += [self.get_plugin_data("map")] if o.get_data("management", "managed_object"): n["plugins"] += [self.get_plugin_data("managedobject")] if o.get_data("contacts", "has_contacts"): n["plugins"] += [self.get_plugin_data("contacts")] # Append model's plugins for p in m_plugins: if not p.startswith("-"): n["plugins"] += [self.get_plugin_data(p)] n["plugins"] += [ self.get_plugin_data("data"), self.get_plugin_data("comment"), self.get_plugin_data("file"), self.get_plugin_data("log"), ] # Process disabled plugins n["plugins"] = [ p for p in n["plugins"] if p["name"] not in disabled_plugins ] r += [n] return r @view( "^add_group/$", method=["POST"], access="create_group", api=True, validate={ "container": ObjectIdParameter(required=False), "type": ObjectIdParameter(), "name": UnicodeParameter(), "serial": UnicodeParameter(required=False), }, ) def api_add_group(self, request, type, name, container=None, serial=None): if is_objectid(container): c = Object.get_by_id(container) if not c: return self.response_not_found() c = c.id elif container: return self.response_bad_request() else: c = None m = ObjectModel.get_by_id(type) if not m: return self.response_not_found() o = Object(name=name, model=m, container=c) if serial and m.get_data("asset", "part_no0"): o.set_data("asset", "serial", serial) o.save() o.log("Created", user=request.user.username, system="WEB", op="CREATE") return str(o.id) @view( "^remove_group/$", method=["DELETE"], access="remove_group", api=True, validate={"container": ObjectIdParameter(required=True)}, ) def api_remove_group(self, request, container=None): c = self.get_object_or_404(Object, id=container) c.delete() return True @view( "^insert/$", method=["POST"], access="reorder", api=True, validate={ "container": ObjectIdParameter(required=False), "objects": ListOfParameter(element=ObjectIdParameter()), "position": StringParameter(), }, ) def api_insert(self, request, container, objects, position): """ :param request: :param container: ObjectID after/in that insert :param objects: List ObjectID for insert :param position: 'append', 'before', 'after' :return: """ c = self.get_object_or_404(Object, id=container) o = [] for r in objects: o += [self.get_object_or_404(Object, id=r)] if position == "append": for x in o: x.put_into(c) elif position in ("before", "after"): cc = self.get_object_or_404( Object, id=c.container.id) if c.container else None for x in o: x.put_into(cc) return True @view("^(?P<id>[0-9a-f]{24})/path/$", method=["GET"], access="read", api=True) def api_get_path(self, request, id): o = self.get_object_or_404(Object, id=id) path = [{"id": str(o.id), "name": o.name}] while o.container: o = o.container path.insert(0, {"id": str(o.id), "name": o.name}) return path @view( "^crossing_proposals/$", method=["GET"], access="read", api=True, validate={ "o1": ObjectIdParameter(required=True), "o2": ObjectIdParameter(required=False), "left_filter": UnicodeParameter(required=False), "right_filter": UnicodeParameter(required=False), "cable_filter": UnicodeParameter(required=False), }, ) def api_get_crossing_proposals( self, request, o1, o2=None, left_filter: Optional[str] = None, right_filter: Optional[str] = None, cable_filter: Optional[str] = None, ): """ API for connnection form. 1) If cable_filter set, checked connection capable with cable. 2) If left_filter set, check renmote object :param request: :param o1: :param o2: :param left_filter: :param right_filter: :param cable_filter: :return: """ self.logger.info( "Crossing proposals: %s:%s, %s:%s. Cable: %s", o1, left_filter, o2, right_filter, cable_filter, ) lo: Object = self.get_object_or_404(Object, id=o1) ro: Optional[Object] = None if o2: ro = self.get_object_or_404(Object, id=o2) lcs: List[Dict[str, Any]] = [] cable: Optional[ObjectModel] = None # Getting cable cables = ObjectModel.objects.filter(data__length__length__gte=0) if cable_filter: cable = ObjectModel.get_by_name(cable_filter) for c in lo.model.connections: valid, disable_reason = True, "" if cable_filter: # If select cable_filter - check every connection to cable cable_connections = [ c for c in lo.model.get_connection_proposals(c.name) if c[0] == cable.id ] valid = bool(cable_connections) elif ro and right_filter: rc = ro.model.get_model_connection(right_filter) if not rc: raise valid, disable_reason = lo.model.check_connection(c, rc) elif ro: valid = bool([ c for c in lo.model.get_connection_proposals(c.name) if c[0] == ro.model.id ]) oc, oo, _ = lo.get_p2p_connection(c.name) lcs += [{ "name": c.name, "type": str(c.type.id), "type__label": c.type.name, "gender": c.gender, "direction": c.direction, "protocols": c.protocols, "free": not bool(oc), "valid": valid, "disable_reason": disable_reason, }] rcs: List[Dict[str, Any]] = [] if ro: for c in ro.model.connections: valid, disable_reason = True, "" if cable_filter: cable_connections = [ c for c in ro.model.get_connection_proposals(c.name) if c[0] == cable.id ] valid = bool(cable_connections) elif left_filter: lc = lo.model.get_model_connection(left_filter) if not lc: raise valid, disable_reason = lo.model.check_connection(c, lc) else: valid = bool([ c for c in ro.model.get_connection_proposals(c.name) if c[0] == lo.model.id ]) oc, oo, _ = ro.get_p2p_connection(c.name) rcs += [{ "name": c.name, "type": str(c.type.id), "type__label": c.type.name, "gender": c.gender, "direction": c.direction, "protocols": c.protocols, "free": not bool(oc), "valid": valid, "disable_reason": disable_reason, }] # Forming cable return { "left": { "connections": lcs }, "right": { "connections": rcs }, "cable": [{ "name": c.name, "available": True } for c in cables], "valid": lcs and rcs and left_filter and right_filter, } @view( "^connect/$", method=["POST"], access="connect", api=True, validate={ "object": ObjectIdParameter(required=True), "name": StringParameter(required=True), "remote_object": ObjectIdParameter(required=True), "remote_name": StringParameter(required=True), # "cable": ObjectIdParameter(required=False), "cable": StringParameter(required=False), "reconnect": BooleanParameter(default=False, required=False), }, ) def api_connect( self, request, object, name, remote_object, remote_name, cable: Optional[str] = None, reconnect=False, ): lo: Object = self.get_object_or_404(Object, id=object) ro: Object = self.get_object_or_404(Object, id=remote_object) cable_o: Optional[Object] = None if cable: cable = ObjectModel.get_by_name(cable) cable_o = Object( name="Wire %s:%s <-> %s:%s" % (lo.name, name, ro.name, remote_name), model=cable, container=lo.container.id, ) cable_o.save() print(lo, ro, cable_o) try: if cable_o: c1, c2 = cable_o.model.connections[:2] self.logger.debug("Wired connect c1:c2", c1, c2) lo.connect_p2p(name, cable_o, c1.name, {}, reconnect=reconnect) ro.connect_p2p(remote_name, cable_o, c2.name, {}, reconnect=reconnect) lo.save() ro.save() else: lo.connect_p2p(name, ro, remote_name, {}, reconnect=reconnect) except ConnectionError as e: self.logger.warning("Connection Error: %s", str(e)) return self.render_json({"status": False, "text": str(e)}) return True
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 InvApplication(ExtApplication): """ inv.inv application """ title = _("Inventory") menu = _("Inventory") # Undeletable nodes UNDELETABLE = { # Global Lost&Found "b0fae773-b214-4edf-be35-3468b53b03f2" } def __init__(self, *args, **kwargs): ExtApplication.__init__(self, *args, **kwargs) # Load plugins from .plugins.base import InvPlugin self.plugins = {} for f in os.listdir("services/web/apps/inv/inv/plugins/"): if not f.endswith(".py") or f == "base.py" or f.startswith("_"): continue mn = "noc.services.web.apps.inv.inv.plugins.%s" % f[:-3] m = __import__(mn, {}, {}, "*") for on in dir(m): o = getattr(m, on) if inspect.isclass(o) and issubclass(o, InvPlugin) and o.__module__.startswith(mn): assert o.name self.plugins[o.name] = o(self) def get_plugin_data(self, name): return {"name": name, "xtype": self.plugins[name].js} @view("^node/$", method=["GET"], access="read", api=True) def api_node(self, request): children = [] if request.GET and "node" in request.GET: container = request.GET["node"] if is_objectid(container): container = Object.get_by_id(container) if not container: return self.response_not_found() children = [(o.name, o) for o in Object.objects.filter(container=container.id)] # Collect inner connections children += [(name, o) for name, o, _ in container.get_inner_connections()] elif container == "root": cmodels = [ d["_id"] for d in ObjectModel._get_collection().find( {"data.container.container": True}, {"_id": 1} ) ] children = [ (o.name, o) for o in Object.objects.filter( __raw__={"container": None, "model": {"$in": cmodels}} ) ] else: return self.response_bad_request() r = [] # Build node interface for name, o in children: m_plugins = o.model.plugins or [] disabled_plugins = set(p[1:] for p in m_plugins if p.startswith("-")) n = { "id": str(o.id), "name": name, "plugins": [], "can_add": bool(o.get_data("container", "container")), "can_delete": str(o.model.uuid) not in self.UNDELETABLE, } if o.get_data("container", "container") or o.has_inner_connections(): # n["expanded"] = Object.objects.filter(container=o.id).count() == 1 n["expanded"] = False else: n["leaf"] = True if o.get_data("rack", "units"): n["plugins"] += [self.get_plugin_data("rack")] if o.model.connections: n["plugins"] += [self.get_plugin_data("inventory")] if o.get_data("geopoint", "layer"): n["plugins"] += [self.get_plugin_data("map")] if o.get_data("management", "managed_object"): n["plugins"] += [self.get_plugin_data("managedobject")] if o.get_data("contacts", "has_contacts"): n["plugins"] += [self.get_plugin_data("contacts")] # Append model's plugins for p in m_plugins: if not p.startswith("-"): n["plugins"] += [self.get_plugin_data(p)] n["plugins"] += [ self.get_plugin_data("data"), self.get_plugin_data("comment"), self.get_plugin_data("file"), self.get_plugin_data("log"), ] # Process disabled plugins n["plugins"] = [p for p in n["plugins"] if p["name"] not in disabled_plugins] r += [n] return r @view( "^add_group/$", method=["POST"], access="create_group", api=True, validate={ "container": ObjectIdParameter(required=False), "type": ObjectIdParameter(), "name": UnicodeParameter(), "serial": UnicodeParameter(required=False), }, ) def api_add_group(self, request, type, name, container=None, serial=None): if is_objectid(container): c = Object.get_by_id(container) if not c: return self.response_not_found() c = c.id elif container: return self.response_bad_request() else: c = None m = ObjectModel.get_by_id(type) if not m: return self.response_not_found() o = Object(name=name, model=m, container=c) if serial and m.get_data("asset", "part_no0"): o.set_data("asset", "serial", serial) o.save() o.log("Created", user=request.user.username, system="WEB", op="CREATE") return str(o.id) @view( "^remove_group/$", method=["DELETE"], access="remove_group", api=True, validate={"container": ObjectIdParameter(required=True)}, ) def api_remove_group(self, request, container=None): c = self.get_object_or_404(Object, id=container) c.delete() return True @view( "^insert/$", method=["POST"], access="reorder", api=True, validate={ "container": ObjectIdParameter(required=False), "objects": ListOfParameter(element=ObjectIdParameter()), "position": StringParameter(), }, ) def api_insert(self, request, container, objects, position): """ :param request: :param container: ObjectID after/in that insert :param objects: List ObjectID for insert :param position: 'append', 'before', 'after' :return: """ c = self.get_object_or_404(Object, id=container) o = [] for r in objects: o += [self.get_object_or_404(Object, id=r)] if position == "append": for x in o: x.put_into(c) elif position in ("before", "after"): cc = self.get_object_or_404(Object, id=c.container.id) if c.container else None for x in o: x.put_into(cc) return True @view("^(?P<id>[0-9a-f]{24})/path/$", method=["GET"], access="read", api=True) def api_get_path(self, request, id): o = self.get_object_or_404(Object, id=id) path = [{"id": str(o.id), "name": o.name}] while o.container: o = o.container path.insert(0, {"id": str(o.id), "name": o.name}) return path
class InvApplication(ExtApplication): """ inv.inv application """ title = "Inventory" menu = "Inventory" # Undeletable nodes UNDELETABLE = set([ # Global Lost&Found "b0fae773-b214-4edf-be35-3468b53b03f2" ]) def __init__(self, *args, **kwargs): ExtApplication.__init__(self, *args, **kwargs) # Load plugins from plugins.base import InvPlugin self.plugins = {} for f in os.listdir("inv/apps/inv/plugins/"): if (not f.endswith(".py") or f == "base.py" or f.startswith("_")): continue mn = "noc.inv.apps.inv.plugins.%s" % f[:-3] m = __import__(mn, {}, {}, "*") for on in dir(m): o = getattr(m, on) if (inspect.isclass(o) and issubclass(o, InvPlugin) and o.__module__.startswith(mn)): assert o.name self.plugins[o.name] = o(self) def get_root(self): """ Get root container """ if not hasattr(self, "root_container"): rm = ObjectModel.objects.get(name="Root") rc = list(Object.objects.filter(model=rm)) if len(rc) == 0: raise Exception("No root object") elif len(rc) == 1: self.root_container = rc[0] return self.root_container else: raise Exception("Multiple root objects") else: return self.root_container def get_plugin_data(self, name): return { "name": name, "xtype": self.plugins[name].js } @view("^node/$", method=["GET"], access="read", api=True) def api_node(self, request): container = None if request.GET and "node" in request.GET: container = request.GET["node"] if container == "root": container = self.get_root() elif not is_objectid(container): raise Exception("Invalid node") else: container = self.get_object_or_404(Object, id=container) r = [] if not container: container = self.get_root() # Collect children objects children = [ (o.name, o) for o in Object.objects.filter(container=container.id) ] # Collect inner connections children += [ (name, o) for name, o, _ in container.get_inner_connections() ] # Build node interface for name, o in children: m_plugins = o.model.plugins or [] disabled_plugins = set(p[1:] for p in m_plugins if p.startswith("-")) n = { "id": str(o.id), "name": name, "plugins": [], "can_add": bool(o.get_data("container", "container")), "can_delete": str(o.model.uuid) not in self.UNDELETABLE } if (o.get_data("container", "container") or o.has_inner_connections()): n["expanded"] = Object.objects.filter(container=o.id).count() == 1 else: n["leaf"] = True if o.get_data("rack", "units"): n["plugins"] += [self.get_plugin_data("rack")] if o.model.connections: n["plugins"] += [self.get_plugin_data("inventory")] if o.get_data("geopoint", "layer"): n["plugins"] += [self.get_plugin_data("map")] if o.get_data("management", "managed_object"): n["plugins"] += [self.get_plugin_data("managedobject")] # Append model's plugins for p in m_plugins: if not p.startswith("-"): n["plugins"] += [self.get_plugin_data(p)] n["plugins"] += [ self.get_plugin_data("data"), self.get_plugin_data("comment"), self.get_plugin_data("file"), self.get_plugin_data("log") ] # Process disabled plugins n["plugins"] = [p for p in n["plugins"] if p["name"] not in disabled_plugins] r += [n] return r @view("^add_group/$", method=["POST"], access="create_group", api=True, validate={ "container": ObjectIdParameter(required=False), "type": ObjectIdParameter(), "name": UnicodeParameter(), "serial": UnicodeParameter(required=False) }) def api_add_group(self, request, type, name, container=None, serial=None): if container is None: c = self.get_root() else: c = self.get_object_or_404(Object, id=container) m = self.get_object_or_404(ObjectModel, id=type) o = Object(name=name, model=m, container=c.id) if serial and m.get_data("asset", "part_no0"): o.set_data("asset", "serial", serial) o.save() o.log("Created", user=request.user.username, system="WEB", op="CREATE") return str(o.id) @view("^remove_group/$", method=["DELETE"], access="remove_group", api=True, validate={ "container": ObjectIdParameter(required=True) }) def api_remove_group(self, request, container=None): c = self.get_object_or_404(Object, id=container) c.delete() return True @view("^insert/$", method=["POST"], access="reorder", api=True, validate={ "container": ObjectIdParameter(required=False), "objects": ListOfParameter(element=ObjectIdParameter()), "position": StringParameter() }) def api_insert(self, request, container, objects, position): c = self.get_object_or_404(Object, id=container) o = [] for r in objects: o += [self.get_object_or_404(Object, id=r)] if position == "append": for x in o: x.put_into(c) elif position in ("before", "after"): cc = self.get_object_or_404(Object, id=c.container) for x in o: x.put_into(cc) return True @view("^(?P<id>[0-9a-f]{24})/path/$", method=["GET"], access="read", api=True) def api_get_path(self, request, id): o = self.get_object_or_404(Object, id=id) path = [{ "id": str(o.id), "name": o.name }] root = self.get_root().id while o.container and o.container != root: o = Object.objects.get(id=o.container) path = [{ "id": str(o.id), "name": o.name }] + path return path
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
def cleaned_query(self, q): nq = {} q = q.copy() # Extract IN # extjs not working with same parameter name in query for p in list(q.keys()): if p.endswith(self.in_param): match = self.rx_oper_splitter.match(p) if match: field = self.rx_oper_splitter.match(p).group("field") + self.in_param if field not in q: q[field] = "%s" % (q[p]) else: q[field] += ",%s" % (q[p]) del q[p] for p in q: if p.endswith("__exists"): v = BooleanParameter().clean(q[p]) nq[p.replace("__exists", "__isnull")] = not v continue if "__" in p: np, lt = p.split("__", 1) else: np, lt = p, None # Skip ignored params if np in self.ignored_params or p in ( self.limit_param, self.page_param, self.start_param, self.format_param, self.sort_param, self.group_param, self.query_param, self.only_param, ): continue v = q[p] if self.in_param in p: v = v.split(",") if v == "\x00": v = None # Pass through interface cleaners if lt == "referred": # Unroll __referred app, fn = v.split("__", 1) model = self.site.apps[app].model if not is_document(model): extra_where = '%s."%s" IN (SELECT "%s" FROM %s)' % ( self.model._meta.db_table, self.model._meta.pk.name, model._meta.get_field(fn).attname, model._meta.db_table, ) if None in nq: nq[None] += [extra_where] else: nq[None] = [extra_where] continue elif lt and hasattr(self, "lookup_%s" % lt): # Custom lookup getattr(self, "lookup_%s" % lt)(nq, np, v) continue elif np in self.fk_fields and lt: # dereference try: nq[np] = self.fk_fields[np].objects.get(**{lt: v}) except self.fk_fields[np].DoesNotExist: nq[np] = 0 # False search continue elif np in self.clean_fields and self.in_param in p: v = ListOfParameter(self.clean_fields[np]).clean(v) elif np in self.clean_fields: # @todo: Check for valid lookup types v = self.clean_fields[np].clean(v) # Write back nq[p] = v return nq
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
IntParameter, ListOfParameter, ListParameter, ) from noc.pm.models.metrictype import MetricType from ..base import NBIAPI Request = DictParameter( attrs={ "bi_id": IntParameter(required=True), "metrics": DictListParameter( attrs={ "metric_type": StringParameter(required=True), "path": StringListParameter(required=True), "values": ListOfParameter(ListParameter(), required=True), }, required=True, ), } ) class TelemetryAPI(NBIAPI): name = "telemetry" @authenticated @tornado.gen.coroutine def post(self): code, result = yield self.executor.submit(self.handler) self.set_status(code)
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
def test_listof_parameter_error(raw, config): with pytest.raises(InterfaceTypeError): assert ListOfParameter(**config).clean(raw)
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
class ObjectModelApplication(ExtDocApplication): """ ObjectModel application """ title = "Object Models" menu = "Setup | Object Models" model = ObjectModel parent_model = DocCategory parent_field = "parent" query_fields = [ "name__icontains", "description__icontains", "data__asset__part_no", "data__asset__order_part_no", "uuid" ] def clean(self, data): if "data" in data: data["data"] = ModelInterface.clean_data(data["data"]) if "plugins" in data and data["plugins"]: data["plugins"] = [ x.strip() for x in data["plugins"].split(",") if x.strip() ] else: data["plugins"] = None return super(ObjectModelApplication, self).clean(data) def cleaned_query(self, q): if "is_container" in q: q["data__container__container"] = True q["name__ne"] = "Root" del q["is_container"] return super(ObjectModelApplication, self).cleaned_query(q) @view(url="^(?P<id>[0-9a-f]{24})/compatible/$", method=["GET"], access="read", api=True) def api_compatible(self, request, id): o = self.get_object_or_404(ObjectModel, id=id) # Connections r = [] for c in o.connections: # Find compatible objects proposals = [] for t, n in o.get_connection_proposals(c.name): m = ObjectModel.objects.filter(id=t).first() mc = m.get_model_connection(n) proposals += [{ "model": m.name, "model_description": m.description, "name": n, "description": mc.description, "gender": mc.gender }] # if (r and r[-1]["direction"] == c.direction and r[-1]["gender"] == c.gender and r[-1]["connections"] == proposals): r[-1]["names"] += [{ "name": c.name, "description": c.description }] else: r += [{ "names": [{ "name": c.name, "description": c.description }], "direction": c.direction, "gender": c.gender, "connections": proposals }] # Crossing # @todo: Count splitter interface rc = [] for c in o.connections: if c.cross: rc += [{"y": c.name, "x": c.cross, "v": "1"}] return {"connections": r, "crossing": rc} @view(url="^actions/json/$", method=["POST"], access="read", validate={ "ids": ListOfParameter(element=DocumentParameter(ObjectModel), convert=True) }, api=True) def api_action_json(self, request, ids): r = [o.json_data for o in ids] s = to_json(r, order=["name", "vendor__code", "description"]) return {"data": s}
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 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 MapApplication(ExtApplication): """ inv.net application """ title = _("Network Map") menu = _("Network Map") glyph = "globe" implied_permissions = {"launch": ["inv:networksegment:lookup"]} # Object statuses ST_UNKNOWN = 0 # Object state is unknown ST_OK = 1 # Object is OK ST_ALARM = 2 # Object is reachable, Active alarms ST_UNREACH = 3 # Object is unreachable due to other's object failure ST_DOWN = 4 # Object is down ST_MAINTENANCE = 32 # Maintenance bit @view(r"^(?P<id>[0-9a-f]{24})/data/$", method=["GET"], access="read", api=True) def api_data(self, request, id): def q_mo(d): x = d.copy() if x["type"] == "managedobject": del x["mo"] x["external"] = x["id"] not in mos if is_view else x.get( "role") != "segment" elif d["type"] == "cloud": del x["link"] x["external"] = False return x # Find segment segment = self.get_object_or_404(NetworkSegment, id=id) if segment.managed_objects.count() > segment.max_objects: # Too many objects return { "id": str(segment.id), "name": segment.name, "error": _("Too many objects") } # if we set selector in segment is_view = segment.selector if is_view: mos = segment.selector.managed_objects.values_list("id", flat=True) # Load settings settings = MapSettings.objects.filter(segment=id).first() node_hints = {} link_hints = {} if settings: self.logger.info("Using stored positions") for n in settings.nodes: node_hints[n.id] = { "type": n.type, "id": n.id, "x": n.x, "y": n.y } for ll in settings.links: link_hints[ll.id] = { "connector": ll.connector if len(ll.vertices) else "normal", "vertices": [{ "x": v.x, "y": v.y } for v in ll.vertices], } else: self.logger.info("Generating positions") # Generate topology topology = SegmentTopology( segment, node_hints, link_hints, force_spring=request.GET.get("force") == "spring") topology.layout() # Build output r = { "id": str(segment.id), "max_links": int(segment.max_shown_downlinks), "name": segment.name, "caps": list(topology.caps), "nodes": [q_mo(x) for x in topology.G.nodes.values()], "links": [topology.G[u][v] for u, v in topology.G.edges()], } # Parent info if segment.parent: r["parent"] = { "id": str(segment.parent.id), "name": segment.parent.name } # Save settings if not settings: self.logger.debug("Saving first-time layout") MapSettings.load_json({ "id": str(segment.id), "nodes": [{ "type": n["type"], "id": n["id"], "x": n["x"], "y": n["y"] } for n in r["nodes"] if n.get("x") is not None and n.get("y") is not None], "links": [{ "type": n["type"], "id": n["id"], "vertices": n.get("vertices", []), "connector": n.get("connector", "normal"), } for n in r["links"]], }) return r @view(r"^(?P<id>[0-9a-f]{24})/data/$", method=["POST"], access="write", api=True) def api_save(self, request, id): self.get_object_or_404(NetworkSegment, id=id) data = self.deserialize(request.body) data["id"] = id MapSettings.load_json(data, request.user.username) return {"status": True} @view(url=r"^(?P<id>[0-9a-f]{24})/info/segment/$", method=["GET"], access="read", api=True) def api_info_segment(self, request, id): segment = self.get_object_or_404(NetworkSegment, id=id) r = { "name": segment.name, "description": segment.description, "objects": segment.managed_objects.count(), } return r @view( url=r"^(?P<id>[0-9a-f]{24})/info/managedobject/(?P<mo_id>\d+)/$", method=["GET"], access="read", api=True, ) def api_info_managedobject(self, request, id, mo_id): segment = self.get_object_or_404(NetworkSegment, id=id) object = self.get_object_or_404(ManagedObject, id=int(mo_id)) s = {1: "telnet", 2: "ssh", 3: "http", 4: "https"}[object.scheme] r = { "id": object.id, "name": object.name, "description": object.description, "address": object.address, "platform": object.platform.full_name if object.platform else "", "profile": object.profile.name, "external": object.segment.id != segment.id, "external_segment": { "id": str(object.segment.id), "name": object.segment.name }, "caps": object.get_caps(), "console_url": "%s://%s/" % (s, object.address), } return r @view( url=r"^(?P<id>[0-9a-f]{24})/info/link/(?P<link_id>[0-9a-f]{24})/$", method=["GET"], access="read", api=True, ) def api_info_link(self, request, id, link_id): def q(s): if isinstance(s, str): s = s.encode("utf-8") return s self.get_object_or_404(NetworkSegment, id=id) link = self.get_object_or_404(Link, id=link_id) r = { "id": str(link.id), "name": link.name or None, "description": link.description or None, "objects": [], "method": link.discovery_method, } o = defaultdict(list) for i in link.interfaces: o[i.managed_object] += [i] for mo in sorted(o, key=lambda x: x.name): r["objects"] += [{ "id": mo.id, "name": mo.name, "interfaces": [{ "name": i.name, "description": i.description or None, "status": i.status } for i in sorted(o[mo], key=lambda x: alnum_key(x.name))], }] # Get link bandwidth mo_in = defaultdict(float) mo_out = defaultdict(float) mos = [ManagedObject.get_by_id(mo["id"]) for mo in r["objects"]] metric_map, last_ts = get_interface_metrics(list(o)) for mo in o: if mo not in metric_map: continue for i in o[mo]: if i.name not in metric_map[mo]: continue mo_in[mo] += metric_map[mo][i.name]["Interface | Load | In"] mo_out[mo] += metric_map[mo][i.name]["Interface | Load | Out"] if len(mos) == 2: mo1, mo2 = mos r["utilisation"] = [ int(max(mo_in[mo1], mo_out[mo2])), int(max(mo_in[mo2], mo_out[mo1])), ] else: mv = list(mo_in.values()) + list(mo_out.values()) if mv: r["utilisation"] = [int(max(mv))] else: r["utilisation"] = 0 return r @view( url=r"^(?P<id>[0-9a-f]{24})/info/cloud/(?P<link_id>[0-9a-f]{24})/$", method=["GET"], access="read", api=True, ) def api_info_cloud(self, request, id, link_id): self.get_object_or_404(NetworkSegment, id=id) link = self.get_object_or_404(Link, id=link_id) r = { "id": str(link.id), "name": link.name or None, "description": link.description or None, "objects": [], "method": link.discovery_method, } o = defaultdict(list) for i in link.interfaces: o[i.managed_object] += [i] for mo in sorted(o, key=lambda x: x.name): r["objects"] += [{ "id": mo.id, "name": mo.name, "interfaces": [{ "name": i.name, "description": i.description or None, "status": i.status } for i in sorted(o[mo], key=lambda x: alnum_key(x.name))], }] return r @view( url=r"^objects_statuses/$", method=["POST"], access="read", api=True, validate={"objects": ListOfParameter(IntParameter())}, ) def api_objects_statuses(self, request, objects: List[int]): def get_alarms(objects: List[int]) -> Set[int]: """ Returns a set of objects with alarms """ alarms: Set[int] = set() coll = ActiveAlarm._get_collection() while objects: chunk, objects = objects[:500], objects[500:] a = coll.aggregate([ { "$match": { "managed_object": { "$in": chunk } } }, { "$group": { "_id": "$managed_object", "count": { "$sum": 1 } } }, ]) alarms.update(d["_id"] for d in a) return alarms def get_maintenance(objects: List[int]) -> Set[int]: """ Returns a set of objects currently in maintenance :param objects: :return: """ now = datetime.datetime.now() so = set(objects) mnt_objects = set() pipeline = [ { "$match": { "affected_objects.object": { "$in": list(so) } } }, { "$unwind": "$affected_objects" }, { "$lookup": { "from": "noc.maintenance", "as": "m", "let": { "maintenance": "_id" }, "pipeline": [{ "$match": { "m.is_completed": False, "m.start": { "$lte": now }, "m.stop": { "gte": now }, }, }], }, }, { "$project": { "_id": 0, "object": "$affected_objects.object", } }, { "$group": { "_id": "$object" } }, ] mnt_objects |= so & { x["_id"] for x in AffectedObjects._get_collection().aggregate(pipeline) } return mnt_objects # Mark all as unknown r = {o: self.ST_UNKNOWN for o in objects} sr = ObjectStatus.get_statuses(objects) sa = get_alarms(objects) mo = get_maintenance(objects) for o in sr: if sr[o]: # Check for alarms if o in sa: r[o] = self.ST_ALARM else: r[o] = self.ST_OK else: r[o] = self.ST_DOWN if o in mo: r[o] |= self.ST_MAINTENANCE return r @classmethod @cachedmethod(key="managedobject-name-to-id-%s", lock=lambda _: tags_lock) def managedobject_name_to_id(cls, name): r = ManagedObject.objects.filter(name=name).values_list("id") if r: return r[0][0] return None @classmethod @cachedmethod(key="interface-tags-to-id-%s-%s", lock=lambda _: tags_lock) def interface_tags_to_id(cls, object_name, interface_name): mo = cls.managedobject_name_to_id(object_name) i = Interface._get_collection().find_one({ "managed_object": mo, "name": interface_name }) if i: return i["_id"] return None @view( url=r"^metrics/$", method=["POST"], access="read", api=True, validate={ "metrics": DictListParameter( attrs={ "id": StringParameter(), "metric": StringParameter(), "tags": DictParameter(), }) }, ) def api_metrics(self, request, metrics): def q(s): if isinstance(s, str): s = s.encode("utf-8") return s def qt(t): return "|".join(["%s=%s" % (v, t[v]) for v in sorted(t)]) # Build query tag_id = {} # object, interface -> id if_ids = {} # id -> port id mlst = [] # (metric, object, interface) for m in metrics: if "object" in m["tags"] and "interface" in m["tags"]: if not m["tags"]["object"]: continue try: if_ids[self.interface_tags_to_id( m["tags"]["object"], m["tags"]["interface"])] = m["id"] object = ManagedObject.objects.get( name=m["tags"]["object"]) tag_id[object, m["tags"]["interface"]] = m["id"] mlst += [(m["metric"], object, m["tags"]["interface"])] except KeyError: pass # @todo: Get last values from cache if not mlst: return {} r = {} # Apply interface statuses for d in Interface._get_collection().find( {"_id": { "$in": list(if_ids) }}, { "_id": 1, "admin_status": 1, "oper_status": 1 }): r[if_ids[d["_id"]]] = { "admin_status": d.get("admin_status", True), "oper_status": d.get("oper_status", True), } metric_map, last_ts = get_interface_metrics([m[1] for m in mlst]) # Apply metrics for rq_mo, rq_iface in tag_id: pid = tag_id.get((rq_mo, rq_iface)) if not pid: continue if pid not in r: r[pid] = {} if rq_mo not in metric_map: continue if rq_iface not in metric_map[rq_mo]: continue r[pid]["Interface | Load | In"] = metric_map[rq_mo][rq_iface][ "Interface | Load | In"] r[pid]["Interface | Load | Out"] = metric_map[rq_mo][rq_iface][ "Interface | Load | Out"] return r @view(r"^(?P<id>[0-9a-f]{24})/data/$", method=["DELETE"], access="write", api=True) def api_reset(self, request, id): self.get_object_or_404(NetworkSegment, id=id) MapSettings.objects.filter(segment=id).delete() return {"status": True} @view( url=r"^stp/status/$", method=["POST"], access="read", api=True, validate={"objects": ListOfParameter(IntParameter())}, ) def api_objects_stp_status(self, request, objects): def get_stp_status(object_id): roots = set() blocked = set() object = ManagedObject.get_by_id(object_id) sr = object.scripts.get_spanning_tree() for instance in sr["instances"]: ro = DiscoveryID.find_object(instance["root_id"]) if ro: roots.add(ro) for i in instance["interfaces"]: if i["state"] == "discarding" and i["role"] == "alternate": iface = object.get_interface(i["interface"]) if iface: link = iface.link if link: blocked.add(str(link.id)) return object_id, roots, blocked r = {"roots": [], "blocked": []} futures = [] with ThreadPoolExecutor(max_workers=10) as executor: for o in objects: futures += [executor.submit(get_stp_status, o)] for future in as_completed(futures): try: obj, roots, blocked = future.result() for ro in roots: if ro.id not in r["roots"]: r["roots"] += [ro.id] r["blocked"] += blocked except Exception as e: self.logger.error("[stp] Exception: %s", e) return r
class ObjectListApplication(ExtApplication): """ ManagedObject application """ model = ManagedObject # Default filter by is_managed managed_filter = True def queryset(self, request, query=None): """ Filter records for lookup """ self.logger.info("Queryset %s" % query) if self.managed_filter: q = d_Q(is_managed=True) else: q = d_Q() if not request.user.is_superuser: q &= UserAccess.Q(request.user) if query: sq = ManagedObject.get_search_Q(query) if sq: q &= sq else: q &= d_Q(name__contains=query) return self.model.objects.filter(q) def instance_to_dict(self, o, fields=None): return { "id": str(o.id), "name": o.name, "address": o.address, "profile_name": o.profile.name, "platform": o.platform.name if o.platform else "", "version": o.version.version if o.version else "", "row_class": o.object_profile.style.css_class_name if o.object_profile.style else "" # "row_class": "" } def cleaned_query(self, q): nq = {} for k in q: if not k.startswith("_") and "__" not in k: nq[k] = q[k] ids = set() self.logger.debug("Cleaned query: %s" % nq) if "ids" in nq: ids = {int(nid) for nid in nq["ids"]} del nq["ids"] if "administrative_domain" in nq: ad = AdministrativeDomain.get_nested_ids( int(nq["administrative_domain"])) if ad: del nq["administrative_domain"] nq["administrative_domain__in"] = ad if "selector" in nq: s = self.get_object_or_404(ManagedObjectSelector, id=int(q["selector"])) if s: if ids: # nq["id__in"] = set(ManagedObject.objects.filter(s.Q).values_list("id", flat=True)) ids = ids.intersection( set( ManagedObject.objects.filter(s.Q).values_list( "id", flat=True))) else: ids = set( ManagedObject.objects.filter(s.Q).values_list( "id", flat=True)) del nq["selector"] mq = None c_in = [] c_nin = [] for cc in [part for part in nq if part.startswith("caps")]: """ Caps: caps0=CapsID,caps1=CapsID:true.... cq - caps query mq - main_query caps0=CapsID - caps is exists caps0=!CapsID - caps is not exists caps0=CapsID:true - caps value equal True caps0=CapsID:2~50 - caps value many then 2 and less then 50 c_ids = set(ObjectCapabilities.objects(cq).distinct('object')) """ # @todo Убирать дубликаты (повторно не добавлять) c = nq.pop(cc) if not c: continue if not mq: mq = m_Q() self.logger.info("Caps: %s" % c) 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: 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)} cq = m_Q(__raw__={ "caps": { "$elemMatch": { "capability": c_id, "value": cond } } }) elif c_query in ("false", "true"): cq = m_Q(caps__match={ "capability": c_id, "value": c_query == "true" }) 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) cq = m_Q( __raw__={ "caps": { "$elemMatch": { "capability": c_id, "value": int(c_query) } } }) except ValueError: cq = m_Q( __raw__={ "caps": { "$elemMatch": { "capability": c_id, "value": { "$regex": c_query } } } }) mq &= cq if c_in: mq &= m_Q(caps__capability__in=c_in) if c_nin: mq &= m_Q(caps__capability__nin=c_nin) if mq: c_ids = set(el["_id"] for el in ObjectCapabilities.objects( mq).values_list("object").as_pymongo()) self.logger.info("Caps objects count: %d" % len(c_ids)) ids = ids.intersection(c_ids) if ids else c_ids if "addresses" in nq: if isinstance(nq["addresses"], list): nq["address__in"] = nq["addresses"] else: nq["address__in"] = [nq["addresses"]] del nq["addresses"] if ids: nq["id__in"] = list(ids) xf = list((set(nq.keys())) - set(f.name for f in self.model._meta.get_fields())) # @todo move validation fields for x in xf: if x in ["address__in", "id__in", "administrative_domain__in"]: continue self.logger.warning("Remove element not in model: %s" % x) del nq[x] return nq def extra_query(self, q, order): extra = {"select": {}} if "address" in order: extra["select"]["ex_address"] = " cast_test_to_inet(address) " extra["order_by"] = ["ex_address", "address"] elif "-address" in order: extra["select"]["ex_address"] = " cast_test_to_inet(address) " extra["order_by"] = ["-ex_address", "-address"] self.logger.info("Extra: %s" % extra) return extra, [] if "order_by" in extra else order @view(method=["GET", "POST"], url="^$", access="read", api=True) def api_list(self, request): return self.list_data(request, self.instance_to_dict) @view( method=["POST"], url="^iplist/$", access="launch", api=True, validate={ "query": DictParameter( attrs={ "addresses": ListOfParameter(element=IPv4Parameter(), convert=True) }) }, ) def api_action_ip_list(self, request, query): # @todo Required user vault implementation return self.render_json({"status": True})
class RenderApplication(ExtApplication): """ Graphite-compatible render """ title = "Render" DEFAULT_GRAPH_WIDTH = 330 DEFAULT_GRAPH_HEIGTH = 250 # Empty space around the borders of chart X_PADDING = 10 Y_PADDING = 10 # @view(url="^$", method=["GET"], access="launch", validate={ "graphType": StringParameter( default="line", choices=GraphTypes.keys() ), "pieMode": StringParameter( default="average", # @todo: Specify all modes choices=["average"] ), "cacheTimeout": IntParameter( min_value=0, default=config.getint("pm_render", "cache_duration") ), "target": ListOfParameter( element=StringParameter(), convert=True, default=[] ), "localOnly": StringParameter(default="0"), "tz": StringParameter(default=TIME_ZONE), "pickle": StringParameter(required=False), "rawData": StringParameter(required=False), "jsonp": StringParameter(required=False), "format": StringParameter(required=False), "noCache": StringParameter(required=False), "maxDataPoints": IntParameter(required=False) }, api=True) def api_render(self, request, graphType=None, pieMode=None, cacheTimeout=None, target=None, localOnly=None, tz=None, pickle=None, rawData=None, jsonp=None, noCache=None, format=None, maxDataPoints=None, **kwargs): # Get timezone info try: tz = pytz.timezone(tz) except pytz.UnknownTimeZoneError: tz = pytz.timezone(TIME_ZONE) # Get format if pickle is not None: format = "pickle" elif rawData is not None: format = "raw" # Get time range try: t0 = parseATTime(kwargs.get("from", "-1d")) t1 = parseATTime(kwargs.get("until", "now")) except Exception, why: return self.response_bad_request( "Cannot parse time: %s" % why ) if t0 == t1: return self.response_bad_request("Empty time range") # Collect parameters request_opts = { "graphType": graphType, "graphClass": GraphTypes[graphType], "pieMode": pieMode, "targets": target or [], "localOnly": localOnly == "1", "tzinfo": tz, "format": format, "noCache": noCache is not None, "startTime": min(t0, t1), "endTime": max(t0, t1), "cacheTimeout": cacheTimeout } if format: request_opts["format"] = format if jsonp is not None: request_opts["jsonp"] = jsonp # Fill possible graph options graph_opts = { "width": self.DEFAULT_GRAPH_WIDTH, "height": self.DEFAULT_GRAPH_HEIGTH, } if format == "svg": graph_opts["outputFormat"] = "svg" for opt in request_opts["graphClass"].customizable: if opt in kwargs: v = kwargs[opt] if opt not in ("fgcolor", "bgcolor", "fontColor"): try: graph_opts[opt] = int(v) continue except ValueError: pass try: graph_opts[opt] = float(v) continue except ValueError: pass if v.lower() in ("true", "false"): graph_opts[opt] = v.lower() == "true" continue if not v or v.lower() == "default": continue graph_opts[opt] = v use_cache = not request_opts["noCache"] cache_timeout = request_opts["cacheTimeout"] ctx = { "startTime": request_opts["startTime"], "endTime": request_opts["endTime"], "localOnly": request_opts["localOnly"], "maxDataPoints": maxDataPoints, "data": [] } data = ctx["data"] # Try to use cached response if use_cache: request_key = hashRequest(request) cached_response = cache.get(request_key) if cached_response: return cached_response else: request_opts["requestKey"] = request_key # Cache miss, prepare requested data if graphType == "pie": for t in request_opts["targets"]: if ":" in t: try: name, value = t.split(":", 1) data += [(name, float(value))] except ValueError: raise ValueError("Invalid target: '%s'" % t) else: for series in evaluateTarget(ctx, t): f = PieFunctions(request_opts["pieMode"]) data += [(series.name, f(ctx, series) or 0)] elif graphType == "line": if use_cache: # Store cached data data_key = hashData(request_opts["targets"], request_opts["startTime"], request_opts["endTime"]) cached_data = cache.get(data_key) else: cached_data = None if cached_data is None: for t in request_opts["targets"]: if not t.strip(): continue data.extend(evaluateTarget(ctx, t)) if use_cache: cache.set( data_key, [d.get_info() for d in data], cache_timeout ) else: # Convert cached data to Time Series data = [TimeSeries(**a) for a in cached_data] # Return data in requested format h = getattr(self, "get_%s_response" % request_opts["format"], None) if h: r = h(data, request_opts) else: graph_opts["data"] = data r = self.render_graph(request_opts, graph_opts) r["Pragma"] = "no-cache" r["Cache-Control"] = "no-cache" return r