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 init_plugin(self): super().init_plugin() self.add_view( "api_plugin_%s_set_contacts" % self.name, self.api_set_contacts, url="^(?P<id>[0-9a-f]{24})/plugin/%s/$" % self.name, method=["POST"], validate={ "administrative": UnicodeParameter(), "billing": UnicodeParameter(), "technical": UnicodeParameter(), }, )
def init_plugin(self): super(CommentPlugin, self).init_plugin() self.add_view("api_plugin_%s_set_comment" % self.name, self.api_set_comment, url="^(?P<id>[0-9a-f]{24})/plugin/%s/$" % self.name, method=["POST"], validate={"comment": UnicodeParameter()})
class SearchApplication(ExtApplication): """ main.search application """ title = _("Search") menu = _("Search") glyph = "search noc-preview" @view(url="^$", method=["POST"], access="launch", api=True, validate={"query": UnicodeParameter()}) def api_search(self, request, query): r = [] for qr in TextIndex.search(query): model = get_model(qr["model"]) if not model: continue # Invalid model url = model.get_search_result_url(qr["object"]) r += [{ "title": str(qr["title"]), "card": str(qr["card"]), "tags:": [str(x) for x in (qr.get("tags", []) or [])], "url": url, "score": qr["score"] }] return r
def test_unicode_parameter(): assert UnicodeParameter().clean(u"Test") == u"Test" assert UnicodeParameter().clean(10) == u"10" assert UnicodeParameter().clean(None) == u"None" assert UnicodeParameter(default=u"test").clean(u"no test") == u"no test" assert UnicodeParameter(default=u"test").clean(None) == u"test" assert UnicodeParameter(choices=[u"1", u"2"]).clean(u"1") == u"1" with pytest.raises(InterfaceTypeError): assert UnicodeParameter(choices=[u"1", u"2"]).clean(u"3")
def init_plugin(self): super(DataPlugin, self).init_plugin() self.add_view("api_plugin_%s_save_data" % self.name, self.api_save_data, url="^(?P<id>[0-9a-f]{24})/plugin/data/$", method=["PUT"], validate={ "interface": StringParameter(), "key": StringParameter(), "value": UnicodeParameter() })
class PrefixListBuilderApplication(ExtApplication): """ Interactive prefix list builder """ title = "Prefix List Builder" menu = "Prefix List Builder" # implied_permissions = { # "read": ["peer:peeringpoint:lookup"] #} @view(method=["GET"], url=r"^$", access="read", api=True, validate={ "peering_point": ModelParameter(PeeringPoint), "name": UnicodeParameter(required=False), "as_set": UnicodeParameter() }) def api_list(self, request, peering_point, name, as_set): prefixes = WhoisCache.resolve_as_set_prefixes_maxlen(as_set) pl = peering_point.profile.generate_prefix_list(name, prefixes) return {"name": name, "prefix_list": pl, "success": True}
def init_plugin(self): super().init_plugin() self.add_view( "api_plugin_%s_get_layer" % self.name, self.api_get_layer, url=r"^plugin/%s/layers/(?P<layer>\S+)/$" % self.name, method=["GET"], ) self.add_view( "api_plugin_%s_object_data" % self.name, self.api_object_data, url="^(?P<id>[0-9a-f]{24})/plugin/%s/object_data/$" % self.name, method=["GET"], ) self.add_view( "api_plugin_%s_set_geopoint" % self.name, self.api_set_geopoint, url="^(?P<id>[0-9a-f]{24})/plugin/%s/set_geopoint/$" % self.name, method=["POST"], validate={ "srid": StringParameter(), "x": FloatParameter(), "y": FloatParameter() }, ) self.add_view( "api_plugin_%s_set_layer_visibility" % self.name, self.api_set_layer_visibility, url="^plugin/%s/layer_visibility/$" % self.name, method=["POST"], validate={ "layer": StringParameter(), "status": BooleanParameter() }, ) self.add_view( "api_plugin_%s_create" % self.name, self.api_create, url="^plugin/%s/$" % self.name, method=["POST"], validate={ "model": DocumentParameter(ObjectModel), "name": UnicodeParameter(), "srid": StringParameter(), "x": FloatParameter(), "y": FloatParameter(), }, )
class SearchApplication(ExtApplication): """ main.search application """ title = "Search" menu = "Search" INDEX = "local/index" LIMIT = 1000 glyph = "search noc-preview" @view(url="^$", method=["POST"], access="launch", api=True, validate={ "query": UnicodeParameter() }) def api_search(self, request, query): user = request.user index = open_dir(self.INDEX, readonly=True) parser = QueryParser("content", index.schema) r = [] q = parser.parse(query) with index.searcher() as searcher: for hit in searcher.search(q, limit=self.LIMIT): o = FTSQueue.get_object(hit["id"]) if not o: continue # Not found in database li = o.get_search_info(user) if not li: continue # Not accessible for user r += [{ "id": hit["id"], "title": hit["title"], "card": hit["card"], "tags": hit.get("tags"), "info": li }] return r
class EventApplication(ExtApplication): """ fm.event application """ title = _("Events") menu = _("Events") icon = "icon_find" model_map = {"A": ActiveEvent, "F": FailedEvent, "S": ArchivedEvent} clean_fields = { "managed_object": ModelParameter(ManagedObject), "timestamp": DateTimeParameter(), } ignored_params = ["status", "_dc"] def __init__(self, *args, **kwargs): ExtApplication.__init__(self, *args, **kwargs) from .plugins.base import EventPlugin # Load plugins self.plugins = {} for f in os.listdir("services/web/apps/fm/event/plugins/"): if not f.endswith(".py") or f == "base.py" or f.startswith("_"): continue mn = "noc.services.web.apps.fm.event.plugins.%s" % f[:-3] m = __import__(mn, {}, {}, "*") for on in dir(m): o = getattr(m, on) if ( inspect.isclass(o) and issubclass(o, EventPlugin) and o.__module__.startswith(mn) ): assert o.name self.plugins[o.name] = o(self) def cleaned_query(self, q): q = q.copy() for p in self.ignored_params: if p in q: del q[p] for p in ( self.limit_param, self.page_param, self.start_param, self.format_param, self.sort_param, self.query_param, self.only_param, ): if p in q: del q[p] # Normalize parameters for p in q: qp = p.split("__")[0] if qp in self.clean_fields: q[p] = self.clean_fields[qp].clean(q[p]) if "administrative_domain" in q: a = AdministrativeDomain.objects.get(id=q["administrative_domain"]) q["managed_object__in"] = a.managedobject_set.values_list("id", flat=True) q.pop("administrative_domain") if "managedobjectselector" in q: s = SelectorCache.objects.filter(selector=q["managedobjectselector"]).values_list( "object" ) if "managed_object__in" in q: q["managed_object__in"] = list(set(q["managed_object__in"]).intersection(s)) else: q["managed_object__in"] = s q.pop("managedobjectselector") return q def instance_to_dict(self, o, fields=None): row_class = None if o.status in ("A", "S"): subject = o.subject repeats = o.repeats duration = o.duration n_alarms = len(o.alarms) if n_alarms: row_class = AlarmSeverity.get_severity_css_class_name(get_severity(o.alarms)) else: subject = None repeats = None duration = None n_alarms = None d = { "id": str(o.id), "status": o.status, "managed_object": o.managed_object.id, "managed_object__label": o.managed_object.name, "administrative_domain": o.managed_object.administrative_domain_id, "administrative_domain__label": o.managed_object.administrative_domain.name, "event_class": str(o.event_class.id) if o.status in ("A", "S") else None, "event_class__label": o.event_class.name if o.status in ("A", "S") else None, "timestamp": self.to_json(o.timestamp), "subject": subject, "repeats": repeats, "duration": duration, "alarms": n_alarms, "row_class": row_class, } if fields: d = dict((k, d[k]) for k in fields) return d def queryset(self, request, query=None): """ Filter records for lookup """ status = request.GET.get("status", "A") if status not in self.model_map: raise Exception("Invalid status") model = self.model_map[status] return model.objects @view(url=r"^$", access="launch", method=["GET"], api=True) def api_list(self, request): return self.list_data(request, self.instance_to_dict) @view(url=r"^(?P<id>[a-z0-9]{24})/$", method=["GET"], api=True, access="launch") def api_event(self, request, id): event = get_event(id) if not event: return self.response_not_found() d = self.instance_to_dict(event) dd = dict( (v, None) for v in ( "body", "symptoms", "probable_causes", "recommended_actions", "log", "vars", "resolved_vars", "raw_vars", ) ) if event.status in ("A", "S"): dd["body"] = event.body dd["symptoms"] = event.event_class.symptoms dd["probable_causes"] = event.event_class.probable_causes dd["recommended_actions"] = event.event_class.recommended_actions # Fill vars left = set(event.vars) vars = [] for ev in event.event_class.vars: if ev.name in event.vars: vars += [(ev.name, event.vars[ev.name], ev.description)] left.remove(ev.name) vars += [(v, event.vars[v], None) for v in sorted(left)] dd["vars"] = vars # Fill resolved vars vars = [] is_trap = event.raw_vars.get("source") == "SNMP Trap" for v in sorted(event.resolved_vars): desc = None if is_trap and "::" in v: desc = MIB.get_description(v) vars += [(v, event.resolved_vars[v], desc)] dd["resolved_vars"] = vars dd["raw_vars"] = sorted(event.raw_vars.items()) # Managed object properties mo = event.managed_object d["managed_object_address"] = mo.address d["managed_object_profile"] = mo.profile.name d["managed_object_platform"] = mo.platform.name if mo.platform else "" d["managed_object_version"] = mo.version.version if mo.version else "" d["segment"] = mo.segment.name d["segment_id"] = str(mo.segment.id) d["tags"] = mo.tags # Log if event.log: dd["log"] = [ { "timestamp": self.to_json(l.timestamp), "from_status": l.from_status, "to_status": l.to_status, "message": l.message, } for l in event.log ] # d.update(dd) # Get alarms if event.status in ("A", "S"): alarms = [] for a_id in event.alarms: a = get_alarm(a_id) if not a: continue if a.opening_event == event.id: role = "O" elif a.closing_event == event.id: role = "C" else: role = "" alarms += [ { "id": str(a.id), "status": a.status, "alarm_class": str(a.alarm_class.id), "alarm_class__label": a.alarm_class.name, "subject": a.subject, "role": role, "timestamp": self.to_json(a.timestamp), } ] d["alarms"] = alarms # Apply plugins if event.status in ("A", "S") and event.event_class.plugins: plugins = [] for p in event.event_class.plugins: if p.name in self.plugins: plugin = self.plugins[p.name] dd = plugin.get_data(event, p.config) if "plugins" in dd: plugins += dd["plugins"] del dd["plugins"] d.update(dd) if plugins: d["plugins"] = plugins elif event.status == "F": # Enable traceback plugin for failed events d["traceback"] = event.traceback d["plugins"] = [("NOC.fm.event.plugins.Traceback", {})] return d @view( url=r"^(?P<id>[a-z0-9]{24})/post/", method=["POST"], api=True, access="launch", validate={"msg": UnicodeParameter()}, ) def api_post(self, request, id, msg): event = get_event(id) if not event: self.response_not_found() event.log_message("%s: %s" % (request.user.username, msg)) return True rx_parse_log = re.compile("^Classified as '(.+?)'.+$") @view(url=r"^(?P<id>[a-z0-9]{24})/json/$", method=["GET"], api=True, access="launch") def api_json(self, request, id): event = get_event(id) if not event: self.response_not_found() # Get event class e_class = None if event.status in ("A", "S"): for l in event.log: match = self.rx_parse_log.match(l.message) if match: e_class = match.group(1) r = ["["] r += [" {"] r += [' "profile": "%s",' % json_escape(event.managed_object.profile.name)] if e_class: r += [' "event_class__name": "%s",' % e_class] r += [' "raw_vars": {'] rr = [] for k in event.raw_vars: if k in ("collector", "severity", "facility"): continue rr += [' "%s": "%s"' % (json_escape(k), json_escape(str(event.raw_vars[k])))] r += [",\n".join(rr)] r += [" }"] r += [" }"] r += ["]"] return "\n".join(r) @view(url=r"^(?P<id>[a-z0-9]{24})/reclassify/$", method=["POST"], api=True, access="launch") def api_reclassify(self, request, id): event = get_event(id) if not event: self.response_not_found() if event.status == "N": return False event.mark_as_new( "Event reclassification has been requested " "by user %s" % request.user.username ) return True
class AlarmApplication(ExtApplication): """ fm.alarm application """ title = "Alarm" menu = "Alarms" glyph = "exclamation-triangle" implied_permissions = { "launch": ["sa:managedobject:alarm"] } model_map = { "A": ActiveAlarm, "C": ArchivedAlarm } clean_fields = { "managed_object": ModelParameter(ManagedObject), "timestamp": DateTimeParameter() } ignored_params = ["status", "_dc"] def __init__(self, *args, **kwargs): ExtApplication.__init__(self, *args, **kwargs) from plugins.base import AlarmPlugin # Load plugins self.plugins = {} for f in os.listdir("fm/apps/alarm/plugins/"): if (not f.endswith(".py") or f == "base.py" or f.startswith("_")): continue mn = "noc.fm.apps.alarm.plugins.%s" % f[:-3] m = __import__(mn, {}, {}, "*") for on in dir(m): o = getattr(m, on) if (inspect.isclass(o) and issubclass(o, AlarmPlugin) and o.__module__.startswith(mn)): assert o.name self.plugins[o.name] = o(self) def cleaned_query(self, q): q = q.copy() for p in self.ignored_params: if p in q: del q[p] for p in ( self.limit_param, self.page_param, self.start_param, self.format_param, self.sort_param, self.query_param, self.only_param): if p in q: del q[p] # Normalize parameters for p in q: qp = p.split("__")[0] if qp in self.clean_fields: q[p] = self.clean_fields[qp].clean(q[p]) if "administrative_domain" in q: a = AdministrativeDomain.objects.get(id = q["administrative_domain"]) q["managed_object__in"] = a.managedobject_set.values_list("id", flat=True) q.pop("administrative_domain") if "managedobjectselector" in q: s = SelectorCache.objects.filter(selector = q["managedobjectselector"]).values_list("object") if "managed_object__in" in q: q["managed_object__in"] = list(set(q["managed_object__in"]).intersection(s)) else: q["managed_object__in"] = s q.pop("managedobjectselector") # if "collapse" in q: c = q["collapse"] del q["collapse"] if c != "0": q["root__exists"] = False return q def instance_to_dict(self, o, fields=None): s = AlarmSeverity.get_severity(o.severity) n_events = (ActiveEvent.objects.filter(alarms=o.id).count() + ArchivedEvent.objects.filter(alarms=o.id).count()) d = { "id": str(o.id), "status": o.status, "managed_object": o.managed_object.id, "managed_object__label": o.managed_object.name, "administrative_domain": o.managed_object.administrative_domain_id, "administrative_domain__label": o.managed_object.administrative_domain.name, "severity": o.severity, "severity__label": s.name, "alarm_class": str(o.alarm_class.id), "alarm_class__label": o.alarm_class.name, "timestamp": self.to_json(o.timestamp), "subject": o.subject, "events": n_events, "duration": o.duration, "row_class": s.style.css_class_name } if fields: d = dict((k, d[k]) for k in fields) return d def queryset(self, request, query=None): """ Filter records for lookup """ status = request.GET.get("status", "A") if status not in self.model_map: raise Exception("Invalid status") model = self.model_map[status] return model.objects.all() @view(url=r"^$", access="launch", method=["GET"], api=True) def api_list(self, request): return self.list_data(request, self.instance_to_dict) @view(url=r"^(?P<id>[a-z0-9]{24})/$", method=["GET"], api=True, access="launch") def api_alarm(self, request, id): alarm = get_alarm(id) if not alarm: self.response_not_found() user = request.user lang = "en" d = self.instance_to_dict(alarm) d["body"] = alarm.body d["symptoms"] = alarm.alarm_class.symptoms d["probable_causes"] = alarm.alarm_class.probable_causes d["recommended_actions"] = alarm.alarm_class.recommended_actions d["vars"] = sorted(alarm.vars.items()) d["status"] = alarm.status d["status__label"] = { "A": "Active", "C": "Cleared" }[alarm.status] # Managed object properties mo = alarm.managed_object d["managed_object_address"] = mo.address d["managed_object_profile"] = mo.profile_name d["managed_object_platform"] = mo.platform d["managed_object_version"] = mo.get_attr("version") # Log if alarm.log: d["log"] = [ { "timestamp": self.to_json(l.timestamp), "from_status": l.from_status, "to_status": l.to_status, "message": l.message } for l in alarm.log ] # Events events = [] for ec in ActiveEvent, ArchivedEvent: for e in ec.objects.filter(alarms=alarm.id): events += [{ "id": str(e.id), "event_class": str(e.event_class.id), "event_class__label": e.event_class.name, "timestamp": self.to_json(e.timestamp), "status": e.status, "managed_object": e.managed_object.id, "managed_object__label": e.managed_object.name, "subject": e.subject }] if events: d["events"] = events # Alarms children = self.get_nested_alarms(alarm) if children: d["alarms"] = { "expanded": True, "children": children } # Subscribers if alarm.status == "A": d["subscribers"] = self.get_alarm_subscribers(alarm) d["is_subscribed"] = user in alarm.subscribers # Apply plugins if alarm.alarm_class.plugins: plugins = [] for p in alarm.alarm_class.plugins: if p.name in self.plugins: plugin = self.plugins[p.name] dd = plugin.get_data(alarm, p.config) if "plugins" in dd: plugins += dd["plugins"] del dd["plugins"] d.update(dd) if plugins: d["plugins"] = plugins return d def get_alarm_subscribers(self, alarm): """ JSON-serializable subscribers :param alarm: :return: """ subscribers = [] for u in alarm.subscribers: try: u = User.objects.get(id=u) subscribers += [{ "id": u.id, "name": " ".join([u.first_name, u.last_name]), "login": u.username }] except User.DoesNotExist: pass return subscribers def get_nested_alarms(self, alarm): """ Return nested alarms as a part of NodeInterface :param alarm: :return: """ children = [] for ac in (ActiveAlarm, ArchivedAlarm): for a in ac.objects.filter(root=alarm.id): s = AlarmSeverity.get_severity(a.severity) c = { "id": str(a.id), "subject": a.subject, "alarm_class": str(a.alarm_class.id), "alarm_class__label": a.alarm_class.name, "managed_object": a.managed_object.id, "managed_object__label": a.managed_object.name, "timestamp": self.to_json(a.timestamp), "iconCls": "icon_error", "row_class": s.style.css_class_name } nc = self.get_nested_alarms(a) if nc: c["children"] = nc c["expanded"] = True else: c["leaf"] = True children += [c] return children @view(url=r"^(?P<id>[a-z0-9]{24})/post/", method=["POST"], api=True, access="launch", validate={"msg": UnicodeParameter()}) def api_post(self, request, id, msg): alarm = get_alarm(id) if not alarm: self.response_not_found() alarm.log_message("%s: %s" % (request.user.username, msg)) return True @view(url=r"^(?P<id>[a-z0-9]{24})/subscribe/", method=["POST"], api=True, access="launch") def api_subscribe(self, request, id): alarm = get_alarm(id) if not alarm: return self.response_not_found() if alarm.status == "A": alarm.subscribe(request.user) return self.get_alarm_subscribers(alarm) else: return [] @view(url=r"^(?P<id>[a-z0-9]{24})/unsubscribe/", method=["POST"], api=True, access="launch") def api_unsubscribe(self, request, id): alarm = get_alarm(id) if not alarm: return self.response_not_found() if alarm.status == "A": alarm.unsubscribe(request.user) return self.get_alarm_subscribers(alarm) else: return [] @view(url=r"^(?P<id>[a-z0-9]{24})/clear/", method=["POST"], api=True, access="launch") def api_clear(self, request, id): alarm = get_alarm(id) if alarm.status == "A": alarm.clear_alarm("Cleared by %s" % request.user) return True @view(url=r"^(?P<id>[a-z0-9]{24})/set_root/", method=["POST"], api=True, access="launch", validate={"root": StringParameter()}) def api_set_root(self, request, id, root): alarm = get_alarm(id) r = get_alarm(root) if not r: return self.response_not_found() alarm.set_root(r) return True
class 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 AlarmApplication(ExtApplication): """ fm.alarm application """ title = _("Alarm") menu = _("Alarms") glyph = "exclamation-triangle" implied_permissions = {"launch": ["sa:managedobject:alarm"]} model_map = {"A": ActiveAlarm, "C": ArchivedAlarm} clean_fields = { "managed_object": ModelParameter(ManagedObject), "timestamp": DateTimeParameter(), } ignored_params = ["status", "_dc"] diagnostic_plugin = AlarmPlugin(name="diagnostic", config={}) advanced_filter_params = { "service_profile": "total_services", "subscribers_profile": "total_subscribers", "profile": get_advanced_field, } DEFAULT_ARCH_ALARM = datetime.timedelta( seconds=config.web.api_arch_alarm_limit) rx_oper_splitter = re.compile(r"^(?P<field>\S+)(?P<f_num>\d+)__in") def __init__(self, *args, **kwargs): ExtApplication.__init__(self, *args, **kwargs) from .plugins.base import AlarmPlugin # Load plugins self.plugins = {} for f in os.listdir("services/web/apps/fm/alarm/plugins/"): if not f.endswith(".py") or f == "base.py" or f.startswith("_"): continue mn = "noc.services.web.apps.fm.alarm.plugins.%s" % f[:-3] m = __import__(mn, {}, {}, "*") for on in dir(m): o = getattr(m, on) if (inspect.isclass(o) and issubclass(o, AlarmPlugin) and o.__module__.startswith(mn)): assert o.name self.plugins[o.name] = o(self) def cleaned_query(self, q): q = q.copy() status = q["status"] if "status" in q else "A" for p in self.ignored_params: if p in q: del q[p] for p in ( self.limit_param, self.page_param, self.start_param, self.format_param, self.sort_param, self.query_param, self.only_param, ): if p in q: del q[p] # Extract IN # extjs not working with same parameter name in query for p in list(q): if p.endswith("__in") and self.rx_oper_splitter.match(p): field = self.rx_oper_splitter.match(p).group("field") + "__in" if field not in q: q[field] = [q[p]] else: q[field] += [q[p]] del q[p] # Normalize parameters for p in list(q): qp = p.split("__")[0] if qp in self.clean_fields: q[p] = self.clean_fields[qp].clean(q[p]) # Advanced filter for p in self.advanced_filter_params: params = [] for x in list(q): if x.startswith(p): params += [q[x]] del q[x] if params: af = self.advanced_filter(self.advanced_filter_params[p], params) if "__raw__" in q and "__raw__" in af: # Multiple raw query q["__raw__"].update(af["__raw__"]) del af["__raw__"] q.update(af) # Exclude maintenance if "maintenance" not in q: q["maintenance"] = "hide" if q["maintenance"] == "hide" and status == "A": q["managed_object__nin"] = Maintenance.currently_affected() elif q["maintenance"] == "only" and status == "A": q["managed_object__in"] = Maintenance.currently_affected() del q["maintenance"] if "administrative_domain" in q: if q["administrative_domain"] != "_root_": q["adm_path"] = int(q["administrative_domain"]) q.pop("administrative_domain") if "administrative_domain__in" in q: if "_root_" not in q["administrative_domain__in"]: q["adm_path__in"] = q["administrative_domain__in"] q.pop("administrative_domain__in") if "segment" in q: if q["segment"] != "_root_": q["segment_path"] = bson.ObjectId(q["segment"]) q.pop("segment") if "managedobjectselector" in q: s = SelectorCache.objects.filter( selector=q["managedobjectselector"]).values_list("object") if "managed_object__in" in q: q["managed_object__in"] = list( set(q["managed_object__in"]).intersection(s)) else: q["managed_object__in"] = s q.pop("managedobjectselector") if "cleared_after" in q: q["clear_timestamp__gte"] = datetime.datetime.now( ) - datetime.timedelta(seconds=int(q["cleared_after"])) q.pop("cleared_after") # if "wait_tt" in q: q["wait_tt__exists"] = True q["wait_ts__exists"] = False del q["wait_tt"] # if "collapse" in q: c = q["collapse"] del q["collapse"] if c != "0": q["root__exists"] = False if status == "C": if ("timestamp__gte" not in q and "timestamp__lte" not in q and "escalation_tt__contains" not in q and "managed_object" not in q): q["timestamp__gte"] = datetime.datetime.now( ) - self.DEFAULT_ARCH_ALARM return q def advanced_filter(self, field, params): """ Field: field0=ProfileID,field1=ProfileID:true.... cq - caps query mq - main_query field0=ProfileID - Profile is exists field0=!ProfileID - Profile is not exists field0=ProfileID:true - Summary value equal True field0=ProfileID:2~50 - Summary value many then 2 and less then 50 :param field: Query Field name :param params: Query params :return: """ q = {} c_in = [] c_nin = [] for c in params: if not c: continue if "!" in c: # @todo Добавить исключение (только этот) !ID c_id = c[1:] c_query = "nexists" elif ":" not in c: c_id = c c_query = "exists" else: c_id, c_query = c.split(":", 1) try: if callable(field): field, c_id = field(c_id) c_id = bson.ObjectId(c_id) except bson.errors.InvalidId as e: self.logger.warning(e) continue if "~" in c_query: l, r = c_query.split("~") if not l: cond = {"$lte": int(r)} elif not r: cond = {"$gte": int(l)} else: cond = {"$lte": int(r), "$gte": int(l)} q["__raw__"] = { field: { "$elemMatch": { "profile": c_id, "summary": cond } } } elif c_query == "exists": c_in += [c_id] continue elif c_query == "nexists": c_nin += [c_id] continue else: try: c_query = int(c_query) q["__raw__"] = { field: { "$elemMatch": { "profile": c_id, "summary": int(c_query) } } } except ValueError: q["__raw__"] = { field: { "$elemMatch": { "profile": c_id, "summary": { "$regex": c_query } } } } if c_in: q["%s__profile__in" % field] = c_in if c_nin: q["%s__profile__nin" % field] = c_nin return q def instance_to_dict(self, o, fields=None): s = AlarmSeverity.get_severity(o.severity) n_events = (ActiveEvent.objects.filter(alarms=o.id).count() + ArchivedEvent.objects.filter(alarms=o.id).count()) d = { "id": str(o.id), "status": o.status, "managed_object": o.managed_object.id, "managed_object__label": o.managed_object.name, "administrative_domain": o.managed_object.administrative_domain_id, "administrative_domain__label": o.managed_object.administrative_domain.name, "severity": o.severity, "severity__label": s.name, "alarm_class": str(o.alarm_class.id), "alarm_class__label": o.alarm_class.name, "timestamp": self.to_json(o.timestamp), "subject": o.subject, "events": n_events, "duration": o.duration, "clear_timestamp": self.to_json(o.clear_timestamp) if o.status == "C" else None, "row_class": s.style.css_class_name, "segment__label": o.managed_object.segment.name, "segment": str(o.managed_object.segment.id), "location_1": self.location(o.managed_object.container.id)[0] if o.managed_object.container else "", "location_2": self.location(o.managed_object.container.id)[1] if o.managed_object.container else "", "escalation_tt": o.escalation_tt, "escalation_error": o.escalation_error, "platform": o.managed_object.platform.name if o.managed_object.platform else "", "address": o.managed_object.address, "ack_ts": self.to_json(o.ack_ts), "ack_user": o.ack_user, "summary": self.f_glyph_summary({ "subscriber": SummaryItem.items_to_dict(o.total_subscribers), "service": SummaryItem.items_to_dict(o.total_services), }), "total_objects": sum(x.summary for x in o.total_objects), "total_subscribers": self.f_summary( {"subscriber": SummaryItem.items_to_dict(o.total_subscribers)}), "total_services": self.f_summary( {"service": SummaryItem.items_to_dict(o.total_services)}), "logs": [{ "timestamp": self.to_json(ll.timestamp), "user": ll.source or "NOC", "message": ll.message, } for ll in o.log if getattr(ll, "source", None) ][:config.web.api_alarm_comments_limit], } if fields: d = {k: d[k] for k in fields} return d def queryset(self, request, query=None): """ Filter records for lookup """ status = request.GET.get("status", "A") if status not in self.model_map: raise Exception("Invalid status") model = self.model_map[status] if request.user.is_superuser: return model.objects.filter().read_preference( ReadPreference.SECONDARY_PREFERRED).all() else: return model.objects.filter(adm_path__in=UserAccess.get_domains( request.user), ).read_preference( ReadPreference.SECONDARY_PREFERRED) @view(url=r"^$", access="launch", method=["GET"], api=True) def api_list(self, request): return self.list_data(request, self.instance_to_dict) @view(url=r"^(?P<id>[a-z0-9]{24})/$", method=["GET"], api=True, access="launch") def api_alarm(self, request, id): alarm = get_alarm(id) if not alarm: self.response_not_found() user = request.user d = self.instance_to_dict(alarm) d["body"] = alarm.body d["symptoms"] = alarm.alarm_class.symptoms d["probable_causes"] = alarm.alarm_class.probable_causes d["recommended_actions"] = alarm.alarm_class.recommended_actions d["vars"] = sorted(alarm.vars.items()) d["status"] = alarm.status d["status__label"] = {"A": "Active", "C": "Cleared"}[alarm.status] # Managed object properties mo = alarm.managed_object d["managed_object_address"] = mo.address d["managed_object_profile"] = mo.profile.name d["managed_object_platform"] = mo.platform.name if mo.platform else "" d["managed_object_version"] = mo.version.version if mo.version else "" d["segment"] = mo.segment.name d["segment_id"] = str(mo.segment.id) d["segment_path"] = " | ".join( NetworkSegment.get_by_id(p).name for p in NetworkSegment.get_path(mo.segment)) if mo.container: cp = [] c = mo.container.id while c: try: o = Object.objects.get(id=c) if o.container: cp.insert(0, o.name) c = o.container.id if o.container else None except DoesNotExist: break d["container_path"] = " | ".join(cp) if not self.location(mo.container.id)[0]: d["address_path"] = None else: d["address_path"] = ", ".join(self.location(mo.container.id)) d["tags"] = mo.labels # Log if alarm.log: d["log"] = [{ "timestamp": self.to_json(ll.timestamp), "from_status": ll.from_status, "to_status": ll.to_status, "source": getattr(ll, "source", ""), "message": ll.message, } for ll in alarm.log] # Events events = [] for ec in ActiveEvent, ArchivedEvent: for e in ec.objects.filter(alarms=alarm.id): events += [{ "id": str(e.id), "event_class": str(e.event_class.id), "event_class__label": e.event_class.name, "timestamp": self.to_json(e.timestamp), "status": e.status, "managed_object": e.managed_object.id, "managed_object__label": e.managed_object.name, "subject": e.subject, }] if events: d["events"] = events # Alarms children = self.get_nested_alarms(alarm) if children: d["alarms"] = {"expanded": True, "children": children} # Subscribers if alarm.status == "A": d["subscribers"] = self.get_alarm_subscribers(alarm) d["is_subscribed"] = user in alarm.subscribers # Apply plugins plugins = [] acp = alarm.alarm_class.plugins or [] acp += [self.diagnostic_plugin] for p in acp: if p.name in self.plugins: plugin = self.plugins[p.name] dd = plugin.get_data(alarm, p.config) if "plugins" in dd: plugins += dd["plugins"] del dd["plugins"] d.update(dd) if plugins: d["plugins"] = plugins return d def get_alarm_subscribers(self, alarm): """ JSON-serializable subscribers :param alarm: :return: """ subscribers = [] for u in alarm.subscribers: try: u = User.objects.get(id=u) subscribers += [{ "id": u.id, "name": " ".join([u.first_name, u.last_name]), "login": u.username }] except User.DoesNotExist: pass return subscribers def get_nested_alarms(self, alarm): """ Return nested alarms as a part of NodeInterface :param alarm: :return: """ children = [] for ac in (ActiveAlarm, ArchivedAlarm): for a in ac.objects.filter(root=alarm.id): s = AlarmSeverity.get_severity(a.severity) c = { "id": str(a.id), "subject": a.subject, "alarm_class": str(a.alarm_class.id), "alarm_class__label": a.alarm_class.name, "managed_object": a.managed_object.id, "managed_object__label": a.managed_object.name, "timestamp": self.to_json(a.timestamp), "iconCls": "icon_error", "row_class": s.style.css_class_name, } nc = self.get_nested_alarms(a) if nc: c["children"] = nc c["expanded"] = True else: c["leaf"] = True children += [c] return children @view( url=r"^(?P<id>[a-z0-9]{24})/post/", method=["POST"], api=True, access="launch", validate={"msg": UnicodeParameter()}, ) def api_post(self, request, id, msg): alarm = get_alarm(id) if not alarm: self.response_not_found() alarm.log_message(msg, source=request.user.username) return True @view( url=r"^comment/post/", method=["POST"], api=True, access="launch", validate={ "ids": StringListParameter(required=True), "msg": UnicodeParameter(), }, ) def api_comment_post(self, request, ids, msg): alarms = list(ActiveAlarm.objects.filter(id__in=ids)) alarms += list(ArchivedAlarm.objects.filter(id__in=ids)) if not alarms: self.response_not_found() for alarm in alarms: alarm.log_message(msg, source=request.user.username) return True @view( url=r"^(?P<id>[a-z0-9]{24})/acknowledge/", method=["POST"], api=True, access="acknowledge", validate={ "msg": UnicodeParameter(default=""), }, ) def api_acknowledge(self, request, id, msg=""): alarm = get_alarm(id) if not alarm: return self.response_not_found() if alarm.status != "A": return self.response_not_found() if alarm.ack_ts: return { "status": False, "message": "Already acknowledged by %s" % alarm.ack_user } alarm.acknowledge(request.user, msg) return {"status": True} @view( url=r"^(?P<id>[a-z0-9]{24})/unacknowledge/", method=["POST"], api=True, access="acknowledge", validate={ "msg": UnicodeParameter(default=""), }, ) def api_unacknowledge(self, request, id, msg=""): alarm = get_alarm(id) if not alarm: return self.response_not_found() if alarm.status != "A": return self.response_not_found() if not alarm.ack_ts: return {"status": False, "message": "Already unacknowledged"} alarm.unacknowledge(request.user, msg=msg) return {"status": True} @view(url=r"^(?P<id>[a-z0-9]{24})/subscribe/", method=["POST"], api=True, access="launch") def api_subscribe(self, request, id): alarm = get_alarm(id) if not alarm: return self.response_not_found() if alarm.status == "A": alarm.subscribe(request.user) return self.get_alarm_subscribers(alarm) else: return [] @view(url=r"^(?P<id>[a-z0-9]{24})/unsubscribe/", method=["POST"], api=True, access="launch") def api_unsubscribe(self, request, id): alarm = get_alarm(id) if not alarm: return self.response_not_found() if alarm.status == "A": alarm.unsubscribe(request.user) return self.get_alarm_subscribers(alarm) else: return [] @view( url=r"^(?P<id>[a-z0-9]{24})/clear/", method=["POST"], api=True, access="launch", validate={ "msg": UnicodeParameter(default=""), }, ) def api_clear(self, request, id, msg=""): alarm = get_alarm(id) if not alarm.alarm_class.user_clearable: return {"status": False, "error": "Deny clear alarm by user"} if alarm.status == "A": alarm.clear_alarm("Cleared by %s: %s" % (request.user, msg), source=request.user.username) return True @view( url=r"^(?P<id>[a-z0-9]{24})/set_root/", method=["POST"], api=True, access="launch", validate={"root": StringParameter()}, ) def api_set_root(self, request, id, root): alarm = get_alarm(id) r = get_alarm(root) if not r: return self.response_not_found() alarm.set_root(r) return True @view(url=r"notification/$", method=["GET"], api=True, access="launch") def api_notification(self, request): delta = request.GET.get("delta") n = 0 sound = None volume = 0 if delta: dt = datetime.timedelta(seconds=int(delta)) t0 = datetime.datetime.now() - dt r = list(ActiveAlarm._get_collection().aggregate([ { "$match": { "timestamp": { "$gt": t0 } } }, { "$group": { "_id": "$item", "severity": { "$max": "$severity" } } }, ])) if r: s = AlarmSeverity.get_severity(r[0]["severity"]) if s and s.sound and s.volume: sound = "/ui/pkg/nocsound/%s.mp3" % s.sound volume = float(s.volume) / 100.0 return {"new_alarms": n, "sound": sound, "volume": volume} @classmethod def f_glyph_summary(cls, s, collapse=False): def be_true(p): return True def be_show(p): return p.show_in_summary def get_summary(d, profile): v = [] if hasattr(profile, "show_in_summary"): show_in_summary = be_show else: show_in_summary = be_true for p, c in d.items(): pv = profile.get_by_id(p) if pv and show_in_summary(pv): if collapse and c < 2: badge = "" else: badge = '<span class="x-display-tag">%s</span>' % c order = getattr(pv, "display_order", 100) v += [( (order, -c), '<i class="%s" title="%s"></i>%s' % (pv.glyph, pv.name, badge), )] return "<span class='x-summary'>%s</span>" % "".join( i[1] for i in sorted(v, key=operator.itemgetter(0))) if not isinstance(s, dict): return "" r = [] if "subscriber" in s: from noc.crm.models.subscriberprofile import SubscriberProfile r += [get_summary(s["subscriber"], SubscriberProfile)] if "service" in s: from noc.sa.models.serviceprofile import ServiceProfile r += [get_summary(s["service"], ServiceProfile)] r = [x for x in r if x] return "".join(r) @view(url=r"^(?P<id>[a-z0-9]{24})/escalate/", method=["GET"], api=True, access="escalate") def api_escalation_alarm(self, request, id): alarm = get_alarm(id) if alarm.status == "A": AlarmEscalation.watch_escalations(alarm) return {"status": True} else: return { "status": False, "error": "The alarm is not active at the moment" } def location(self, id): """ Return geo address for Managed Objects """ def chunkIt(seq, num): avg = len(seq) / float(num) out = [] last = 0.0 while last < len(seq): out.append(seq[int(last):int(last + avg)]) last += avg return out location = [] address = Object.get_by_id(id).get_address_text() if address: for res in address.split(","): adr = normalize_division(smart_text(res).strip().lower()) if None in adr and "" in adr: continue if None in adr: location += [adr[1].title().strip()] else: location += [" ".join(adr).title().strip()] res = chunkIt(location, 2) location_1 = ", ".join(res[0]) location_2 = ", ".join(res[1]) return [location_1, location_2] return ["", ""] @classmethod def f_summary(cls, s): def be_true(p): return True def be_show(p): return p.show_in_summary def get_summary(d, profile): v = [] if hasattr(profile, "show_in_summary"): show_in_summary = be_show else: show_in_summary = be_true for p, c in sorted(d.items(), key=lambda x: -x[1]): pv = profile.get_by_id(p) if pv and show_in_summary(pv): v += [{ "profile": str(pv.id), "glyph": pv.glyph, "display_order": pv.display_order, "profile__label": pv.name, "summary": c, }] return v if not isinstance(s, dict): return "" r = [] if "subscriber" in s: from noc.crm.models.subscriberprofile import SubscriberProfile r += get_summary(s["subscriber"], SubscriberProfile) if "service" in s: from noc.sa.models.serviceprofile import ServiceProfile r += get_summary(s["service"], ServiceProfile) r = [x for x in r if x] return r def bulk_field_isinmaintenance(self, data): if not data: return data if data[0]["status"] == "A": mtc = set(Maintenance.currently_affected()) for x in data: x["isInMaintenance"] = x["managed_object"] in mtc else: mos = set([x["managed_object"] for x in data]) mtc = {} for mo in list(mos): interval = [] for ao in AffectedObjects._get_collection().find( {"affected_objects.object": { "$eq": mo }}, { "_id": 0, "maintenance": 1 }): m = Maintenance.get_by_id(ao["maintenance"]) interval += [(m.start, m.stop)] if interval: mtc[mo] = interval for x in data: if x["managed_object"] in mtc: left, right = list(zip(*mtc[x["managed_object"]])) x["isInMaintenance"] = bisect.bisect( right, dateutil.parser.parse(x["timestamp"]).replace( tzinfo=None)) != bisect.bisect( left, dateutil.parser.parse( x["clear_timestamp"]).replace(tzinfo=None)) else: x["isInMaintenance"] = False return data @view(url=r"profile_lookup/$", access="launch", method=["GET"], api=True) def api_profile_lookup(self, request): r = [] for model, short_type, field_id in ( (ServiceProfile, _("Service"), "total_services"), (SubscriberProfile, _("Subscribers"), "total_subscribers"), ): # "%s|%s" % (field_id, r += [{ "id": str(o.id), "type": short_type, "display_order": o.display_order, "icon": o.glyph, "label": o.name, } for o in model.objects.all() if getattr(o, "show_in_summary", True)] return r
def test_unicode_parameter_error(raw, config): with pytest.raises(InterfaceTypeError): assert UnicodeParameter(**config).clean(raw)
def test_unicode_parameter(raw, config, expected): assert UnicodeParameter(**config).clean(raw) == expected
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 AlarmApplication(ExtApplication): """ fm.alarm application """ title = _("Alarm") menu = _("Alarms") glyph = "exclamation-triangle" implied_permissions = {"launch": ["sa:managedobject:alarm"]} model_map = {"A": ActiveAlarm, "C": ArchivedAlarm} clean_fields = { "managed_object": ModelParameter(ManagedObject), "timestamp": DateTimeParameter() } ignored_params = ["status", "_dc"] diagnostic_plugin = AlarmPlugin(name="diagnostic", config={}) DEFAULT_ARCH_ALARM = datetime.timedelta( seconds=config.web.api_arch_alarm_limit) def __init__(self, *args, **kwargs): ExtApplication.__init__(self, *args, **kwargs) from .plugins.base import AlarmPlugin # Load plugins self.plugins = {} for f in os.listdir("services/web/apps/fm/alarm/plugins/"): if (not f.endswith(".py") or f == "base.py" or f.startswith("_")): continue mn = "noc.services.web.apps.fm.alarm.plugins.%s" % f[:-3] m = __import__(mn, {}, {}, "*") for on in dir(m): o = getattr(m, on) if (inspect.isclass(o) and issubclass(o, AlarmPlugin) and o.__module__.startswith(mn)): assert o.name self.plugins[o.name] = o(self) def cleaned_query(self, q): q = q.copy() status = q["status"] if "status" in q else "A" for p in self.ignored_params: if p in q: del q[p] for p in (self.limit_param, self.page_param, self.start_param, self.format_param, self.sort_param, self.query_param, self.only_param): if p in q: del q[p] # Normalize parameters for p in q: qp = p.split("__")[0] if qp in self.clean_fields: q[p] = self.clean_fields[qp].clean(q[p]) # Exclude maintenance if "maintenance" not in q: q["maintenance"] = "hide" if q["maintenance"] == "hide": q["managed_object__nin"] = Maintenance.currently_affected() elif q["maintenance"] == "only": q["managed_object__in"] = Maintenance.currently_affected() del q["maintenance"] if "administrative_domain" in q: q["adm_path"] = int(q["administrative_domain"]) q.pop("administrative_domain") if "segment" in q: q["segment_path"] = bson.ObjectId(q["segment"]) q.pop("segment") if "managedobjectselector" in q: s = SelectorCache.objects.filter( selector=q["managedobjectselector"]).values_list("object") if "managed_object__in" in q: q["managed_object__in"] = list( set(q["managed_object__in"]).intersection(s)) else: q["managed_object__in"] = s q.pop("managedobjectselector") if "cleared_after" in q: q["clear_timestamp__gte"] = datetime.datetime.now( ) - datetime.timedelta(seconds=int(q["cleared_after"])) q.pop("cleared_after") # if "wait_tt" in q: q["wait_tt__exists"] = True q["wait_ts__exists"] = False del q["wait_tt"] # if "collapse" in q: c = q["collapse"] del q["collapse"] if c != "0": q["root__exists"] = False if status == "C": if ("timestamp__gte" not in q and "timestamp__lte" not in q and "escalation_tt__contains" not in q and "managed_object" not in q): q["timestamp__gte"] = datetime.datetime.now( ) - self.DEFAULT_ARCH_ALARM return q def instance_to_dict(self, o, fields=None): s = AlarmSeverity.get_severity(o.severity) n_events = (ActiveEvent.objects.filter(alarms=o.id).count() + ArchivedEvent.objects.filter(alarms=o.id).count()) mtc = o.managed_object.id in Maintenance.currently_affected() if o.status == "C": # For archived alarms mtc = Maintenance.objects.filter( start__lte=o.clear_timestamp, stop__lte=o.timestamp, affected_objects__in=[ MaintenanceObject(object=o.managed_object) ]).count() > 0 d = { "id": str(o.id), "status": o.status, "managed_object": o.managed_object.id, "managed_object__label": o.managed_object.name, "administrative_domain": o.managed_object.administrative_domain_id, "administrative_domain__label": o.managed_object.administrative_domain.name, "severity": o.severity, "severity__label": s.name, "alarm_class": str(o.alarm_class.id), "alarm_class__label": o.alarm_class.name, "timestamp": self.to_json(o.timestamp), "subject": o.subject, "events": n_events, "duration": o.duration, "clear_timestamp": self.to_json(o.clear_timestamp) if o.status == "C" else None, "row_class": s.style.css_class_name, "segment__label": o.managed_object.segment.name, "segment": str(o.managed_object.segment.id), "location_1": self.location(o.managed_object.container.id)[0] if o.managed_object.container else "", "location_2": self.location(o.managed_object.container.id)[1] if o.managed_object.container else "", "escalation_tt": o.escalation_tt, "escalation_error": o.escalation_error, "platform": o.managed_object.platform.name if o.managed_object.platform else "", "address": o.managed_object.address, "isInMaintenance": mtc, "summary": self.f_glyph_summary({ "subscriber": SummaryItem.items_to_dict(o.total_subscribers), "service": SummaryItem.items_to_dict(o.total_services) }), "total_objects": sum(x.summary for x in o.total_objects) } if fields: d = dict((k, d[k]) for k in fields) return d def queryset(self, request, query=None): """ Filter records for lookup """ status = request.GET.get("status", "A") if status not in self.model_map: raise Exception("Invalid status") model = self.model_map[status] if request.user.is_superuser: return model.objects.filter( read_preference=ReadPreference.SECONDARY_PREFERRED).all() else: return model.objects.filter( adm_path__in=UserAccess.get_domains(request.user), read_preference=ReadPreference.SECONDARY_PREFERRED) @view(url=r"^$", access="launch", method=["GET"], api=True) def api_list(self, request): return self.list_data(request, self.instance_to_dict) @view(url=r"^(?P<id>[a-z0-9]{24})/$", method=["GET"], api=True, access="launch") def api_alarm(self, request, id): alarm = get_alarm(id) if not alarm: self.response_not_found() user = request.user d = self.instance_to_dict(alarm) d["body"] = alarm.body d["symptoms"] = alarm.alarm_class.symptoms d["probable_causes"] = alarm.alarm_class.probable_causes d["recommended_actions"] = alarm.alarm_class.recommended_actions d["vars"] = sorted(alarm.vars.items()) d["status"] = alarm.status d["status__label"] = {"A": "Active", "C": "Cleared"}[alarm.status] # Managed object properties mo = alarm.managed_object d["managed_object_address"] = mo.address d["managed_object_profile"] = mo.profile.name d["managed_object_platform"] = mo.platform.name if mo.platform else "" d["managed_object_version"] = mo.version.version if mo.version else "" d["segment"] = mo.segment.name d["segment_id"] = str(mo.segment.id) d["segment_path"] = " | ".join( NetworkSegment.get_by_id(p).name for p in NetworkSegment.get_path(mo.segment)) if mo.container: cp = [] c = mo.container.id while c: try: o = Object.objects.get(id=c) if o.container: cp.insert(0, o.name) c = o.container.id if o.container else None except DoesNotExist: break d["container_path"] = " | ".join(cp) if not self.location(mo.container.id)[0]: d["address_path"] = None else: d["address_path"] = ", ".join(self.location(mo.container.id)) d["tags"] = mo.tags # Log if alarm.log: d["log"] = [{ "timestamp": self.to_json(l.timestamp), "from_status": l.from_status, "to_status": l.to_status, "message": l.message } for l in alarm.log] # Events events = [] for ec in ActiveEvent, ArchivedEvent: for e in ec.objects.filter(alarms=alarm.id): events += [{ "id": str(e.id), "event_class": str(e.event_class.id), "event_class__label": e.event_class.name, "timestamp": self.to_json(e.timestamp), "status": e.status, "managed_object": e.managed_object.id, "managed_object__label": e.managed_object.name, "subject": e.subject }] if events: d["events"] = events # Alarms children = self.get_nested_alarms(alarm) if children: d["alarms"] = {"expanded": True, "children": children} # Subscribers if alarm.status == "A": d["subscribers"] = self.get_alarm_subscribers(alarm) d["is_subscribed"] = user in alarm.subscribers # Apply plugins plugins = [] acp = alarm.alarm_class.plugins or [] acp += [self.diagnostic_plugin] for p in acp: if p.name in self.plugins: plugin = self.plugins[p.name] dd = plugin.get_data(alarm, p.config) if "plugins" in dd: plugins += dd["plugins"] del dd["plugins"] d.update(dd) if plugins: d["plugins"] = plugins return d def get_alarm_subscribers(self, alarm): """ JSON-serializable subscribers :param alarm: :return: """ subscribers = [] for u in alarm.subscribers: try: u = User.objects.get(id=u) subscribers += [{ "id": u.id, "name": " ".join([u.first_name, u.last_name]), "login": u.username }] except User.DoesNotExist: pass return subscribers def get_nested_alarms(self, alarm): """ Return nested alarms as a part of NodeInterface :param alarm: :return: """ children = [] for ac in (ActiveAlarm, ArchivedAlarm): for a in ac.objects.filter(root=alarm.id): s = AlarmSeverity.get_severity(a.severity) c = { "id": str(a.id), "subject": a.subject, "alarm_class": str(a.alarm_class.id), "alarm_class__label": a.alarm_class.name, "managed_object": a.managed_object.id, "managed_object__label": a.managed_object.name, "timestamp": self.to_json(a.timestamp), "iconCls": "icon_error", "row_class": s.style.css_class_name } nc = self.get_nested_alarms(a) if nc: c["children"] = nc c["expanded"] = True else: c["leaf"] = True children += [c] return children @view(url=r"^(?P<id>[a-z0-9]{24})/post/", method=["POST"], api=True, access="launch", validate={"msg": UnicodeParameter()}) def api_post(self, request, id, msg): alarm = get_alarm(id) if not alarm: self.response_not_found() alarm.log_message("%s: %s" % (request.user.username, msg)) return True @view(url=r"^(?P<id>[a-z0-9]{24})/subscribe/", method=["POST"], api=True, access="launch") def api_subscribe(self, request, id): alarm = get_alarm(id) if not alarm: return self.response_not_found() if alarm.status == "A": alarm.subscribe(request.user) return self.get_alarm_subscribers(alarm) else: return [] @view(url=r"^(?P<id>[a-z0-9]{24})/unsubscribe/", method=["POST"], api=True, access="launch") def api_unsubscribe(self, request, id): alarm = get_alarm(id) if not alarm: return self.response_not_found() if alarm.status == "A": alarm.unsubscribe(request.user) return self.get_alarm_subscribers(alarm) else: return [] @view(url=r"^(?P<id>[a-z0-9]{24})/clear/", method=["POST"], api=True, access="launch") def api_clear(self, request, id): alarm = get_alarm(id) if alarm.status == "A": alarm.clear_alarm("Cleared by %s" % request.user) return True @view(url=r"^(?P<id>[a-z0-9]{24})/set_root/", method=["POST"], api=True, access="launch", validate={"root": StringParameter()}) def api_set_root(self, request, id, root): alarm = get_alarm(id) r = get_alarm(root) if not r: return self.response_not_found() alarm.set_root(r) return True @view(url="notification/$", method=["GET"], api=True, access="launch") def api_notification(self, request): delta = request.GET.get("delta") n = 0 sound = None volume = 0 if delta: dt = datetime.timedelta(seconds=int(delta)) t0 = datetime.datetime.now() - dt r = list(ActiveAlarm._get_collection().aggregate([{ "$match": { "timestamp": { "$gt": t0 } } }, { "$group": { "_id": "$item", "severity": { "$max": "$severity" } } }])) if r: s = AlarmSeverity.get_severity(r[0]["severity"]) if s and s.sound and s.volume: sound = "/ui/pkg/nocsound/%s.mp3" % s.sound volume = float(s.volume) / 100.0 return {"new_alarms": n, "sound": sound, "volume": volume} @classmethod def f_glyph_summary(cls, s, collapse=False): def be_true(p): return True def be_show(p): return p.show_in_summary def get_summary(d, profile): v = [] if hasattr(profile, "show_in_summary"): show_in_summary = be_show else: show_in_summary = be_true for p, c in sorted(d.items(), key=lambda x: -x[1]): pv = profile.get_by_id(p) if pv and show_in_summary(pv): if collapse and c < 2: badge = "" else: badge = " <span class=\"x-display-tag\">%s</span>" % c v += [ "<i class=\"%s\" title=\"%s\"></i>%s" % (pv.glyph, pv.name, badge) ] return "<span class='x-summary'>%s</span>" % " ".join(v) if not isinstance(s, dict): return "" r = [] if "subscriber" in s: from noc.crm.models.subscriberprofile import SubscriberProfile r += [get_summary(s["subscriber"], SubscriberProfile)] if "service" in s: from noc.sa.models.serviceprofile import ServiceProfile r += [get_summary(s["service"], ServiceProfile)] r = [x for x in r if x] return " ".join(r) @view(url=r"^(?P<id>[a-z0-9]{24})/escalate/", method=["GET"], api=True, access="escalate") def api_escalation_alarm(self, request, id): alarm = get_alarm(id) if alarm.status == "A": AlarmEscalation.watch_escalations(alarm) return {'status': True} else: return { 'status': False, 'error': 'The alarm is not active at the moment' } def location(self, id): """ Return geo address for Managed Objects """ def chunkIt(seq, num): avg = len(seq) / float(num) out = [] last = 0.0 while last < len(seq): out.append(seq[int(last):int(last + avg)]) last += avg return out location = [] address = Object.get_by_id(id).get_address_text() if address: for res in address.split(","): adr = normalize_division(res.strip().decode("utf-8").lower()) if None in adr and "" in adr: continue if None in adr: location += [adr[1].title().strip()] else: location += [' '.join(adr).title().strip()] res = chunkIt(location, 2) location_1 = ", ".join(res[0]) location_2 = ", ".join(res[1]) return [location_1, location_2] else: return ["", ""]
class PrefixListBuilderApplication(ExtApplication): """ Interactive prefix list builder """ title = _("Prefix List Builder") menu = _("Prefix List Builder") @view(method=["GET"], url=r"^$", access="read", api=True, validate={ "peering_point": ModelParameter(PeeringPoint), "name": UnicodeParameter(required=False), "as_set": UnicodeParameter() }) def api_list(self, request, peering_point, name, as_set): if not WhoisCache.has_asset_members(): return { "name": name, "prefix_list": "", "success": False, "message": _("AS-SET members cache is empty. Please update Whois Cache") } if not WhoisCache.has_origin_routes(): return { "name": name, "prefix_list": "", "success": False, "message": _("Origin routes cache is empty. Please update Whois Cache") } if not WhoisCache.has_asset(as_set): return { "name": name, "prefix_list": "", "success": False, "message": _("Unknown AS-SET") } prefixes = WhoisCache.resolve_as_set_prefixes_maxlen(as_set) if not prefixes: return { "name": name, "prefix_list": "", "success": False, "message": _("Cannot resolve AS-SET prefixes") } try: pl = peering_point.profile.get_profile().generate_prefix_list( name, prefixes) except NotImplementedError: return { "name": name, "prefix_list": "", "success": False, "message": _("Prefix-list generator is not implemented for this profile") } return { "name": name, "prefix_list": pl, "success": True, "message": _("Prefix List built") }
class 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