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"
class AuthProfileApplication(ExtModelApplication): """ AuthProfile application """ title = _("Auth Profile") menu = [_("Setup"), _("Auth Profiles")] model = AuthProfile suggest_snmp = ModelInline(AuthProfileSuggestSNMP) suggest_cli = ModelInline(AuthProfileSuggestCLI)
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 ManagedObjectSelectorApplication(ExtModelApplication): """ ManagedObjectSelector application """ title = _("Managed Object Selector") menu = [_("Setup"), _("Managed Object Selector")] model = ManagedObjectSelector query_fields = ["name__icontains", "description__icontains"] attrs = ModelInline(ManagedObjectSelectorByAttribute) def field_expression(self, o): return o.expr def cleaned_query(self, q): if q.get("id__referred") == "sa.managedobject__selector": del q["id__referred"] return super(ManagedObjectSelectorApplication, self).cleaned_query(q) @view(url=r"(?P<id>\d+)/objects/", method=["GET"], access="read", api=True) def api_test(self, request, id): o = self.get_object_or_404(ManagedObjectSelector, id=int(id)) return [{ "id": mo.id, "name": mo.name, "is_managed": mo.is_managed, "profile": mo.profile.name, "platform": mo.platform.name if mo.platform else "", "administrative_domain": smart_text(mo.administrative_domain), "address": mo.address, "description": mo.description, "tags": mo.tags, } for mo in o.managed_objects]
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 ManagedObjectSelectorApplication(ExtModelApplication): """ ManagedObjectSelector application """ title = "Managed Object Selector" menu = "Setup | Managed Object Selector" model = ManagedObjectSelector query_fields = ["name__icontains", "description__icontains"] attrs = ModelInline(ManagedObjectSelectorByAttribute) def field_expression(self, o): return o.expr @view(url="(?P<id>\d+)/objects/", method=["GET"], access="read", api=True) def api_test(self, request, id): o = self.get_object_or_404(ManagedObjectSelector, id=int(id)) return [ { "id": mo.id, "name": mo.name, "is_managed": mo.is_managed, "profile": mo.profile_name, "platform": mo.platform, "administrative_domain": unicode(mo.administrative_domain), "address": mo.address, "description": mo.description, "tags": mo.tags } for mo in o.managed_objects ]
class CustomFieldEnumGroupApplication(ExtModelApplication): """ CustomFieldEnumGroup application """ title = _("Enum Groups") menu = [_("Setup"), _("Enum Groups")] model = CustomFieldEnumGroup values = ModelInline(CustomFieldEnumValue)
class CustomFieldEnumGroupApplication(ExtModelApplication): """ CustomFieldEnumGroup application """ title = "Enum Groups" menu = "Setup | Enum Groups" model = CustomFieldEnumGroup values = ModelInline(CustomFieldEnumValue)
class DNSZoneApplication(ExtModelApplication): """ DNSZone application """ title = _("DNS Zone") menu = _("Zones") model = DNSZone query_condition = "icontains" records = ModelInline(DNSZoneRecord) zone = RepoInline("zone")
class RefBookAdminApplication(ExtModelApplication): """ RefBook application """ model = RefBook title = _("Reference Books") menu = _("Setup") + " | " + _("Reference Books") default_ordering = ["name"] fields = ModelInline(RefBookField)
class DNSZoneApplication(ExtModelApplication): """ DNSZone application """ title = "DNS Zone" menu = "Zones" model = DNSZone query_condition = "icontains" records = ModelInline(DNSZoneRecord) zone = RepoInline("zone") @view(url="^(?P<zone_id>\d+)/text/$", method=["GET"], access="read", api=True) def api_text(self, request, zone_id): zone = self.get_object_or_404(DNSZone, id=int(zone_id)) return zone.get_zone_text()
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 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)