Exemple #1
0
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)
Exemple #2
0
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
Exemple #3
0
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 != ''
         """)
Exemple #5
0
 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))
Exemple #6
0
 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)
Exemple #7
0
 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,
         ),
     )
Exemple #9
0
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()
Exemple #10
0
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()
Exemple #11
0
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])
Exemple #12
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
Exemple #13
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
Exemple #14
0
 def forwards(self):
     db.add_column("sa_terminationgroup", "tags",
                   TagsField("Tags", null=True, blank=True))
Exemple #15
0
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)
Exemple #16
0
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)
Exemple #17
0
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
Exemple #18
0
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()
Exemple #19
0
 def migrate(self):
     self.db.add_column("sa_terminationgroup", "tags", TagsField("Tags", null=True, blank=True))
Exemple #20
0
 def migrate(self):
     self.db.add_column("sa_managedobjectprofile", "tags",
                        TagsField("Tags", null=True, blank=True))
Exemple #21
0
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()
Exemple #22
0
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"
Exemple #23
0
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()
Exemple #24
0
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
Exemple #25
0
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))