class VRFGroup(NOCModel): """ Group of VRFs with common properties """ class Meta(object): verbose_name = _("VRF Group") verbose_name_plural = _("VRF Groups") db_table = "ip_vrfgroup" app_label = "ip" ordering = ["name"] name = models.CharField( _("VRF Group"), unique=True, max_length=64, help_text=_("Unique VRF Group name") ) address_constraint = models.CharField( _("Address Constraint"), max_length=1, choices=[ ("V", _("Addresses are unique per VRF")), ("G", _("Addresses are unique per VRF Group")), ], default="V", ) description = models.TextField(_("Description"), blank=True, null=True) tags = TagsField(_("Tags"), null=True, blank=True) def __str__(self): return smart_text(self.name) def get_absolute_url(self): return site.reverse("ip:vrfgroup:change", self.id)
class DNSZoneRecord(NOCModel): """ Zone RRs """ class Meta(object): verbose_name = _("DNS Zone Record") verbose_name_plural = _("DNS Zone Records") db_table = "dns_dnszonerecord" app_label = "dns" zone = models.ForeignKey(DNSZone, verbose_name="Zone", on_delete=models.CASCADE) name = models.CharField(_("Name"), max_length=64, blank=True, null=True) ttl = models.IntegerField(_("TTL"), null=True, blank=True) type = models.CharField(_("Type"), max_length=16) priority = models.IntegerField(_("Priority"), null=True, blank=True) content = models.CharField(_("Content"), max_length=65536) tags = TagsField(_("Tags"), null=True, blank=True) def __str__(self): return "%s %s" % ( self.zone.name, " ".join([x for x in (self.name, self.type, self.content) if x]), ) def iter_changed_datastream(self, changed_fields=None): for ds, id in self.zone.iter_changed_datastream( changed_fields=changed_fields): yield ds, id
class ASSet(NOCModel): class Meta(object): verbose_name = "ASSet" verbose_name_plural = "ASSets" db_table = "peer_asset" app_label = "peer" name = models.CharField("Name", max_length=32, unique=True) project = models.ForeignKey( Project, verbose_name="Project", null=True, blank=True, related_name="asset_set", on_delete=models.CASCADE, ) description = models.CharField("Description", max_length=64) members = models.TextField("Members", null=True, blank=True) rpsl_header = models.TextField("RPSL Header", null=True, blank=True) rpsl_footer = models.TextField("RPSL Footer", null=True, blank=True) tags = TagsField("Tags", null=True, blank=True) rpsl = GridVCSField("rpsl_asset") def __str__(self): return self.name @property def member_list(self): if self.members is None: return [] m = sorted( self.members.replace(",", " ").replace("\n", " ").replace( "\r", " ").upper().split()) return m def get_rpsl(self): sep = "remark: %s" % ("-" * 72) s = [] if self.rpsl_header: s += self.rpsl_header.split("\n") s += ["as-set: %s" % self.name] for m in self.member_list: s += ["members: %s" % m] if self.rpsl_footer: s += [sep] s += self.rpsl_footer.split("\n") return rpsl_format("\n".join(s)) def touch_rpsl(self): c_rpsl = self.rpsl.read() n_rpsl = self.get_rpsl() if c_rpsl == n_rpsl: return # Not changed self.rpsl.write(n_rpsl) def on_save(self): self.touch_rpsl()
def forwards(self): # Create temporary tags fields db.add_column("sa_managedobjectselector", "tmp_filter_tags", TagsField("Tags", null=True, blank=True)) # Migrate data db.execute(""" UPDATE sa_managedobjectselector SET tmp_filter_tags = string_to_array(regexp_replace(filter_tags, ',$', ''), ',') WHERE filter_tags != '' """)
def forwards(self): # Administrative Domain db.add_column("sa_administrativedomain", "remote_system", DocumentReferenceField("self", null=True, blank=True)) db.add_column("sa_administrativedomain", "remote_id", models.CharField(max_length=64, null=True, blank=True)) db.add_column("sa_administrativedomain", "bi_id", models.IntegerField(null=True, blank=True)) # AuthProfile db.add_column("sa_authprofile", "remote_system", DocumentReferenceField("self", null=True, blank=True)) db.add_column("sa_authprofile", "remote_id", models.CharField(max_length=64, null=True, blank=True)) db.add_column("sa_authprofile", "bi_id", models.IntegerField(null=True, blank=True)) db.add_column("sa_authprofile", "tags", TagsField("Tags", null=True, blank=True)) # ManagedObject db.add_column("sa_managedobject", "remote_system", DocumentReferenceField("self", null=True, blank=True)) db.add_column("sa_managedobject", "remote_id", models.CharField(max_length=64, null=True, blank=True)) db.add_column("sa_managedobject", "bi_id", models.IntegerField(null=True, blank=True)) db.add_column( "sa_managedobject", "escalation_policy", models.CharField("Escalation Policy", max_length=1, choices=[("E", "Enable"), ("D", "Disable"), ("P", "From Profile")], default="P")) db.add_column("sa_managedobject", "tt_system", DocumentReferenceField("self", null=True, blank=True)) db.add_column("sa_managedobject", "tt_system_id", models.CharField(max_length=64, null=True, blank=True)) # ManagedObjectProfile db.add_column("sa_managedobjectprofile", "remote_system", DocumentReferenceField("self", null=True, blank=True)) db.add_column("sa_managedobjectprofile", "remote_id", models.CharField(max_length=64, null=True, blank=True)) db.add_column("sa_managedobjectprofile", "bi_id", models.IntegerField(null=True, blank=True)) db.add_column( "sa_managedobjectprofile", "escalation_policy", models.CharField("Escalation Policy", max_length=1, choices=[("E", "Enable"), ("D", "Disable")], default="E")) # TerminationGroup db.add_column("sa_terminationgroup", "remote_system", DocumentReferenceField("self", null=True, blank=True)) db.add_column("sa_terminationgroup", "remote_id", models.CharField(max_length=64, null=True, blank=True)) db.add_column("sa_terminationgroup", "bi_id", models.IntegerField(null=True, blank=True))
def migrate(self): # Create temporary tags fields for m in self.TAG_MODELS: self.db.add_column(m, "tmp_tags", TagsField("Tags", null=True, blank=True)) # Migrate data for m in self.TAG_MODELS: self.db.execute(""" UPDATE %s SET tmp_tags = string_to_array(regexp_replace(tags, ',$', ''), ',') WHERE tags != '' """ % m)
def forwards(self): AdministrativeDomain = db.mock_model( model_name="AdministrativeDomain", db_table="sa_administrativedomain", db_tablespace="", pk_field_name="id", pk_field_type=models.AutoField) db.add_column("sa_administrativedomain", "tags", TagsField("Tags", null=True, blank=True)) db.add_column( "sa_administrativedomain", "parent", models.ForeignKey(AdministrativeDomain, verbose_name="Parent", null=True, blank=True))
def migrate(self): AdministrativeDomain = self.db.mock_model( model_name="AdministrativeDomain", db_table="sa_administrativedomain") self.db.add_column("sa_administrativedomain", "tags", TagsField("Tags", null=True, blank=True)) self.db.add_column( "sa_administrativedomain", "parent", models.ForeignKey( AdministrativeDomain, verbose_name="Parent", null=True, blank=True, on_delete=models.CASCADE, ), )
class AddressRange(NOCModel): class Meta(object): verbose_name = _("Address Range") db_table = "ip_addressrange" app_label = "ip" unique_together = [("vrf", "afi", "from_address", "to_address")] name = models.CharField(_("Name"), max_length=64, unique=True) is_active = models.BooleanField(_("Is Active"), default=True) vrf = models.ForeignKey(VRF, verbose_name=_("VRF"), on_delete=models.CASCADE) afi = models.CharField(_("Address Family"), max_length=1, choices=AFI_CHOICES) from_address = CIDRField(_("From Address")) to_address = CIDRField(_("To address")) description = models.TextField(_("Description"), blank=True, null=True) is_locked = models.BooleanField( _("Is Locked"), default=False, help_text=_("Check to deny address creation or editing within the range"), ) action = models.CharField( _("Action"), max_length=1, choices=[ ("N", _("Do nothing")), ("G", _("Generate FQDNs")), ("D", _("Partial reverse zone delegation")), ], default="N", ) fqdn_template = models.CharField( _("FQDN Template"), max_length=255, null=True, blank=True, help_text=_("Template to generate FQDNs when 'Action' set to 'Generate FQDNs'"), ) reverse_nses = models.CharField( _("Reverse NSes"), max_length=255, null=True, blank=True, help_text=_( "Comma-separated list of NSes to partial reverse zone delegation when " "'Action' set to 'Partial reverse zone delegation" ), ) tags = TagsField(_("Tags"), null=True, blank=True) tt = models.IntegerField("TT", blank=True, null=True, help_text=_("Ticket #")) allocated_till = models.DateField( _("Allocated till"), null=True, blank=True, help_text=_("VRF temporary allocated till the date"), ) def __str__(self): return "%s (IPv%s): %s -- %s" % ( self.vrf.name, self.afi, self.from_address, self.to_address, ) def iter_changed_datastream(self, changed_fields=None): if not config.datastream.enable_dnszone: return from noc.dns.models.dnszone import DNSZone if self.action == "D": zone = DNSZone.get_reverse_for_address(self.from_address) if zone: yield "dnszone", zone.id def clean(self): """ Field validation """ super(AddressRange, self).clean() # Check prefix is of AFI type if self.afi == "4": check_ipv4(self.from_address) check_ipv4(self.to_address) elif self.afi == "6": check_ipv6(self.from_address) check_ipv6(self.to_address) def save(self, *args, **kwargs): def generate_fqdns(): # Prepare FQDN template t = Template(self.fqdn_template) # Sync FQDNs sn = 0 for ip in self.addresses: # Generage FQDN vars = {"afi": self.afi, "vrf": self.vrf, "range": self, "n": sn} sn += 1 if self.afi == "4": i = ip.address.split(".") vars["ip"] = i # ip.0 .. ip.3 # ip1, ip2, ip3, ip4 for backward compatibility for n, i in enumerate(i): vars["ip%d" % (n + 1)] = i elif self.afi == "6": vars["ip"] = ip.digits # ip.0 .. ip.31 fqdn = t.render(Context(vars)) description = "Generated by address range '%s'" % self.name # Create or update address record when necessary a, created = Address.objects.get_or_create( vrf=self.vrf, afi=self.afi, address=ip.address ) if created: a.fqdn = fqdn a.description = description a.save() elif a.fqdn != fqdn or a.description != a.description: a.fqdn = fqdn a.description = description a.save() created = self.id is None if not created: # Get old values old = AddressRange.objects.get(id=self.id) super(AddressRange, self).save(*args, **kwargs) if created: # New if self.action == "G": generate_fqdns() else: # Changed if old.action == "G" and self.action != "G": # Drop all auto-generated IPs Address.objects.filter( vrf=self.vrf, afi=self.afi, address__gte=self.from_address, address__lte=self.to_address, ).delete() elif old.action != "G" and self.action == "G": # Generate IPs generate_fqdns() elif self.action == "G": # Check for boundaries change if IP.prefix(old.from_address) < IP.prefix(self.from_address): # Lower boundary raised up. Clean up addresses falled out of range Address.objects.filter( vrf=self.vrf, afi=self.afi, address__gte=old.from_address, address__lt=self.to_address, ).delete() if IP.prefix(old.to_address) > IP.prefix(self.to_address): # Upper boundary is lowered. Clean up addressess falled out of range Address.objects.filter( vrf=self.vrf, afi=self.afi, address__gt=self.to_address, address__lte=old.to_address, ).delete() # Finally recheck FQDNs generate_fqdns() @property def short_description(self): """ First line of description """ if self.description: return self.description.split("\n", 1)[0].strip() else: return "" @property def addresses(self): """ Generator returning all addresses in range """ return IP.prefix(self.from_address).iter_address(until=IP.prefix(self.to_address)) @classmethod def get_overlapping_ranges(cls, vrf, afi, from_address, to_address): """ Returns a list of overlapping ranges :param vrf: :param afi: :param from_address: :param to_address: :return: """ return AddressRange.objects.raw( """ SELECT * FROM ip_addressrange WHERE vrf_id=%(vrf)s AND afi=%(afi)s AND is_active AND ( from_address BETWEEN %(from_address)s AND %(to_address)s OR to_address BETWEEN %(from_address)s AND %(to_address)s OR %(from_address)s BETWEEN from_address AND to_address OR %(to_address)s BETWEEN from_address AND to_address ) """, {"vrf": vrf.id, "afi": afi, "from_address": from_address, "to_address": to_address}, ) @property def overlapping_ranges(self): """ Returns a queryset with overlapped ranges :return: """ return self.get_overlapping_ranges(self.vrf, self.afi, self.from_address, self.to_address) @classmethod def address_is_locked(cls, vrf, afi, address): """ Check wrether address is locked by any range """ return AddressRange.objects.filter( vrf=vrf, afi=afi, is_locked=True, is_active=True, from_address__lte=address, to_address__gte=address, ).exists()
class KBEntry(NOCModel): """ KB Entry """ class Meta(object): verbose_name = "KB Entry" verbose_name_plural = "KB Entries" app_label = "kb" db_table = "kb_kbentry" ordering = ("id", ) subject = models.CharField("Subject", max_length=256) body = models.TextField("Body") language = models.ForeignKey( Language, verbose_name="Language", limit_choices_to={"is_active": True}, on_delete=models.CASCADE, ) markup_language = models.CharField("Markup Language", max_length="16", choices=[(x, x) for x in loader]) tags = TagsField("Tags", null=True, blank=True) def __str__(self): if self.id: return "KB%d: %s" % (self.id, self.subject) else: return "New: %s" % self.subject def get_absolute_url(self): return site.reverse("kb:view:view", self.id) def save(self, *args, **kwargs): """ save model, compute body's diff and save event history """ from noc.core.middleware.tls import get_user from noc.kb.models.kbentryhistory import KBEntryHistory user = get_user() if self.id: old_body = KBEntry.objects.get(id=self.id).body else: old_body = "" super(KBEntry, self).save(*args, **kwargs) if old_body != self.body: diff = "\n".join( difflib.unified_diff(self.body.splitlines(), old_body.splitlines())) KBEntryHistory( kb_entry=self, user=user, diff=diff, timestamp=datetime.datetime.now().replace(microsecond=0), ).save() @property def parser(self): """ Wiki parser class """ return loader[self.markup_language] @property def html(self): """ Returns parsed HTML """ return self.parser.to_html(self) @property def last_history(self): """ Returns latest KBEntryHistory record """ from .kbentryhistory import KBEntryHistory d = KBEntryHistory.objects.filter( kb_entry=self).order_by("-timestamp")[:1] if d: return d[0] return None @classmethod def last_modified(cls, num=20): """ Returns a list of last modified KB Entries """ from django.db import connection c = connection.cursor() c.execute(""" SELECT kb_entry_id,MAX(timestamp) FROM kb_kbentryhistory GROUP BY 1 ORDER BY 2 DESC LIMIT %d""" % num) return [KBEntry.objects.get(id=r[0]) for r in c.fetchall()] def log_preview(self, user): """ Write article preview log """ from .kbentrypreviewlog import KBEntryPreviewLog KBEntryPreviewLog(kb_entry=self, user=user).save() @property def preview_count(self): """ Returns preview count """ return self.kbentrypreviewlog_set.count() @classmethod def most_popular(cls, num=20): """ Returns most popular articles """ from django.db import connection c = connection.cursor() c.execute(""" SELECT kb_entry_id,COUNT(*) FROM kb_kbentrypreviewlog GROUP BY 1 ORDER BY 2 DESC LIMIT %d""" % num) return [KBEntry.objects.get(id=r[0]) for r in c.fetchall()] @classmethod def upload_to(cls, instance, filename): """ Callable for KBEntryAttachment.file.upload_to """ return "/kb/%d/%s" % (instance.kb_entry.id, filename) @property def visible_attachments(self): """ Returns a list of visible attachments """ return [{ "name": x.name, "size": x.size, "mtime": x.mtime, "description": x.description } for x in self.kbentryattachment_set.filter( is_hidden=False).order_by("name")] @property def has_visible_attachments(self): return self.kbentryattachment_set.filter(is_hidden=False).exists()
class AS(models.Model): class Meta(object): verbose_name = "AS" verbose_name_plural = "ASes" db_table = "peer_as" app_label = "peer" asn = models.IntegerField("ASN", unique=True) # as-name RPSL Field as_name = models.CharField("AS Name", max_length=64, null=True, blank=True) profile = DocumentReferenceField(ASProfile, null=False, blank=False) project = models.ForeignKey(Project, verbose_name="Project", null=True, blank=True, related_name="as_set") # RPSL descr field description = models.CharField("Description", max_length=64) organisation = models.ForeignKey(Organisation, verbose_name="Organisation") administrative_contacts = models.ManyToManyField( Person, verbose_name="admin-c", related_name="as_administrative_contacts", null=True, blank=True) tech_contacts = models.ManyToManyField(Person, verbose_name="tech-c", related_name="as_tech_contacts", null=True, blank=True) maintainers = models.ManyToManyField(Maintainer, verbose_name="Maintainers", related_name="as_maintainers", null=True, blank=True) routes_maintainers = models.ManyToManyField( Maintainer, verbose_name="Routes Maintainers", related_name="as_route_maintainers", null=True, blank=True) # remarks: will be prepended automatically header_remarks = models.TextField("Header Remarks", null=True, blank=True) # remarks: will be prepended automatically footer_remarks = models.TextField("Footer Remarks", null=True, blank=True) rir = models.ForeignKey(RIR, verbose_name="RIR") # source: tags = TagsField("Tags", null=True, blank=True) rpsl = GridVCSField("rpsl_as") _id_cache = cachetools.TTLCache(maxsize=100, ttl=60) _asn_cache = cachetools.TTLCache(maxsize=100, ttl=60) def __unicode__(self): return u"AS%d (%s)" % (self.asn, self.description) @classmethod @cachetools.cachedmethod(operator.attrgetter("_id_cache"), lock=lambda _: id_lock) def get_by_id(cls, id): asn = AS.objects.filter(id=id)[:1] if asn: return asn[0] return None @classmethod @cachetools.cachedmethod(operator.attrgetter("_asn_cache"), lock=lambda _: id_lock) def get_by_asn(cls, asn): asn = AS.objects.filter(asn=asn)[:1] if asn: return asn[0] return None def get_rpsl(self): sep = "remarks: %s" % ("-" * 72) s = [] s += ["aut-num: AS%s" % self.asn] if self.as_name: s += ["as-name: %s" % self.as_name] if self.description: s += ["descr: %s" % x for x in self.description.split("\n")] s += ["org: %s" % self.organisation.organisation] # Add header remarks if self.header_remarks: s += ["remarks: %s" % x for x in self.header_remarks.split("\n")] # Find AS peers pg = { } # Peer Group -> AS -> peering_point -> [(import, export, localpref, import_med, export_med, remark)] for peer in self.peer_set.filter(status="A"): if peer.peer_group not in pg: pg[peer.peer_group] = {} if peer.remote_asn not in pg[peer.peer_group]: pg[peer.peer_group][peer.remote_asn] = {} if peer.peering_point not in pg[peer.peer_group][peer.remote_asn]: pg[peer.peer_group][peer.remote_asn][peer.peering_point] = [] to_skip = False e_import_med = peer.effective_import_med e_export_med = peer.effective_export_med for R in pg[peer.peer_group][peer.remote_asn][peer.peering_point]: p_import, p_export, localpref, import_med, export_med, remark = R if (peer.import_filter == p_import and peer.export_filter == p_export and e_import_med == import_med and e_export_med == export_med): to_skip = True break if not to_skip: pg[peer.peer_group][peer.remote_asn][peer.peering_point] +=\ [(peer.import_filter, peer.export_filter, peer.effective_local_pref, e_import_med, e_export_med, peer.rpsl_remark)] # Build RPSL inverse_pref = config.peer.rpsl_inverse_pref_style for peer_group in pg: s += [sep] s += [ "remarks: -- %s" % x for x in peer_group.description.split("\n") ] s += [sep] for asn in sorted(pg[peer_group]): add_at = len(pg[peer_group][asn]) != 1 for pp in pg[peer_group][asn]: for R in pg[peer_group][asn][pp]: import_filter, export_filter, localpref, import_med, export_med, remark = R # Prepend import and export with remark when given if remark: s += ["remarks: # %s" % remark] # Build import statement i_s = "import: from AS%d" % asn if add_at: i_s += " at %s" % pp.hostname actions = [] if localpref: pref = (65535 - localpref) if inverse_pref else localpref actions += ["pref=%d;" % pref] if import_med: actions += ["med=%d;" % import_med] if actions: i_s += " action " + " ".join(actions) i_s += " accept %s" % import_filter s += [i_s] # Build export statement e_s = "export: to AS%d" % asn if add_at: e_s += " at %s" % pp.hostname if export_med: e_s += " action med=%d;" % export_med e_s += " announce %s" % export_filter s += [e_s] # Add contacts for c in self.administrative_contacts.order_by("nic_hdl"): s += ["admin-c: %s" % c.nic_hdl] for c in self.tech_contacts.order_by("nic_hdl"): s += ["tech-c: %s" % c.nic_hdl] # Add maintainers for m in self.maintainers.all(): s += ["mnt-by: %s" % m.maintainer] for m in self.routes_maintainers.all(): s += ["mnt-routes: %s" % m.maintainer] # Add footer remarks if self.footer_remarks: s += ["remarks: %s" % x for x in self.footer_remarks.split("\n")] return rpsl_format("\n".join(s)) def touch_rpsl(self): c_rpsl = self.rpsl.read() n_rpsl = self.get_rpsl() if c_rpsl == n_rpsl: return # Not changed self.rpsl.write(n_rpsl) def on_save(self): self.touch_rpsl() @property def dot(self): from .peer import Peer s = ["graph {"] all_peers = Peer.objects.filter(local_asn__exact=self) uplinks = {} peers = {} downlinks = {} for p in all_peers: if p.import_filter == "ANY" and p.export_filter != "ANY": uplinks[p.remote_asn] = p elif p.export_filter == "ANY": downlinks[p.remote_asn] = p else: peers[p.remote_asn] = p asn = "AS%d" % self.asn for subgraph, peers in [("uplinks", uplinks.values()), ("peers", peers.values()), ("downlinks", downlinks.values())]: s += ["subgraph %s {" % subgraph] for p in peers: attrs = [ "taillabel=\" %s\"" % p.import_filter, "headlabel=\" %s\"" % p.export_filter ] if p.import_filter == "ANY": attrs += ["arrowtail=open"] if p.export_filter == "ANY": attrs += ["arrothead=open"] s += [ " %s -- AS%d [%s];" % (asn, p.remote_asn, ",".join(attrs)) ] s += ["}"] s += ["}"] return "\n".join(s) def update_rir_db(self): return self.rir.update_rir_db(self.rpsl, self.maintainers.all()[0])
class ManagedObjectProfile(NOCModel): class Meta(object): verbose_name = _("Managed Object Profile") verbose_name_plural = _("Managed Object Profiles") db_table = "sa_managedobjectprofile" app_label = "sa" ordering = ["name"] name = models.CharField(_("Name"), max_length=64, unique=True) description = models.TextField(_("Description"), blank=True, null=True) level = models.IntegerField(_("Level"), default=25) style = models.ForeignKey(Style, verbose_name=_("Style"), blank=True, null=True, on_delete=models.CASCADE) # Stencils shape = models.CharField(_("Shape"), blank=True, null=True, choices=stencil_registry.choices, max_length=128) # Name restrictions # Regular expression to check name format name_template = models.CharField(_("Name template"), max_length=256, blank=True, null=True) # Suffix for ManagedObject's FQDN fqdn_suffix = models.CharField(_("FQDN suffix"), max_length=256, null=True, blank=True) # Policy for MO address resolution from FQDN address_resolution_policy = models.CharField( _("Address Resolution Policy"), choices=[("D", "Disabled"), ("O", "Once"), ("E", "Enabled")], max_length=1, null=False, blank=False, default="D", ) # Name to address resolver. socket.gethostbyname by default resolver_handler = DocumentReferenceField(Handler, null=True, blank=True) # @todo: Name validation function # FM settings enable_ping = models.BooleanField(_("Enable ping check"), default=True) ping_interval = models.IntegerField(_("Ping interval"), default=60) ping_policy = models.CharField( _("Ping check policy"), max_length=1, choices=[("f", "First Success"), ("a", "All Successes")], default="f", ) ping_size = models.IntegerField(_("Ping packet size"), default=64) ping_count = models.IntegerField(_("Ping packets count"), default=3) ping_timeout_ms = models.IntegerField(_("Ping timeout (ms)"), default=1000) report_ping_rtt = models.BooleanField(_("Report RTT"), default=False) report_ping_attempts = models.BooleanField(_("Report Attempts"), default=False) # Additional alarm weight weight = models.IntegerField("Alarm weight", default=0) # card = models.CharField(_("Card name"), max_length=256, blank=True, null=True, default="managedobject") card_title_template = models.CharField( _("Card title template"), max_length=256, default="{{ object.object_profile.name }}: {{ object.name }}", ) # Enable box discovery. # Box discovery launched on system changes enable_box_discovery = models.BooleanField(default=True) # Interval of periodic discovery when no changes registered box_discovery_interval = models.IntegerField(default=86400) # Retry interval in case of failure (Object is down) box_discovery_failed_interval = models.IntegerField(default=10800) # Running policy for box discovery box_discovery_running_policy = models.CharField( _("Box Running Policy"), choices=[("R", _("Require Up")), ("r", _("Require if enabled")), ("i", _("Ignore"))], max_length=1, default="R", ) # Start box discovery when system start registered box_discovery_on_system_start = models.BooleanField(default=False) # after delay box_discovery_system_start_delay = models.IntegerField(default=300) # Start box discovery when config change registered box_discovery_on_config_changed = models.BooleanField(default=False) # After delay box_discovery_config_changed_delay = models.IntegerField(default=300) # Check profile enable_box_discovery_profile = models.BooleanField(default=True) # Collect version info enable_box_discovery_version = models.BooleanField(default=False) # Collect capabilities enable_box_discovery_caps = models.BooleanField(default=False) # Collect interface settings enable_box_discovery_interface = models.BooleanField(default=False) # Collect chassis ID information enable_box_discovery_id = models.BooleanField(default=False) # Collect config enable_box_discovery_config = models.BooleanField(default=False) # Collect hardware configuration enable_box_discovery_asset = models.BooleanField(default=False) # Process topology from NRI enable_box_discovery_nri = models.BooleanField(default=False) # Process NRI portmapping enable_box_discovery_nri_portmap = models.BooleanField(default=False) # Process NRI service binding enable_box_discovery_nri_service = models.BooleanField(default=False) # VPN discovery (interface) enable_box_discovery_vpn_interface = models.BooleanField(default=False) # VPN discovery (MPLS) enable_box_discovery_vpn_mpls = models.BooleanField(default=False) # VPN discovery (MPLS) enable_box_discovery_vpn_confdb = models.BooleanField(default=False) # IP discovery (interface) enable_box_discovery_address_interface = models.BooleanField(default=False) # IP discovery (Management) enable_box_discovery_address_management = models.BooleanField( default=False) # IP discovery (DHCP) enable_box_discovery_address_dhcp = models.BooleanField(default=False) # IP discovery (neighbbors) enable_box_discovery_address_neighbor = models.BooleanField(default=False) # IP discovery (ConfDB) enable_box_discovery_address_confdb = models.BooleanField(default=False) # IP discovery (interface) enable_box_discovery_prefix_interface = models.BooleanField(default=False) # IP discovery (neighbbors) enable_box_discovery_prefix_neighbor = models.BooleanField(default=False) # Prefix discovery (ConfDB) enable_box_discovery_prefix_confdb = models.BooleanField(default=False) # Collect static vlans enable_box_discovery_vlan = models.BooleanField(default=False) # L2 topology using BFD enable_box_discovery_bfd = models.BooleanField(default=False) # L2 topology using CDP enable_box_discovery_cdp = models.BooleanField(default=False) # L2 topology using Huawei NDP enable_box_discovery_huawei_ndp = models.BooleanField(default=False) # L2 topology using MikroTik NDP enable_box_discovery_mikrotik_ndp = models.BooleanField(default=False) # L2 topology using Foundry FDP enable_box_discovery_fdp = models.BooleanField(default=False) # L2 topology using LLDP enable_box_discovery_lldp = models.BooleanField(default=False) # L2 topology using OAM enable_box_discovery_oam = models.BooleanField(default=False) # L2 topology using REP enable_box_discovery_rep = models.BooleanField(default=False) # L2 topology using STP enable_box_discovery_stp = models.BooleanField(default=False) # L2 topology using UDLD enable_box_discovery_udld = models.BooleanField(default=False) # L2 topology using LACP enable_box_discovery_lacp = models.BooleanField(default=False) # Enable SLA probes discovery enable_box_discovery_sla = models.BooleanField(default=False) # Enable CPE discovery enable_box_discovery_cpe = models.BooleanField(default=False) # Enable MAC discovery enable_box_discovery_mac = models.BooleanField(default=False) # Enable metrics enable_box_discovery_metrics = models.BooleanField(default=False) # Enable Housekeeping enable_box_discovery_hk = models.BooleanField(default=False) # Enable CPE status enable_box_discovery_cpestatus = models.BooleanField(default=False) # Enable Box CPE status policy box_discovery_cpestatus_policy = models.CharField( _("CPE Status Policy"), max_length=1, choices=[("S", "Status Only"), ("F", "Full")], default="S", ) # Enable periodic discovery. # Periodic discovery launched repeatedly enable_periodic_discovery = models.BooleanField(default=True) # Periodic discovery repeat interval periodic_discovery_interval = models.IntegerField(default=300) periodic_discovery_running_policy = models.CharField( _("Periodic Running Policy"), choices=[("R", _("Require Up")), ("r", _("Require if enabled")), ("i", _("Ignore"))], max_length=1, default="R", ) # Collect uptime enable_periodic_discovery_uptime = models.BooleanField(default=False) # Collect interface status enable_periodic_discovery_interface_status = models.BooleanField( default=False) # Collect mac address table enable_periodic_discovery_mac = models.BooleanField(default=False) # Collect metrics enable_periodic_discovery_metrics = models.BooleanField(default=False) # Enable CPE status enable_periodic_discovery_cpestatus = models.BooleanField(default=False) # CPE status discovery settings periodic_discovery_cpestatus_policy = models.CharField( _("CPE Status Policy"), max_length=1, choices=[("S", "Status Only"), ("F", "Full")], default="S", ) # Collect ARP cache # enable_periodic_discovery_ip = models.BooleanField(default=False) # clear_links_on_platform_change = models.BooleanField(default=False) clear_links_on_serial_change = models.BooleanField(default=False) # CPE discovery settings cpe_segment_policy = models.CharField( _("CPE Segment Policy"), max_length=1, choices=[("C", "From controller"), ("L", "From linked object")], default="C", ) cpe_cooldown = models.IntegerField(_("CPE cooldown, days"), default=0) cpe_profile = models.ForeignKey("self", verbose_name="Object Profile", blank=True, null=True, on_delete=models.CASCADE) cpe_auth_profile = models.ForeignKey(AuthProfile, verbose_name="Auth Profile", null=True, blank=True, on_delete=models.CASCADE) # hk_handler = DocumentReferenceField(Handler, null=True, blank=True) # MAC collection settings # Collect all MAC addresses mac_collect_all = models.BooleanField(default=False) # Collect MAC addresses if permitted by interface profile mac_collect_interface_profile = models.BooleanField(default=True) # Collect MAC addresses from management VLAN mac_collect_management = models.BooleanField(default=False) # Collect MAC addresses from multicast VLAN mac_collect_multicast = models.BooleanField(default=False) # Collect MAC from designated VLANs (NetworkSegment/NetworkSegmentProfile) mac_collect_vcfilter = models.BooleanField(default=False) # access_preference = models.CharField( "Access Preference", max_length=8, choices=[("S", "SNMP Only"), ("C", "CLI Only"), ("SC", "SNMP, CLI"), ("CS", "CLI, SNMP")], default="CS", ) # Autosegmentation policy autosegmentation_policy = models.CharField( max_length=1, choices=[ # Do not allow to move object by autosegmentation ("d", "Do not segmentate"), # Allow moving of object to another segment # by autosegmentation process ("e", "Allow autosegmentation"), # Move seen objects to this object's segment ("o", "Segmentate to existing segment"), # Expand autosegmentation_segment_name template, # ensure that children segment with same name exists # then move seen objects to this segment. # Following context variables are availale: # * object - this object # * interface - interface on which remote_object seen from object # * remote_object - remote object name # To create single segment use templates like {{object.name}} # To create segments on per-interface basic use # names like {{object.name}}-{{interface.name}} ("c", "Segmentate to child segment"), ], default="d", ) # Objects can be autosegmented by *o* and *i* policy # only if their level below *autosegmentation_level_limit* # 0 - disable autosegmentation_level_limit = models.IntegerField(_("Level"), default=0) # Jinja2 tempplate for segment name # object and interface context variables are exist autosegmentation_segment_name = models.CharField(max_length=255, default="{{object.name}}") # Integration with external NRI and TT systems # Reference to remote system object has been imported from remote_system = DocumentReferenceField(RemoteSystem, null=True, blank=True) # Object id in remote system remote_id = models.CharField(max_length=64, null=True, blank=True) # Object id in BI bi_id = models.BigIntegerField(unique=True) # Caps discovery settings caps_profile = DocumentReferenceField( CapsProfile, null=False, blank=False, default=CapsProfile.get_default_profile) # Object alarms can be escalated escalation_policy = models.CharField( "Escalation Policy", max_length=1, choices=[("E", "Enable"), ("D", "Disable"), ("R", "Escalate as Depended")], default="E", ) # Raise alarms on discovery problems box_discovery_alarm_policy = models.CharField( "Box Discovery Alarm Policy", max_length=1, choices=[("E", "Enable"), ("D", "Disable")], default="E", ) periodic_discovery_alarm_policy = models.CharField( "Periodic Discovery Alarm Policy", max_length=1, choices=[("E", "Enable"), ("D", "Disable")], default="E", ) box_discovery_fatal_alarm_weight = models.IntegerField( "Box Fatal Alarm Weight", default=10) box_discovery_alarm_weight = models.IntegerField("Box Alarm Weight", default=1) periodic_discovery_fatal_alarm_weight = models.IntegerField( "Box Fatal Alarm Weight", default=10) periodic_discovery_alarm_weight = models.IntegerField( "Periodic Alarm Weight", default=1) # Telemetry box_discovery_telemetry_sample = models.IntegerField( "Box Discovery Telemetry Sample", default=0) periodic_discovery_telemetry_sample = models.IntegerField( "Box Discovery Telemetry Sample", default=0) # CLI Sessions cli_session_policy = models.CharField("CLI Session Policy", max_length=1, choices=[("E", "Enable"), ("D", "Disable")], default="E") # CLI privilege policy cli_privilege_policy = models.CharField( "CLI Privilege Policy", max_length=1, choices=[("E", "Raise privileges"), ("D", "Do not raise")], default="E", ) # Event processing policy event_processing_policy = models.CharField( "Event Processing Policy", max_length=1, choices=[("E", "Process Events"), ("D", "Drop events")], default="E", ) # Collect and archive syslog events syslog_archive_policy = models.CharField( "SYSLOG Archive Policy", max_length=1, choices=[("E", "Enable"), ("D", "Disable")], default="D", ) # Cache protocol neighbors up to *neighbor_cache_ttl* seconds # 0 - disable cache neighbor_cache_ttl = models.IntegerField("Neighbor Cache TTL", default=0) # VPN discovery profiles vpn_profile_interface = DocumentReferenceField(VPNProfile, null=True, blank=True) vpn_profile_mpls = DocumentReferenceField(VPNProfile, null=True, blank=True) vpn_profile_confdb = DocumentReferenceField(VPNProfile, null=True, blank=True) # Prefix discovery profiles prefix_profile_interface = DocumentReferenceField(PrefixProfile, null=True, blank=True) prefix_profile_neighbor = DocumentReferenceField(PrefixProfile, null=True, blank=True) prefix_profile_confdb = DocumentReferenceField(PrefixProfile, null=True, blank=True) # Address discovery profiles address_profile_interface = DocumentReferenceField(AddressProfile, null=True, blank=True) address_profile_management = DocumentReferenceField(AddressProfile, null=True, blank=True) address_profile_dhcp = DocumentReferenceField(AddressProfile, null=True, blank=True) address_profile_neighbor = DocumentReferenceField(AddressProfile, null=True, blank=True) address_profile_confdb = DocumentReferenceField(AddressProfile, null=True, blank=True) # Config policy config_policy = models.CharField( _("Config Policy"), max_length=1, choices=[ ("s", "Script"), ("S", "Script, Download"), ("D", "Download, Script"), ("d", "Download"), ], default="s", ) config_download_storage = DocumentReferenceField(ExtStorage, null=True, blank=True) config_download_template = models.ForeignKey( Template, verbose_name=_("Config Mirror Template"), blank=True, null=True, related_name="config_download_objects_set", on_delete=models.CASCADE, ) config_fetch_policy = models.CharField( _("Config Fetch Policy"), max_length=1, choices=[("s", "Startup"), ("r", "Running")], default="r", ) # Config mirror settings config_mirror_storage = DocumentReferenceField(ExtStorage, null=True, blank=True) config_mirror_template = models.ForeignKey( Template, verbose_name=_("Config Mirror Template"), blank=True, null=True, related_name="config_mirror_objects_set", on_delete=models.CASCADE, ) config_mirror_policy = models.CharField( _("Config Mirror Policy"), max_length=1, choices=[("D", "Disable"), ("A", "Always"), ("C", "Change")], default="C", ) # Config validation settings config_validation_policy = models.CharField( _("Config Validation Policy"), max_length=1, choices=[("D", "Disable"), ("A", "Always"), ("C", "Change")], default="C", ) object_validation_policy = DocumentReferenceField(ObjectValidationPolicy, null=True, blank=True) # Interface discovery settings interface_discovery_policy = models.CharField( _("Interface Discovery Policy"), max_length=1, choices=[ ("s", "Script"), ("S", "Script, ConfDB"), ("C", "ConfDB, Script"), ("c", "ConfDB"), ], default="s", ) # Caps discovery settings caps_discovery_policy = models.CharField( _("Caps Discovery Policy"), max_length=1, choices=[ ("s", "Script"), ("S", "Script, ConfDB"), ("C", "ConfDB, Script"), ("c", "ConfDB"), ], default="s", ) # VLAN discovery settings vlan_discovery_policy = models.CharField( _("VLAN Discovery Policy"), max_length=1, choices=[ ("s", "Script"), ("S", "Script, ConfDB"), ("C", "ConfDB, Script"), ("c", "ConfDB"), ], default="s", ) # Behaviour on new platform detection in version check new_platform_creation_policy = models.CharField( _("New Platform Creation Policy"), max_length=1, choices=[("C", "Create"), ("A", "Alarm")], default="C", ) # Behavior on denied firmware detection denied_firmware_policy = models.CharField( _("Firmware Policy"), max_length=1, choices=[ ("I", "Ignore"), ("s", "Ignore&Stop"), ("A", "Raise Alarm"), ("S", "Raise Alarm&Stop"), ], default="I", ) # Beef collection settings beef_storage = DocumentReferenceField(ExtStorage, null=True, blank=True) beef_path_template = models.ForeignKey( Template, verbose_name=_("Beef Path Template"), blank=True, null=True, related_name="beef_objects_set", on_delete=models.CASCADE, ) beef_policy = models.CharField( _("Beef Policy"), max_length=1, choices=[("D", "Disable"), ("A", "Always"), ("C", "Change")], default="D", ) # ConfDB policies confdb_raw_policy = models.CharField( _("ConfDB Raw Policy"), max_length=1, choices=[("D", "Disable"), ("E", "Enable")], default="D", ) # metrics = PickledField(blank=True) # tags = TagsField("Tags", null=True, blank=True) _id_cache = cachetools.TTLCache(maxsize=100, ttl=60) _bi_id_cache = cachetools.TTLCache(maxsize=100, ttl=60) def __str__(self): return self.name @classmethod @cachetools.cachedmethod(operator.attrgetter("_id_cache"), lock=lambda _: id_lock) def get_by_id(cls, id): mop = ManagedObjectProfile.objects.filter(id=id)[:1] if mop: return mop[0] else: return None @classmethod @cachetools.cachedmethod(operator.attrgetter("_bi_id_cache"), lock=lambda _: id_lock) def get_by_bi_id(cls, id): mop = ManagedObjectProfile.objects.filter(bi_id=id)[:1] if mop: return mop[0] else: return None def iter_changed_datastream(self, changed_fields=None): from noc.sa.models.managedobject import ManagedObject if config.datastream.enable_managedobject and changed_fields.intersection( {"name", "remote_system", "remote_id"}): for mo_id in ManagedObject.objects.filter( object_profile=self).values_list("id", flat=True): yield "managedobject", mo_id if config.datastream.enable_cfgping and changed_fields.intersection({ "enable_ping", "ping_interval", "ping_policy", "ping_size", "ping_count", "ping_timeout_ms", "report_ping_rtt", "report_ping_attempts", "event_processing_policy", }): for mo_id in ManagedObject.objects.filter( object_profile=self).values_list("id", flat=True): yield "cfgping", mo_id if config.datastream.enable_cfgsyslog and changed_fields.intersection( {"event_processing_policy", "syslog_archive_policy"}): for mo_id in ManagedObject.objects.filter( object_profile=self).values_list("id", flat=True): yield "cfgsyslog", mo_id if config.datastream.enable_cfgtrap and "event_processing_policy" in changed_fields: for mo_id in ManagedObject.objects.filter( object_profile=self).values_list("id", flat=True): yield "cfgtrap", mo_id def iter_pools(self): """ Iterate all pool instances covered by profile """ for mo in self.managedobject_set.order_by("pool").distinct("pool"): yield mo.pool def on_save(self): box_changed = self.initial_data[ "enable_box_discovery"] != self.enable_box_discovery periodic_changed = (self.initial_data["enable_periodic_discovery"] != self.enable_periodic_discovery) access_changed = (self.initial_data["access_preference"] != self.access_preference) or ( self.initial_data["cli_privilege_policy"] != self.cli_privilege_policy) if box_changed or periodic_changed: call_later( "noc.sa.models.managedobjectprofile.apply_discovery_jobs", profile_id=self.id, box_changed=box_changed, periodic_changed=periodic_changed, ) if access_changed: cache.delete_many([ "cred-%s" % x for x in self.managedobject_set.values_list("id", flat=True) ]) def can_escalate(self, depended=False): """ Check alarms on objects within profile can be escalated :return: """ if self.escalation_policy == "R": return bool(depended) else: return self.escalation_policy == "E" def can_create_box_alarms(self): return self.box_discovery_alarm_policy == "E" def can_create_periodic_alarms(self): return self.periodic_discovery_alarm_policy == "E" def can_cli_session(self): return self.cli_session_policy == "E" def save(self, *args, **kwargs): # Validate MeticType for object profile if self.metrics: try: self.metrics = m_valid.clean(self.metrics) except ValueError as e: raise ValueError(e) super(ManagedObjectProfile, self).save(*args, **kwargs) @classmethod def get_max_metrics_interval(cls, managed_object_profiles=None): Q = models.Q op_query = (Q(enable_box_discovery_metrics=True) & Q(enable_box_discovery=True)) | ( Q(enable_periodic_discovery=True) & Q(enable_periodic_discovery_metrics=True)) if managed_object_profiles: op_query &= Q(id__in=managed_object_profiles) r = set() for mop in (ManagedObjectProfile.objects.filter(op_query).exclude( metrics=[]).exclude(metrics=None)): if not mop.metrics: continue for m in mop.metrics: if m["enable_box"]: r.add(mop.box_discovery_interval) if m["enable_periodic"]: r.add(mop.periodic_discovery_interval) return max(r) if r else 0
class AuthProfile(models.Model): class Meta: verbose_name = "Auth Profile" verbose_name_plural = "Auth Profiles" db_table = "sa_authprofile" app_label = "sa" ordering = ["name"] name = models.CharField("Name", max_length=64, unique=True) description = models.TextField("Description", null=True, blank=True) type = models.CharField("Name", max_length=1, choices=[("G", "Local Group"), ("R", "RADIUS"), ("T", "TACACS+"), ("L", "LDAP"), ("S", "Suggest")]) user = models.CharField("User", max_length=32, blank=True, null=True) password = models.CharField("Password", max_length=32, blank=True, null=True) super_password = models.CharField("Super Password", max_length=32, blank=True, null=True) snmp_ro = models.CharField("RO Community", blank=True, null=True, max_length=64) snmp_rw = models.CharField("RW Community", blank=True, null=True, max_length=64) # Integration with external NRI systems # Reference to remote system object has been imported from remote_system = DocumentReferenceField(RemoteSystem, null=True, blank=True) # Object id in remote system remote_id = models.CharField(max_length=64, null=True, blank=True) # Object id in BI bi_id = models.BigIntegerField(unique=True) tags = TagsField("Tags", null=True, blank=True) _id_cache = cachetools.TTLCache(maxsize=100, ttl=60) def __unicode__(self): return self.name @classmethod @cachetools.cachedmethod(operator.attrgetter("_id_cache"), lock=lambda _: id_lock) def get_by_id(cls, id): try: return AuthProfile.objects.get(id=id) except AuthProfile.DoesNotExist: return None def on_save(self): if not self.enable_suggest: cache.delete_many([ "cred-%s" % x for x in self.managedobject_set.values_list("id", flat=True) ]) @property def enable_suggest(self): return self.type == "S" def iter_snmp(self): """ Yield all possible snmp_ro, snmp_rw tuples :return: """ if self.enable_suggest: for s in self.authprofilesuggestsnmp_set.all(): yield s.snmp_ro, s.snmp_rw def iter_cli(self): """ Yield all possible user, password, super_password :return: """ if self.enable_suggest: for s in self.authprofilesuggestcli_set.all(): yield s.user, s.password, s.super_password
def forwards(self): db.add_column("sa_terminationgroup", "tags", TagsField("Tags", null=True, blank=True))
class ManagedObjectSelector(models.Model): class Meta: verbose_name = _("Managed Object Selector") verbose_name_plural = _("Managed Object Selectors") db_table = "sa_managedobjectselector" app_label = "sa" ordering = ["name"] name = models.CharField(_("Name"), max_length=64, unique=True) description = models.TextField(_("Description"), blank=True, null=True) is_enabled = models.BooleanField(_("Is Enabled"), default=True) filter_id = models.IntegerField(_("Filter by ID"), null=True, blank=True) filter_name = models.CharField(_("Filter by Name (REGEXP)"), max_length=256, null=True, blank=True, validators=[check_re]) filter_managed = models.NullBooleanField(_("Filter by Is Managed"), null=True, blank=True, default=True) filter_pool = DocumentReferenceField(Pool, null=True, blank=True) filter_profile = DocumentReferenceField(Profile, null=True, blank=True) filter_vendor = DocumentReferenceField(Vendor, null=True, blank=True) filter_platform = DocumentReferenceField(Platform, null=True, blank=True) filter_version = DocumentReferenceField(Firmware, null=True, blank=True) filter_object_profile = models.ForeignKey( ManagedObjectProfile, verbose_name=_("Filter by Object's Profile"), null=True, blank=True) filter_address = models.CharField(_("Filter by Address (REGEXP)"), max_length=256, null=True, blank=True, validators=[check_re]) filter_prefix = models.ForeignKey(PrefixTable, verbose_name=_("Filter by Prefix Table"), null=True, blank=True) filter_administrative_domain = models.ForeignKey( AdministrativeDomain, verbose_name=_("Filter by Administrative Domain"), null=True, blank=True) filter_vrf = models.ForeignKey("ip.VRF", verbose_name=_("Filter by VRF"), null=True, blank=True) filter_vc_domain = models.ForeignKey("vc.VCDomain", verbose_name=_("Filter by VC Domain"), null=True, blank=True) filter_service_group = DocumentReferenceField(ResourceGroup, null=True, blank=True) filter_client_group = DocumentReferenceField(ResourceGroup, null=True, blank=True) filter_tt_system = DocumentReferenceField(TTSystem, null=True, blank=True) filter_user = models.CharField(_("Filter by User (REGEXP)"), max_length=256, null=True, blank=True) filter_remote_path = models.CharField(_("Filter by Remote Path (REGEXP)"), max_length=256, null=True, blank=True, validators=[check_re]) filter_description = models.CharField(_("Filter by Description (REGEXP)"), max_length=256, null=True, blank=True, validators=[check_re]) filter_tags = TagsField(_("Filter By Tags"), null=True, blank=True) source_combine_method = models.CharField(_("Source Combine Method"), max_length=1, default="O", choices=[("A", "AND"), ("O", "OR")]) sources = models.ManyToManyField("self", verbose_name=_("Sources"), symmetrical=False, null=True, blank=True, related_name="sources_set") _id_cache = cachetools.TTLCache(maxsize=100, ttl=60) def __unicode__(self): return self.name @classmethod @cachetools.cachedmethod(operator.attrgetter("_id_cache"), lock=lambda _: id_lock) def get_by_id(cls, id): try: return ManagedObjectSelector.objects.get(id=id) except ManagedObjectSelector.DoesNotExist: return None def on_save(self): # Rebuild selector cache SelectorCache.refresh() def on_delete(self): # Rebuild selector cache SelectorCache.refresh() @property def Q(self): """ Returns Q object which can be applied to ManagedObject.objects.filter """ # Exclude NOC internal objects q = ~Q(profile__in=list(Profile.objects.filter( name__startswith="NOC."))) # Exclude objects being wiped q &= ~Q(name__startswith="wiping-") # Filter by is_managed if self.filter_managed is not None: q &= Q(is_managed=self.filter_managed) # Filter by ID if self.filter_id: q &= Q(id=self.filter_id) # Filter by pool if self.filter_pool: q &= Q(pool=self.filter_pool) # Filter by name (regex) if self.filter_name: q &= Q(name__regex=self.filter_name) # Filter by profile if self.filter_profile: q &= Q(profile=self.filter_profile) # Filter by vendor if self.filter_vendor: q &= Q(vendor=self.filter_vendor) # Filter by platform if self.filter_platform: q &= Q(platform=self.filter_platform) # Filter by version if self.filter_version: q &= Q(version=self.filter_version) # Filter by ttsystem if self.filter_tt_system: q &= Q(tt_system=self.filter_tt_system) # Filter by object's profile if self.filter_object_profile: q &= Q(object_profile=self.filter_object_profile) # Filter by address (regex) if self.filter_address: q &= Q(address__regex=self.filter_address) # Filter by prefix table if self.filter_prefix: q &= SQL(""" EXISTS ( SELECT * FROM main_prefixtableprefix p WHERE table_id=%d AND address::inet <<= p.prefix)""" % self.filter_prefix.id) # Filter by administrative domain if self.filter_administrative_domain: dl = AdministrativeDomain.get_nested_ids( self.filter_administrative_domain) q &= SQL(""" "sa_managedobject"."administrative_domain_id" IN (%s) """ % ", ".join(str(x) for x in dl)) # Filter by VRF if self.filter_vrf: q &= Q(vrf=self.filter_vrf) # Filter by VC domain if self.filter_vc_domain: q &= Q(vc_domain=self.filter_vc_domain) # Filter by termination group if self.filter_service_group: q &= Q(effective_service_groups=self.filter_service_group.id) # Filter by termination group if self.filter_client_group: q &= Q(effective_client_groups=self.filter_client_group.id) # Filter by username if self.filter_user: q &= Q(user__regex=self.filter_user) # Filter by remote path if self.filter_remote_path: q &= Q(remote_path__regex=self.filter_remote_path) # Filter by description if self.filter_description: q &= Q(description__regex=self.filter_description) # Restrict to tags when necessary if self.filter_tags: q &= QTags(self.filter_tags) # Restrict to attributes when necessary for s in self.managedobjectselectorbyattribute_set.all(): q &= SQL(""" ("sa_managedobject"."id" IN ( SELECT managed_object_id FROM sa_managedobjectattribute WHERE key ~ %s AND value ~ %s )) """ % (adapt(s.key_re).getquoted(), adapt(s.value_re).getquoted())) # Restrict to sources if self.sources.count(): if self.source_combine_method == "A": # AND for s in self.sources.all(): q &= s.Q else: # OR ql = list(self.sources.all()) q = ql.pop(0).Q for qo in ql: q |= qo.Q return q EXPR_MAP = [ # Field, var, op ["filter_id", "id", "=="], ["filter_name", "name", "~"], ["filter_pool", "pool", "=="], ["filter_profile", "profile", "=="], ["filter_object_profile", "object_profile", "=="], ["filter_address", "address", "~"], ["filter_prefix", "address", "IN"], ["filter_administrative_domain", "administrative_domain", "IN"], ["filter_vrf", "vrf", "=="], ["filter_vc_domain", "vc_domain", "=="], ["filter_service_group", "effective_service_groups", "=="], ["filter_client_group", "effective_client_groups", "=="], ["filter_user", "user", "=="], ["filter_remote_path", "remote_path", "~"], ["filter_description", "description", "~"], ["filter_tags", "tags", "CONTAINS"] ] @property def expr(self): """ Return selector as text expression """ def q(s): if isinstance(s, six.integer_types): return str(s) elif isinstance(s, (list, tuple)): s = [q(x) for x in s] return u"[%s]" % ", ".join(s) else: return u"\"%s\"" % unicode(s).replace("\\", "\\\\").replace( "'", "\\'") expr = [] # Filter by is_managed if self.filter_managed is not None: if self.filter_managed: expr += [u"IS MANAGED"] else: expr += [u"IS NOT MANAGED"] # Apply filters for f, n, op in self.EXPR_MAP: v = getattr(self, f) if v: expr += [u"%s %s %s" % (n, op, q(v))] # Apply attributes filters for s in self.managedobjectselectorbyattribute_set.all(): expr += [u"attr(%s) ~ %s" % (q(s.key_re), q(s.value_re))] expr = [u" AND ".join(expr)] # Restrict to sources if self.sources.count(): for s in self.sources.all(): expr += [s.expr] op = u" AND " if self.source_combine_method == "A" else u" OR " expr = [op.join(u"(%s)" % x for x in expr)] return expr[0] @property def managed_objects(self): """ Returns queryset containing managed objects :return: """ from .managedobject import ManagedObject return ManagedObject.objects.filter(self.Q) def match(self, managed_object): """ Check managed object matches selector :param managed_object: :return: """ return self.managed_objects.filter(id=managed_object.id).exists() def __contains__(self, managed_object): """ "managed_object in selector" :param managed_object: :return: """ return self.match(managed_object) @classmethod def resolve_expression(cls, s): """ Resolve expression to a list of object. Expression must be string or list. Elements must be one of: * string starting with @ - treated as selector name * string containing numbers - treated as object's id * string - managed object name. * string - IPv4 or IPv6 address - management address Raises ManagedObject.DoesNotExists if object is not found. Raises ManagedObjectSelector.DoesNotExists if selector is not found :param cls: :param s: :return: """ from .managedobject import ManagedObject if type(s) in (int, long, str, unicode): s = [s] if type(s) != list: raise ValueError("list required") objects = set() for so in s: if not isinstance(so, six.string_types): so = str(so) if so.startswith("@"): # Selector expression: @<selector name> o = ManagedObjectSelector.objects.get(name=so[1:]) objects |= set(o.managed_objects) else: # Search by name q = Q(name=so) if is_int(so): # Search by id q |= Q(id=int(so)) if is_ipv4(so) or is_ipv6(so): q |= Q(address=so) o = ManagedObject.objects.get(q) objects.add(o) return list(objects)
class VRF(models.Model): """ VRF """ class Meta(object): verbose_name = _("VRF") verbose_name_plural = _("VRFs") db_table = "ip_vrf" app_label = "ip" ordering = ["name"] name = models.CharField(_("VRF"), unique=True, max_length=64, help_text=_("Unique VRF Name")) profile = DocumentReferenceField(VPNProfile) vrf_group = models.ForeignKey(VRFGroup, verbose_name=_("VRF Group"), null=True, blank=True) rd = models.CharField( _("RD"), max_length=21, validators=[check_rd], null=True, blank=True, help_text=_("Route Distinguisher in form of ASN:N or IP:N")) # RFC2685-compatible VPN id vpn_id = models.CharField(_("VPN ID"), max_length=15, help_text=_("RFC2685 compatible VPN ID"), unique=True) afi_ipv4 = models.BooleanField(_("IPv4"), default=True, help_text=_("Enable IPv4 Address Family")) afi_ipv6 = models.BooleanField(_("IPv6"), default=False, help_text=_("Enable IPv6 Address Family")) project = models.ForeignKey(Project, verbose_name="Project", null=True, blank=True, related_name="vrf_set") description = models.TextField(_("Description"), blank=True, null=True) tt = models.IntegerField(_("TT"), blank=True, null=True, help_text=_("Ticket #")) tags = TagsField(_("Tags"), null=True, blank=True) state = DocumentReferenceField(State, null=True, blank=True) allocated_till = models.DateField( _("Allocated till"), null=True, blank=True, help_text=_("VRF temporary allocated till the date")) source = models.CharField("Source", max_length=1, choices=[("M", "Manual"), ("i", "Interface"), ("m", "MPLS")], null=False, blank=False, default="M") GLOBAL_RD = "0:0" IPv4_ROOT = "0.0.0.0/0" IPv6_ROOT = "::/0" def __unicode__(self): if self.rd == self.GLOBAL_RD: return u"global" else: return self.name _id_cache = cachetools.TTLCache(maxsize=1000, ttl=60) _vpn_id_cache = cachetools.TTLCache(maxsize=1000, ttl=60) @classmethod @cachetools.cachedmethod(operator.attrgetter("_id_cache"), lock=lambda _: id_lock) def get_by_id(cls, id): vrf = VRF.objects.filter(id=id)[:1] if vrf: return vrf[0] return None @classmethod @cachetools.cachedmethod(operator.attrgetter("_vpn_id_cache"), lock=lambda _: id_lock) def get_by_vpn_id(cls, vpn_id): vrf = VRF.objects.filter(vpn_id=vpn_id)[:1] if vrf: return vrf[0] return None def get_absolute_url(self): return site.reverse("ip:vrf:change", self.id) @classmethod def get_global(cls): """ Returns VRF 0:0 """ return VRF.get_by_vpn_id(cls.GLOBAL_RD) def save(self, **kwargs): """ Create root entries for all enabled AFIs """ from .prefix import Prefix # Generate unique rd, if empty if not self.vpn_id: vdata = {"type": "VRF", "name": self.name, "rd": self.rd} self.vpn_id = get_vpn_id(vdata) if self.initial_data["id"]: # Delete empty ipv4 root if AFI changed if self.initial_data.get( "afi_ipv4") != self.afi_ipv4 and not self.afi_ipv4: root = Prefix.objects.filter(vrf=self, afi="4", prefix=self.IPv4_ROOT)[:1] if root: root = root[0] if root.is_empty(): root.disable_delete_protection() root.delete() else: # Cannot change until emptied self.afi_ipv4 = True # Delete empty ipv4 root if AFI changed if self.initial_data.get( "afi_ipv6") != self.afi_ipv6 and not self.afi_ipv6: root = Prefix.objects.filter(vrf=self, afi="6", prefix=self.IPv6_ROOT)[:1] if root: root = root[0] if root.is_empty(): root.disable_delete_protection() root.delete() else: # Cannot change until emptied self.afi_ipv6 = True # Save VRF super(VRF, self).save(**kwargs) if self.afi_ipv4: # Create IPv4 root, if not exists Prefix.objects.get_or_create( vrf=self, afi="4", prefix=self.IPv4_ROOT, defaults={ "description": "IPv4 Root", "profile": self.profile.default_prefix_profile }) if self.afi_ipv6: # Create IPv6 root, if not exists Prefix.objects.get_or_create( vrf=self, afi="6", prefix=self.IPv6_ROOT, defaults={ "description": "IPv6 Root", "profile": self.profile.default_prefix_profile }) def get_index(self): """ Full-text search """ content = [self.name, str(self.rd)] card = "VRF %s. RD %s" % (self.name, self.rd) if self.description: content += [self.description] card += " (%s)" % self.description r = { "id": "ip.vrf:%s" % self.id, "title": self.name, "content": "\n".join(content), "card": card } if self.tags: r["tags"] = self.tags return r @classmethod def get_search_result_url(cls, obj_id): return "/api/card/view/vrf/%s/" % obj_id def delete(self, *args, **kwargs): # Cleanup prefixes self.afi_ipv4 = False self.afi_ipv6 = False self.save() # Delete super(VRF, self).delete(*args, **kwargs)
class AdministrativeDomain(models.Model): """ Administrative Domain """ class Meta: verbose_name = _("Administrative Domain") verbose_name_plural = _("Administrative Domains") db_table = "sa_administrativedomain" app_label = "sa" ordering = ["name"] name = models.CharField(_("Name"), max_length=255, unique=True) parent = models.ForeignKey("self", verbose_name="Parent", null=True, blank=True) description = models.TextField(_("Description"), null=True, blank=True) default_pool = DocumentReferenceField(Pool, null=True, blank=True) # Integration with external NRI systems # Reference to remote system object has been imported from remote_system = DocumentReferenceField(RemoteSystem, null=True, blank=True) # Object id in remote system remote_id = models.CharField(max_length=64, null=True, blank=True) # Object id in BI bi_id = models.BigIntegerField(unique=True) tags = TagsField("Tags", null=True, blank=True) _id_cache = cachetools.TTLCache(maxsize=1000, ttl=60) _bi_id_cache = cachetools.TTLCache(maxsize=1000, ttl=60) _path_cache = cachetools.TTLCache(maxsize=1000, ttl=60) _nested_cache = cachetools.TTLCache(maxsize=1000, ttl=60) def __unicode__(self): return self.name @classmethod @cachetools.cachedmethod(operator.attrgetter("_id_cache"), lock=lambda _: id_lock) def get_by_id(cls, id): ad = AdministrativeDomain.objects.filter(id=id)[:1] if ad: return ad[0] else: return None @classmethod @cachetools.cachedmethod(operator.attrgetter("_bi_id_cache"), lock=lambda _: id_lock) def get_by_bi_id(cls, id): ad = AdministrativeDomain.objects.filter(bi_id=id)[:1] if ad: return ad[0] else: return None def iter_changed_datastream(self): if config.datastream.enable_administrativedomain: yield "administrativedomain", self.id @cachetools.cachedmethod(operator.attrgetter("_path_cache"), lock=lambda _: id_lock) def get_path(self): """ Returns list of parent segment ids :return: """ if self.parent: return self.parent.get_path() + [self.id] else: return [self.id] def get_default_pool(self): if self.default_pool: return self.default_pool if self.parent: return self.parent.get_default_pool() return None def get_nested(self): """ Returns list of nested administrative domains :return: """ r = [self] for d in AdministrativeDomain.objects.filter(parent=self): r += d.get_nested() return r @classmethod @cachetools.cachedmethod(operator.attrgetter("_nested_cache"), lock=lambda _: id_lock) def get_nested_ids(cls, administrative_domain): from django.db import connection if hasattr(administrative_domain, "id"): administrative_domain = administrative_domain.id cursor = connection.cursor() cursor.execute(""" WITH RECURSIVE r AS ( SELECT id, parent_id FROM sa_administrativedomain WHERE id = %d UNION SELECT ad.id, ad.parent_id FROM sa_administrativedomain ad JOIN r ON ad.parent_id = r.id ) SELECT id FROM r """ % administrative_domain) return [r[0] for r in cursor] @property def has_children(self): return True if AdministrativeDomain.objects.filter( parent=self.id) else False
class CommandSnippet(NOCModel): """ Command snippet """ class Meta(object): verbose_name = _("Command Snippet") verbose_name_plural = _("Command Snippets") db_table = "sa_commandsnippet" app_label = "sa" ordering = ["name"] name = models.CharField(_("Name"), max_length=128, unique=True) description = models.TextField(_("Description")) snippet = models.TextField(_("Snippet"), help_text=_("Code snippet template")) change_configuration = models.BooleanField(_("Change configuration"), default=False) selector = models.ForeignKey(ManagedObjectSelector, verbose_name=_("Object Selector"), on_delete=models.CASCADE) is_enabled = models.BooleanField(_("Is Enabled?"), default=True) timeout = models.IntegerField(_("Timeout (sec)"), default=60) require_confirmation = models.BooleanField(_("Require Confirmation"), default=False) ignore_cli_errors = models.BooleanField(_("Ignore CLI errors"), default=False) # Restrict access to snippet if set # effective permission name will be sa:runsnippet:<permission_name> permission_name = models.CharField(_("Permission Name"), max_length=64, null=True, blank=True) display_in_menu = models.BooleanField(_("Show in menu"), default=False) # tags = TagsField(_("Tags"), null=True, blank=True) def __str__(self): return self.name def get_absolute_url(self): return site.reverse("sa:commandsnippet:change", self.id) rx_var = re.compile(r"{{\s*([^|}]+?)\s*(?:\|.+?)?}}", re.MULTILINE) rx_vartag = re.compile( r"\{%\s*var\s+(?P<name>\S+)\s+(?P<type>\S+)(?P<rest>.*)\s*%\}", re.MULTILINE) @property def vars(self): """ Variables used in snippet. Returns dict name -> {type: , required: } """ vars = {} # Search for {{ var }} for v in self.rx_var.findall(self.snippet): if "." in v: v = v.split(".", 1)[0] if v != "object": vars[v] = {"type": "str", "required": True, "label": v} # Search for {% var <name> <type> %} for match in self.rx_vartag.finditer(self.snippet): name, type, rest = match.groups() vars[name] = {"type": type, "required": True, "label": name} if rest: for a in shlex.split(rest.strip()): k, v = a.split("=", 1) if k == "label": vars[name][k] = v return vars def expand(self, data): """ Expand snippet with variables """ return Template(self.snippet).render(Context(data)) @property def effective_permission_name(self): if self.permission_name: return "sa:runsnippet:" + self.permission_name else: return "sa:runsnippet:default" def save(self, *args, **kwargs): super(CommandSnippet, self).save(*args, **kwargs) # Create permission if required if self.permission_name: try: Permission.objects.get(name=self.effective_permission_name) except Permission.DoesNotExist: Permission(name=self.effective_permission_name).save()
def migrate(self): self.db.add_column("sa_terminationgroup", "tags", TagsField("Tags", null=True, blank=True))
def migrate(self): self.db.add_column("sa_managedobjectprofile", "tags", TagsField("Tags", null=True, blank=True))
class Prefix(models.Model): """ Allocated prefix """ class Meta(object): verbose_name = _("Prefix") verbose_name_plural = _("Prefixes") db_table = "ip_prefix" app_label = "ip" unique_together = [("vrf", "afi", "prefix")] parent = models.ForeignKey( "self", related_name="children_set", verbose_name=_("Parent"), null=True, blank=True) vrf = CachedForeignKey( VRF, verbose_name=_("VRF"), default=VRF.get_global ) afi = models.CharField( _("Address Family"), max_length=1, choices=AFI_CHOICES) prefix = CIDRField(_("Prefix")) name = models.CharField( _("Name"), max_length=255, null=True, blank=True ) profile = DocumentReferenceField( PrefixProfile, null=False, blank=False ) asn = CachedForeignKey( AS, verbose_name=_("AS"), help_text=_("Autonomous system granted with prefix"), null=True, blank=True ) project = CachedForeignKey( Project, verbose_name="Project", on_delete=models.SET_NULL, null=True, blank=True, related_name="prefix_set") vc = models.ForeignKey( VC, verbose_name=_("VC"), null=True, blank=True, on_delete=models.SET_NULL, help_text=_("VC bound to prefix")) description = models.TextField( _("Description"), blank=True, null=True) tags = TagsField("Tags", null=True, blank=True) tt = models.IntegerField( "TT", blank=True, null=True, help_text=_("Ticket #")) state = DocumentReferenceField( State, null=True, blank=True ) allocated_till = models.DateField( _("Allocated till"), null=True, blank=True, help_text=_("Prefix temporary allocated till the date")) ipv6_transition = models.OneToOneField( "self", related_name="ipv4_transition", null=True, blank=True, limit_choices_to={"afi": "6"}, on_delete=models.SET_NULL) prefix_discovery_policy = models.CharField( _("Prefix Discovery Policy"), max_length=1, choices=[ ("P", "Profile"), ("E", "Enable"), ("D", "Disable") ], default="P", blank=False, null=False ) address_discovery_policy = models.CharField( _("Address Discovery Policy"), max_length=1, choices=[ ("P", "Profile"), ("E", "Enable"), ("D", "Disable") ], default="P", blank=False, null=False ) source = models.CharField( "Source", max_length=1, choices=[ ("M", "Manual"), ("i", "Interface"), ("w", "Whois"), ("n", "Neighbor") ], null=False, blank=False, default="M" ) csv_ignored_fields = ["parent"] _id_cache = cachetools.TTLCache(maxsize=1000, ttl=60) def __unicode__(self): return u"%s(%s): %s" % (self.vrf.name, self.afi, self.prefix) @classmethod @cachetools.cachedmethod(operator.attrgetter("_id_cache"), lock=lambda _: id_lock) def get_by_id(cls, id): mo = Prefix.objects.filter(id=id)[:1] if mo: return mo[0] else: return None def get_absolute_url(self): return site.reverse("ip:ipam:vrf_index", self.vrf.id, self.afi, self.prefix) @property def has_transition(self): """ Check prefix has ipv4/ipv6 transition :return: """ if self.is_ipv4: return bool(self.ipv6_transition) else: try: # pylint: disable=pointless-statement self.ipv4_transition # noqa return True except Prefix.DoesNotExist: return False @classmethod def get_parent(cls, vrf, afi, prefix): """ Get nearest closing prefix """ r = Prefix.objects.filter( vrf=vrf, afi=str(afi) ).extra( select={ "masklen": "masklen(prefix)" }, where=["prefix >> %s"], params=[str(prefix)], order_by=["-masklen"] )[:1] if r: return r[0] return None @property def is_ipv4(self): return self.afi == "4" @property def is_ipv6(self): return self.afi == "6" @property def is_root(self): """ Returns true if the prefix is a root of VRF """ return ((self.is_ipv4 and self.prefix == "0.0.0.0/0") or (self.is_ipv6 and self.prefix == "::/0")) def clean(self): """ Field validation """ super(Prefix, self).clean() # Set defaults self.afi = "6" if ":" in self.prefix else "4" # Check prefix is of AFI type if self.is_ipv4: check_ipv4_prefix(self.prefix) elif self.is_ipv6: check_ipv6_prefix(self.prefix) # Set defaults if not self.vrf: self.vrf = VRF.get_global() if not self.is_root: # Set proper parent self.parent = Prefix.get_parent(self.vrf, self.afi, self.prefix) # Check root prefix have no parent if self.is_root and self.parent: raise ValidationError("Root prefix cannot have parent") def save(self, **kwargs): """ Save prefix """ self.clean() super(Prefix, self).save(**kwargs) # Rebuild tree if necessary # Reconnect children children prefixes c = connection.cursor() c.execute( """ UPDATE %s SET parent_id=%%s WHERE vrf_id=%%s AND afi=%%s AND prefix << %%s AND parent_id=%%s """ % Prefix._meta.db_table, [ self.id, self.vrf.id, self.afi, self.prefix, self.parent.id if self.parent else None ] ) # Reconnect children addresses c.execute( """ UPDATE %s SET prefix_id=%%s WHERE prefix_id=%%s AND address << %%s """ % Address._meta.db_table, [ self.id, self.parent.id if self.parent else None, self.prefix ] ) def delete(self, *args, **kwargs): """ Delete prefix """ if self.is_root and not getattr(self, "_disable_delete_protection", False): raise ValidationError("Cannot delete root prefix") # Reconnect children prefixes self.children_set.update(parent=self.parent) # Reconnect children addresses self.address_set.update(prefix=self.parent) # Unlink dual-stack allocations # self.clear_transition() # Remove bookmarks self.prefixbookmark_set.all().delete() # Finally delete super(Prefix, self).delete(*args, **kwargs) def delete_recursive(self): """ Delete prefix and all descendancies """ # Unlink dual-stack allocations # self.clear_transition() # Recursive delete # Get nested prefixes ids = Prefix.objects.filter( vrf=self.vrf, afi=self.afi ).extra( where=["prefix <<= %s"], params=[self.prefix] ).values_list("id", flat=True) # zones = set() for a in Address.objects.filter(prefix__in=ids): zones.add(a.address) zones.add(a.fqdn) # Delete nested addresses Address.objects.filter(prefix__in=ids).delete() # Delete nested prefixes Prefix.objects.filter(id__in=ids).delete() # Delete permissions PrefixAccess.objects.filter( vrf=self.vrf, afi=self.afi ).extra( where=["prefix <<= %s"], params=[self.prefix] ) # Touch dns zones for z in zones: DNSZone.touch(z) @property def maintainers(self): """ List of persons having write access @todo: PostgreSQL-independent implementation """ return User.objects.raw( """ SELECT id,username,first_name,last_name FROM %s u WHERE is_active=TRUE AND (is_superuser=TRUE OR EXISTS(SELECT id FROM %s a WHERE user_id=u.id AND vrf_id=%%s AND afi=%%s AND prefix>>=%%s AND can_change=TRUE )) ORDER BY username """ % (User._meta.db_table, PrefixAccess._meta.db_table), [ self.vrf.id, self.afi, self.prefix ] ) @property def short_description(self): """ Returns first line of description :return: """ if self.description: return self.description.split("\n", 1)[0].strip() return "" @property def netmask(self): """ returns Netmask for IPv4 :return: """ if self.is_ipv4: return IPv4(self.prefix).netmask.address return None @property def broadcast(self): """ Returns Broadcast for IPv4 :return: """ if self.is_ipv4: return IPv4(self.prefix).last.address return None @property def wildcard(self): """ Returns Cisco wildcard for IPv4 :return: """ if self.is_ipv4: return IPv4(self.prefix).wildcard.address return "" @property def size(self): """ Returns IPv4 prefix size :return: """ if self.is_ipv4: return IPv4(self.prefix).size return None def can_view(self, user): """ Returns True if user has view access :param user: :return: """ return PrefixAccess.user_can_view( user, self.vrf, self.afi, self.prefix) def can_change(self, user): """ Returns True if user has change access :param user: :return: """ return PrefixAccess.user_can_change( user, self.vrf, self.afi, self.prefix) def has_bookmark(self, user): """ Check the user has bookmark on prefix :param user: :return: """ from .prefixbookmark import PrefixBookmark try: PrefixBookmark.objects.get(user=user, prefix=self) return True except PrefixBookmark.DoesNotExist: return False def toggle_bookmark(self, user): """ Toggle user bookmark. Returns new bookmark state :param user: :return: """ from .prefixbookmark import PrefixBookmark b, created = PrefixBookmark.objects.get_or_create(user=user, prefix=self) if created: return True b.delete() return False def get_index(self): """ Full-text search """ content = [self.prefix] card = "Prefix %s" % self.prefix if self.description: content += [self.description] card += " (%s)" % self.description r = { "id": "ip.prefix:%s" % self.id, "title": self.prefix, "content": "\n".join(content), "card": card } if self.tags: r["tags"] = self.tags return r @classmethod def get_search_result_url(cls, obj_id): return "/api/card/view/prefix/%s/" % obj_id @property def address_ranges(self): """ All prefix-related address ranges :return: """ return list( AddressRange.objects.raw( """ SELECT * FROM ip_addressrange WHERE vrf_id=%s AND afi=%s AND is_active=TRUE AND ( from_address << %s OR to_address << %s OR %s BETWEEN from_address AND to_address ) ORDER BY from_address, to_address """, [ self.vrf.id, self.afi, self.prefix, self.prefix, self.prefix ] ) ) def rebase(self, vrf, new_prefix): """ Rebase prefix to a new location :param vrf: :param new_prefix: :return: """ # b = IP.prefix(self.prefix) nb = IP.prefix(new_prefix) # Validation if vrf == self.vrf and self.prefix == new_prefix: raise ValueError("Cannot rebase to self") if b.afi != nb.afi: raise ValueError("Cannot change address family during rebase") if b.mask < nb.mask: raise ValueError("Cannot rebase to prefix of lesser size") # Rebase prefix and all nested prefixes # Parents are left untouched for p in Prefix.objects.filter( vrf=self.vrf, afi=self.afi ).extra( where=["prefix <<= %s"], params=[self.prefix] ): np = IP.prefix(p.prefix).rebase(b, nb).prefix # Prefix.objects.filter(pk=p.pk).update(prefix=np, vrf=vrf) p.prefix = np p.vrf = vrf p.save() # Raise events # Rebase addresses # Parents are left untouched for a in Address.objects.filter( vrf=self.vrf, afi=self.afi ).extra( where=["address <<= %s"], params=[self.prefix] ): na = IP.prefix(a.address).rebase(b, nb).address # Address.objects.filter(pk=a.pk).update(address=na, vrf=vrf) a.address = na a.vrf = vrf a.save() # Raise events # Rebase permissions # move all permissions to the nested blocks for pa in PrefixAccess.objects.filter( vrf=self.vrf ).extra( where=["prefix <<= %s"], params=[self.prefix] ): np = IP.prefix(pa.prefix).rebase(b, nb).prefix PrefixAccess.objects.filter(pk=pa.pk).update( prefix=np, vrf=vrf) # create permissions for covered blocks for pa in PrefixAccess.objects.filter( vrf=self.vrf ).extra( where=["prefix >> %s"], params=[self.prefix] ): PrefixAccess( user=pa.user, vrf=vrf, afi=pa.afi, prefix=new_prefix, can_view=pa.can_view, can_change=pa.can_change ).save() # @todo: Rebase bookmarks # @todo: Update caches # Return rebased prefix return Prefix.objects.get(pk=self.pk) # Updated object @property def nested_prefix_set(self): """ Queryset returning all nested prefixes inside the prefix """ return Prefix.objects.filter(vrf=self.vrf, afi=self.afi).extra( where=["prefix <<= %s"], params=[self.prefix]) @property def nested_address_set(self): """ Queryset returning all nested addresses inside the prefix """ return Address.objects.filter(vrf=self.vrf, afi=self.afi).extra( where=["address <<= %s"], params=[self.prefix]) def iter_free(self): """ Generator returning all available free prefixes inside :return: """ for fp in IP.prefix(self.prefix).iter_free( [p.prefix for p in self.children_set.all()]): yield str(fp) @property def effective_address_discovery(self): if self.address_discovery_policy == "P": return self.profile.address_discovery_policy return self.address_discovery_policy @property def effective_prefix_discovery(self): if self.prefix_discovery_policy == "P": return self.profile.prefix_discovery_policy return self.prefix_discovery_policy @property def usage(self): if self.is_ipv4: usage = getattr(self, "_usage_cache", None) if usage is not None: # Use update_prefixes_usage results return usage size = IPv4(self.prefix).size if not size: return 100.0 n_ips = Address.objects.filter(prefix=self).count() n_pfx = sum( IPv4(p).size for p in Prefix.objects.filter(parent=self).only("prefix").values_list("prefix", flat=True) ) if n_ips: if size > 2: # Not /31 or /32 size -= 2 # Exclude broadcast and network return float(n_ips + n_pfx) * 100.0 / float(size) else: return None @property def usage_percent(self): u = self.usage if u is None: return "" return "%.2f%%" % u @staticmethod def update_prefixes_usage(prefixes): """ Bulk calculate and update prefixes usages :param prefixes: List of Prefix instances :return: """ # Filter IPv4 only ipv4_prefixes = [p for p in prefixes if p.is_ipv4] # Calculate nested prefixrs usage = defaultdict(int) for parent, prefix in Prefix.objects.filter( parent__in=ipv4_prefixes ).values_list("parent", "prefix"): ln = int(prefix.split("/")[1]) usage[parent] += 2 ** (32 - ln) # Calculate nested addresses has_address = set() for parent, count in Address.objects.filter( prefix__in=ipv4_prefixes ).values("prefix").annotate( count=models.Count("prefix") ).values_list("prefix", "count"): usage[parent] += count has_address.add(parent) # Update usage cache for p in ipv4_prefixes: ln = int(p.prefix.split("/")[1]) size = 2 ** (32 - ln) if p.id in has_address and size > 2: # Not /31 or /32 size -= 2 # Exclude broadcast and network p._usage_cache = float(usage[p.id]) * 100.0 / float(size) def is_empty(self): """ Check prefix is empty and does not contain nested prefixes and addresses :return: """ if Prefix.objects.filter(parent=self).count() > 0: return False if Address.objects.filter(prefix=self).count() > 0: return False return True def disable_delete_protection(self): """ Disable root delete protection :return: """ self._disable_delete_protection = True def get_effective_as(self): """ Return effective AS (first found upwards) :return: AS instance or None """ if self.asn: return self.asn if not self.parent: return None return self.parent.get_effective_as()
class Address(NOCModel): class Meta(object): verbose_name = _("Address") verbose_name_plural = _("Addresses") db_table = "ip_address" app_label = "ip" unique_together = [("vrf", "afi", "address")] prefix = models.ForeignKey("ip.Prefix", verbose_name=_("Prefix"), on_delete=models.CASCADE) vrf = models.ForeignKey(VRF, verbose_name=_("VRF"), default=VRF.get_global, on_delete=models.CASCADE) afi = models.CharField(_("Address Family"), max_length=1, choices=AFI_CHOICES) address = INETField(_("Address")) profile = DocumentReferenceField(AddressProfile, null=False, blank=False) name = models.CharField(_("Name"), max_length=255, null=False, blank=False) fqdn = models.CharField( _("FQDN"), max_length=255, help_text=_("Full-qualified Domain Name"), validators=[check_fqdn], null=True, blank=True, ) project = models.ForeignKey( Project, verbose_name="Project", on_delete=models.SET_NULL, null=True, blank=True, related_name="address_set", ) mac = MACField("MAC", null=True, blank=True, help_text=_("MAC Address")) auto_update_mac = models.BooleanField( "Auto Update MAC", default=False, help_text=_("Set to auto-update MAC field")) managed_object = models.ForeignKey( ManagedObject, verbose_name=_("Managed Object"), null=True, blank=True, related_name="address_set", on_delete=models.SET_NULL, help_text=_( "Set if address belongs to the Managed Object's interface"), ) subinterface = models.CharField("SubInterface", max_length=128, null=True, blank=True) description = models.TextField(_("Description"), blank=True, null=True) tags = TagsField(_("Tags"), null=True, blank=True) tt = models.IntegerField(_("TT"), blank=True, null=True, help_text=_("Ticket #")) state = DocumentReferenceField(State, null=True, blank=True) allocated_till = models.DateField( _("Allocated till"), null=True, blank=True, help_text=_("Address temporary allocated till the date"), ) ipv6_transition = models.OneToOneField( "self", related_name="ipv4_transition", null=True, blank=True, limit_choices_to={"afi": "6"}, on_delete=models.SET_NULL, ) source = models.CharField( "Source", max_length=1, choices=[ ("M", "Manual"), ("i", "Interface"), ("m", "Management"), ("d", "DHCP"), ("n", "Neighbor"), ], null=False, blank=False, default="M", ) csv_ignored_fields = ["prefix"] def __str__(self): return "%s(%s): %s" % (self.vrf.name, self.afi, self.address) def iter_changed_datastream(self, changed_fields=None): if config.datastream.enable_address: yield "address", self.id if config.datastream.enable_dnszone: from noc.dns.models.dnszone import DNSZone if self.fqdn: # Touch forward zone fz = DNSZone.get_zone(self.fqdn) if fz: for ds, id in fz.iter_changed_datastream( changed_fields=changed_fields): yield ds, id # Touch reverse zone rz = DNSZone.get_zone(self.address) if rz: for ds, id in rz.iter_changed_datastream( changed_fields=changed_fields): yield ds, id @classmethod def get_afi(cls, address): return "6" if ":" in address else "4" @classmethod def get_collision(cls, vrf, address): """ Check VRFGroup restrictions :param vrf: :param address: :return: VRF already containing address or None :rtype: VRF or None """ if not vrf.vrf_group or vrf.vrf_group.address_constraint != "G": return None afi = cls.get_afi(address) try: a = Address.objects.get( afi=afi, address=address, vrf__in=vrf.vrf_group.vrf_set.exclude(id=vrf.id)) return a.vrf except Address.DoesNotExist: return None def save(self, *args, **kwargs): """ Override default save() method to set AFI, parent prefix, and check VRF group restrictions :param kwargs: :return: """ self.clean() super(Address, self).save(*args, **kwargs) def clean(self): """ Field validation :return: """ super(Address, self).clean() # Get proper AFI self.afi = "6" if ":" in self.address else "4" # Check prefix is of AFI type if self.is_ipv4: check_ipv4(self.address) elif self.is_ipv6: check_ipv6(self.address) # Check VRF if not self.vrf: self.vrf = VRF.get_global() # Find parent prefix self.prefix = Prefix.get_parent(self.vrf, self.afi, self.address) # Check VRF group restrictions cv = self.get_collision(self.vrf, self.address) if cv: # Collision detected raise ValidationError("Address already exists in VRF %s" % cv) @property def short_description(self): """ First line of description """ if self.description: return self.description.split("\n", 1)[0].strip() else: return "" def get_index(self): """ Full-text search """ content = [self.address, self.name] card = "Address %s, Name %s" % (self.address, self.name) if self.fqdn: content += [self.fqdn] card += ", FQDN %s" % self.fqdn if self.mac: content += [self.mac] card += ", MAC %s" % self.mac if self.description: content += [self.description] card += " (%s)" % self.description r = { "id": "ip.address:%s" % self.id, "title": self.address, "content": "\n".join(content), "card": card, } if self.tags: r["tags"] = self.tags return r @classmethod def get_search_result_url(cls, obj_id): return "/api/card/view/address/%s/" % obj_id @property def is_ipv4(self): return self.afi == "4" @property def is_ipv6(self): return self.afi == "6"
class Peer(NOCModel): """ BGP Peering session """ class Meta(object): verbose_name = "Peer" verbose_name_plural = "Peers" db_table = "peer_peer" app_label = "peer" peer_group = models.ForeignKey(PeerGroup, verbose_name="Peer Group", on_delete=models.CASCADE) project = models.ForeignKey( Project, verbose_name="Project", null=True, blank=True, related_name="peer_set", on_delete=models.CASCADE, ) peering_point = models.ForeignKey(PeeringPoint, verbose_name="Peering Point", on_delete=models.CASCADE) local_asn = models.ForeignKey(AS, verbose_name="Local AS", on_delete=models.CASCADE) local_ip = INETField("Local IP") local_backup_ip = INETField("Local Backup IP", null=True, blank=True) remote_asn = models.IntegerField("Remote AS") remote_ip = INETField("Remote IP") remote_backup_ip = INETField("Remote Backup IP", null=True, blank=True) status = models.CharField( "Status", max_length=1, default="A", choices=[("P", "Planned"), ("A", "Active"), ("S", "Shutdown")], ) import_filter = models.CharField("Import filter", max_length=64) # Override PeerGroup.local_pref local_pref = models.IntegerField("Local Pref", null=True, blank=True) # Override PeerGroup.import_med import_med = models.IntegerField("Import MED", blank=True, null=True) # Override PeerGroup.export_med export_med = models.IntegerField("Export MED", blank=True, null=True) export_filter = models.CharField("Export filter", max_length=64) description = models.CharField("Description", max_length=64, null=True, blank=True) # Peer remark to be shown in RPSL rpsl_remark = models.CharField("RPSL Remark", max_length=64, null=True, blank=True) tt = models.IntegerField("TT", blank=True, null=True) # In addition to PeerGroup.communities # and PeeringPoint.communities communities = models.CharField("Import Communities", max_length=128, blank=True, null=True) max_prefixes = models.IntegerField("Max. Prefixes", default=100) import_filter_name = models.CharField("Import Filter Name", max_length=64, blank=True, null=True) export_filter_name = models.CharField("Export Filter Name", max_length=64, blank=True, null=True) tags = TagsField("Tags", null=True, blank=True) rpsl = GridVCSField("rpsl_peer") def __str__(self): return " %s (%s@%s)" % (self.remote_asn, self.remote_ip, self.peering_point.hostname) def save(self, *args, **kwargs): if self.import_filter_name is not None and not self.import_filter_name.strip( ): self.import_filter_name = None if self.export_filter_name is not None and not self.export_filter_name.strip( ): self.export_filter_name = None super(Peer, self).save(*args, **kwargs) self.peering_point.sync_cm_prefix_list() self.touch_rpsl() @property def all_communities(self): r = {} for cl in [ self.peering_point.communities, self.peer_group.communities, self.communities ]: if cl is None: continue for c in cl.replace(",", " ").split(): r[c] = None c = sorted(r.keys()) return " ".join(c) def get_rpsl(self): s = "import: from AS%d" % self.remote_asn s += " at %s" % self.peering_point.hostname actions = [] local_pref = self.effective_local_pref if local_pref: # Select pref meaning if config.peer.rpsl_inverse_pref_style: pref = 65535 - local_pref # RPSL style else: pref = local_pref actions += ["pref=%d;" % pref] import_med = self.effective_import_med if import_med: actions += ["med=%d;" % import_med] if actions: s += " action " + " ".join(actions) s += " accept %s\n" % self.import_filter actions = [] export_med = self.effective_export_med if export_med: actions += ["med=%d;" % export_med] s += "export: to AS%s at %s" % (self.remote_asn, self.peering_point.hostname) if actions: s += " action " + " ".join(actions) s += " announce %s" % self.export_filter return s @property def effective_max_prefixes(self): if self.max_prefixes: return self.max_prefixes if self.peer_group.max_prefixes: return self.peer_group.max_prefixes return 0 @property def effective_local_pref(self): """ Effective localpref: Peer specific or PeerGroup inherited """ if self.local_pref is not None: return self.local_pref return self.peer_group.local_pref @property def effective_import_med(self): """ Effective import med: Peer specific or PeerGroup inherited """ if self.import_med is not None: return self.import_med return self.peer_group.import_med @property def effective_export_med(self): """ Effective export med: Peer specific or PeerGroup inherited """ if self.export_med is not None: return self.export_med return self.peer_group.export_med @classmethod def get_peer(cls, address): """ Get peer by address :param address: Remote address :type address: Str :returns: Peer instance or None """ data = list(Peer.objects.filter().extra( where=["host(remote_ip)=%s OR host(remote_backup_ip)=%s"], params=[address, address])) if data: return data[0] else: return None def touch_rpsl(self): c_rpsl = self.rpsl.read() n_rpsl = self.get_rpsl() if c_rpsl == n_rpsl: return # Not changed self.rpsl.write(n_rpsl) def on_save(self): self.touch_rpsl()
class DNSZone(models.Model): """ DNS Zone """ class Meta(object): verbose_name = _("DNS Zone") verbose_name_plural = _("DNS Zones") ordering = ["name"] db_table = "dns_dnszone" app_label = "dns" name = models.CharField(_("Domain"), max_length=256, unique=True) type = models.CharField(_("Type"), max_length=1, null=False, blank=False, default=ZONE_FORWARD, choices=[(ZONE_FORWARD, "Forward"), (ZONE_REVERSE_IPV4, "Reverse IPv4"), (ZONE_REVERSE_IPV6, "Reverse IPv6")]) description = models.CharField(_("Description"), null=True, blank=True, max_length=64) project = models.ForeignKey(Project, verbose_name="Project", null=True, blank=True, related_name="dnszone_set") # @todo: Rename to is_provisioned is_auto_generated = models.BooleanField(_("Auto generated?")) serial = models.IntegerField(_("Serial"), default=0) profile = models.ForeignKey(DNSZoneProfile, verbose_name=_("Profile")) notification_group = models.ForeignKey( NotificationGroup, verbose_name=_("Notification Group"), null=True, blank=True, help_text=_("Notification group to use when zone changed")) paid_till = models.DateField(_("Paid Till"), null=True, blank=True) tags = TagsField(_("Tags"), null=True, blank=True) # Managers objects = models.Manager() zone = GridVCSField("dnszone") # Caches _id_cache = cachetools.TTLCache(maxsize=100, ttl=60) _name_cache = cachetools.TTLCache(maxsize=100, ttl=60) def __unicode__(self): return self.name @classmethod @cachetools.cachedmethod(operator.attrgetter("_id_cache"), lock=lambda _: id_lock) def get_by_id(cls, id): zone = DNSZone.objects.filter(id=id)[:1] if zone: return zone[0] return None @classmethod @cachetools.cachedmethod(operator.attrgetter("_name_cache"), lock=lambda _: id_lock) def get_by_name(cls, name): zone = DNSZone.objects.filter(name=name)[:1] if zone: return zone[0] return None def iter_changed_datastream(self): if config.datastream.enable_dnszone: yield "dnszone", self.id def clean(self): super(DNSZone, self).clean() self.type = self.get_type_for_zone(self.name or "") def save(self, *args, **kwargs): self.clean() super(DNSZone, self).save(*args, **kwargs) @staticmethod def get_type_for_zone(name): """ Zone type. One of: * R4 - IPv4 reverse * R6 - IPv6 reverse * F - forward zone :return: Zone type :rtype: String """ nl = name.lower() if nl.endswith(".in-addr.arpa"): return ZONE_REVERSE_IPV4 # IPv4 reverse elif nl.endswith(".ip6.int") or nl.endswith(".ip6.arpa"): return ZONE_REVERSE_IPV6 # IPv6 reverse else: return ZONE_FORWARD # Forward rx_rzone = re.compile(r"^(\d+)\.(\d+)\.(\d+)\.in-addr.arpa$") @property def reverse_prefix(self): """ Appropriative prefix for reverse zone :return: IPv4 or IPv6 prefix :rtype: String """ if self.type == ZONE_REVERSE_IPV4: # Get IPv4 prefix covering reverse zone n = self.name.lower() if n.endswith(".in-addr.arpa"): r = n[:-13].split(".") r.reverse() length = 4 - len(r) r += ["0"] * length ml = 32 - 8 * length return ".".join(r) + "/%d" % ml elif self.type == ZONE_REVERSE_IPV6: # Get IPv6 prefix covering reverse zone n = self.name.lower() if n.endswith(".ip6.int"): n = n[:-8] elif n.endswith(".ip6.arpa"): n = n[:-9] else: raise Exception("Invalid IPv6 zone suffix") p = n.split(".") p.reverse() length = len(p) if length % 4: p += [u"0"] * (4 - length % 4) r = "" for i, c in enumerate(p): if i and i % 4 == 0: r += ":" r += c if len(p) != 32: r += "::" prefix = r + "/%d" % (length * 4) return IPv6(prefix).normalized.prefix @classmethod def get_reverse_for_address(cls, address): """ Return reverse zone holding address :param address: Address (as a string) :return: DNSZone instance or None """ if ":" in address: return cls._get_reverse_for_ipv6_address(address) return cls._get_reverse_for_ipv4_address(address) @classmethod def _get_reverse_for_ipv4_address(cls, address): """ Get reverze zone holding IPv4 address :param address: Address (as a string) :return: DNSZone instance or None """ parts = list(reversed(address.split(".")))[1:] while parts: name = "%s.in-addr.arpa" % ".".join(parts) zone = DNSZone.get_by_name(name) if zone: return zone parts.pop(0) return None @classmethod def _get_reverse_for_ipv6_address(cls, address): """ Get reverze zone holding IPv6 address :param address: Address (as a string) :return: DNSZone instance or None """ # @todo: Impelement properly parts = [str(x) for x in reversed(IPv6(address).iter_bits())][1:] while parts: for suffix in (".ip6.int", ".ip6.arpa"): name = "%s.%s" % (".".join(parts), suffix) zone = DNSZone.get_by_name(name) if zone: return zone parts.pop(0) # Remove first par return None def get_next_serial(self): """ Next zone serial number. Next serial is greater than current one. Serial is built using current data to follow common practive. :return: Zone serial number :rtype: int """ T = time.gmtime() base = T[0] * 10000 + T[1] * 100 + T[2] s_base = self.serial // 100 if s_base < base: return base * 100 # New day else: return self.serial + 1 # May cause future lap def set_next_serial(self): old_serial = self.serial self.serial = self.get_next_serial() logger.info("Zone %s serial change: %s -> %s", self.name, old_serial, self.serial) # Hack to not send post_save signal DNSZone.objects.filter(id=self.id).update(serial=self.serial) @property def children(self): """List of next-level nested zones""" length = len(self.name) s = ".%s" % self.name return [ z for z in DNSZone.objects.filter(name__iendswith=s) if "." not in z.name[:-length - 1] ] @classmethod def get_ns_name(cls, ns): """Add missed '.' to the end of NS name, if given as FQDN""" name = ns.name.strip() if not is_ipv4(name) and not name.endswith("."): return name + "." else: return name @property def ns_list(self): """ Sorted list of zone NSes. NSes are properly formatted and have '.' at the end. :return: List of zone NSes :rtype: List of string """ return sorted( self.get_ns_name(ns) for ns in self.profile.authoritative_servers) @property def masters(self): """ Sorted list of zone master NSes. NSes are properly formatted and have '.' at the end. :return: List of zone master NSes :rtype: List of string :return: """ return sorted( self.get_ns_name(ns) for ns in self.profile.masters.all()) @property def slaves(self): """ Sorted list of zone slave NSes. NSes are properly formatted and have '.' at the end. :return: List of zone slave NSes :rtype: List of string :return: """ return sorted(self.get_ns_name(ns) for ns in self.profile.slaves.all()) @property def rpsl(self): """ RPSL for reverse zone. RPSL contains domain: and nserver: attributes :return: RPSL :rtype: String """ if self.type == ZONE_FORWARD: return "" # Do not generate RPSL for private reverse zones if self.name.lower().endswith(".10.in-addr.arpa"): return "" n1, n2, n = self.name.lower().split(".", 2) if "16.172.in-addr.arpa" <= n <= "31.172.in-addr.arpa": return "" n1, n = self.name.lower().split(".", 1) if n == "168.192.in-addr.arpa": return "" s = ["domain: %s" % self.name ] + ["nserver: %s" % ns for ns in self.ns_list] return rpsl_format("\n".join(s)) @staticmethod def to_idna(n): if isinstance(n, unicode): return n.lower().encode("idna") elif isinstance(n, six.string_types): return unicode(n, "utf-8").lower().encode("idna") else: return n @classmethod def get_zone(cls, name): """ Resolve name to zone object :return: """ def get_closest(n): """ Return closest matching zone """ while n: try: return DNSZone.objects.get(name=n) except DNSZone.DoesNotExist: pass n = ".".join(n.split(".")[1:]) return None if not name: return None if is_ipv4(name): # IPv4 zone n = name.split(".") n.reverse() return get_closest("%s.in-addr.arpa" % (".".join(n[1:]))) elif is_ipv6(name): # IPv6 zone d = IPv6(name).digits d.reverse() c = ".".join(d) return (get_closest("%s.ip6.arpa" % c) or get_closest("%s.ip6.int" % c)) else: return get_closest(name) def get_notification_groups(self): """ Get a list of notification groups to notify about zone changes :return: """ if self.notification_group: return [self.notification_group] if self.profile.notification_group: return [self.profile.notification_group] ng = SystemNotification.get_notification_group("dns.change") if ng: return [ng] else: return [] @property def is_forward(self): return self.type == ZONE_FORWARD @property def is_reverse_ipv4(self): return self.type == ZONE_REVERSE_IPV4 @property def is_reverse_ipv6(self): return self.type == ZONE_REVERSE_IPV6
class VC(models.Model): """ Virtual circuit """ class Meta: verbose_name = "VC" verbose_name_plural = "VCs" unique_together = [("vc_domain", "l1", "l2"), ("vc_domain", "name")] db_table = "vc_vc" app_label = "vc" ordering = ["vc_domain", "l1", "l2"] vc_domain = models.ForeignKey(VCDomain, verbose_name="VC Domain") name = models.CharField("Name", max_length=64) state = models.ForeignKey( ResourceState, verbose_name="State", default=ResourceState.get_default ) project = models.ForeignKey( Project, verbose_name="Project", on_delete=models.SET_NULL, null=True, blank=True, related_name="vc_set") l1 = models.IntegerField("Label 1") l2 = models.IntegerField("Label 2", default=0) description = models.CharField("Description", max_length=256, null=True, blank=True) style = models.ForeignKey(Style, verbose_name="Style", blank=True, null=True) tags = TagsField("Tags", null=True, blank=True) _id_cache = cachetools.TTLCache(maxsize=1000, ttl=60) def __unicode__(self): s = u"%s %d" % (self.vc_domain, self.l1) if self.l2: s += u"/%d" % self.l2 s += u": %s" % self.name return s @classmethod @cachedmethod(operator.attrgetter("_id_cache"), key="vc-id-%s", lock=lambda _: id_lock) def get_by_id(cls, id): mo = VC.objects.filter(id=id)[:1] if mo: return mo[0] else: return None def get_absolute_url(self): return site.reverse("vc:vc:change", self.id) @classmethod def convert_name(cls, name): name = rx_vc_underline.sub("_", name) name = rx_vc_empty.sub("", name) return name def save(self): """ Enforce additional checks """ if self.l1 < self.vc_domain.type.label1_min or self.l1 > self.vc_domain.type.label1_max: raise InvalidLabelException("Invalid value for L1") if self.vc_domain.type.min_labels > 1 and self.l2 is None: raise MissedLabelException("L2 required") if ( self.vc_domain.type.min_labels > 1 and not (self.vc_domain.type.label2_min <= self.l2 <= self.vc_domain.type.label2_max) ): raise InvalidLabelException("Invalid value for L2") # Format name if self.name: self.name = self.convert_name(self.name) else: self.name = "VC_%04d" % self.l1 super(VC, self).save() def get_index(self): """ Full-text search """ content = [self.name, str(self.l1)] card = "VC %s. Tag %s" % (self.name, self.l1) if self.description: content += [self.description] card += " (%s)" % self.description if self.l2: content += [str(self.l2)] r = { "id": "vc.vc:%s" % self.id, "title": self.name, "content": "\n".join(content), "card": card } if self.tags: r["tags"] = self.tags return r @classmethod def get_search_result_url(cls, obj_id): return "/api/card/view/vlan/%s/" % obj_id def get_bridge_subinterfaces(self): """ Returns a list of SubInterface instances belonging to VC """ from noc.inv.models.interface import Interface from noc.inv.models.subinterface import SubInterface r = [] si_q = MEQ(untagged_vlan=self.l1) | MEQ(tagged_vlans=self.l1) # VC Domain's objects objects = set(self.vc_domain.managedobject_set.values_list( "id", flat=True)) for si in SubInterface.objects.filter( managed_object__in=objects, enabled_afi="BRIDGE").filter(si_q): if (si.interface.vc_domain is None or si.interface.vc_domain.id == self.vc_domain.id): r += [si] # Explicit interfaces for i in Interface.objects.filter(vc_domain=self.vc_domain.id): for si in SubInterface.objects.filter( interface=i.id, enabled_afi="BRIDGE").filter(si_q): r += [si] return r
def forwards(self): db.add_column("sa_managedobjectprofile", "tags", TagsField("Tags", null=True, blank=True))