Ejemplo n.º 1
0
    def forwards(self):
        db.create_table("main_prefixtable", (
            ("id",
             models.AutoField(
                 verbose_name="ID", primary_key=True, auto_created=True)),
            ("name", models.CharField(_("Name"), max_length=128, unique=True)),
            ("description",
             models.TextField(_("Description"), null=True, blank=True)),
        ))

        PrefixTable = db.mock_model(model_name="PrefixTable",
                                    db_table="main_prefixtable",
                                    db_tablespace="",
                                    pk_field_name="id",
                                    pk_field_type=models.AutoField)

        db.create_table(
            "main_prefixtableprefix",
            (("id",
              models.AutoField(
                  verbose_name="ID", primary_key=True, auto_created=True)),
             ("table",
              models.ForeignKey(PrefixTable, verbose_name=_("Prefix Table"))),
             ("afi",
              models.CharField(_("Address Family"),
                               max_length=1,
                               choices=[("4", _("IPv4")), ("6", _("IPv6"))])),
             ("prefix", CIDRField(_("Prefix")))))

        db.send_create_signal("main", ["PrefixTable", "PrefixTablePrefix"])
Ejemplo n.º 2
0
    def migrate(self):
        self.db.create_table(
            "main_prefixtable",
            (
                ("id", models.AutoField(verbose_name="ID", primary_key=True, auto_created=True)),
                ("name", models.CharField("Name", max_length=128, unique=True)),
                ("description", models.TextField("Description", null=True, blank=True)),
            ),
        )

        PrefixTable = self.db.mock_model(model_name="PrefixTable", db_table="main_prefixtable")

        self.db.create_table(
            "main_prefixtableprefix",
            (
                ("id", models.AutoField(verbose_name="ID", primary_key=True, auto_created=True)),
                (
                    "table",
                    models.ForeignKey(
                        PrefixTable, verbose_name="Prefix Table", on_delete=models.CASCADE
                    ),
                ),
                (
                    "afi",
                    models.CharField(
                        "Address Family", max_length=1, choices=[("4", "IPv4"), ("6", "IPv6")]
                    ),
                ),
                ("prefix", CIDRField("Prefix")),
            ),
        )
Ejemplo n.º 3
0
 def forwards(self):
     db.delete_column("ip_ipv4block", "prefix")
     db.add_column("ip_ipv4block", "prefix", CIDRField("prefix", null=True))
     db.execute("UPDATE ip_ipv4block SET prefix=prefix_cidr")
     db.delete_column("ip_ipv4block", "prefix_cidr")
     db.execute("ALTER TABLE ip_ipv4block ALTER prefix SET NOT NULL")
     db.execute("DROP TRIGGER t_ip_ipv4block_modify ON ip_ipv4block")
     db.execute("DROP FUNCTION f_trigger_ip_ipv4block()")
     db.delete_column("ip_ipv4blockaccess", "prefix")
     db.add_column("ip_ipv4blockaccess", "prefix",
                   CIDRField("prefix", null=True))
     db.execute("UPDATE ip_ipv4blockaccess SET prefix=prefix_cidr")
     db.delete_column("ip_ipv4blockaccess", "prefix_cidr")
     db.execute("ALTER TABLE ip_ipv4blockaccess ALTER prefix SET NOT NULL")
     db.execute(
         "DROP TRIGGER t_ip_ipv4blockaccess_modify ON ip_ipv4blockaccess")
     db.execute("DROP FUNCTION f_trigger_ip_ipv4blockaccess()")
     db.execute(RAW_SQL)
Ejemplo n.º 4
0
class VCBindFilter(NOCModel):
    class Meta(object):
        verbose_name = "VC Bind Filter"
        verbose_name_plural = "VC Bind Filters"
        db_table = "vc_vcbindfilter"
        app_label = "vc"

    vc_domain = models.ForeignKey(VCDomain, verbose_name="VC Domain", on_delete=models.CASCADE)
    vrf = models.ForeignKey("ip.VRF", verbose_name="VRF", on_delete=models.CASCADE)
    afi = models.CharField("Address Family", max_length=1, choices=AFI_CHOICES, default="4")
    prefix = CIDRField("Prefix")
    vc_filter = models.ForeignKey(VCFilter, verbose_name="VC Filter", on_delete=models.CASCADE)

    def __str__(self):
        return "%s %s %s %s" % (self.vc_domain, self.vrf, self.prefix, self.vc_filter)

    @classmethod
    def get_vcs(cls, vrf, afi, prefix):
        """
        Returns queryset with all suitable VCs
        """
        if hasattr(prefix, "prefix"):
            prefix = prefix.prefix
        c = connection.cursor()
        c.execute(
            """
            SELECT v.id,v.l1,vf.id
            FROM
                vc_vcdomain d JOIN vc_vcbindfilter f ON (d.id=f.vc_domain_id)
                JOIN vc_vcfilter vf ON (f.vc_filter_id=vf.id)
                JOIN vc_vc v ON (v.vc_domain_id=d.id)
            WHERE
                    f.vrf_id=%s
                AND f.afi=%s
                AND f.prefix>>=%s
        """,
            [vrf.id, afi, prefix],
        )
        vcs = set()  # vc.id
        F = {}  # id -> filter
        for vc_id, l1, vf_id in c.fetchall():
            try:
                f = F[vf_id]
            except KeyError:
                f = VCFilter.objects.get(id=vf_id)
                F[vf_id] = f
            if f.check(l1):
                vcs.add(vc_id)
        return VC.objects.filter(id__in=vcs).order_by("l1")
Ejemplo n.º 5
0
    def forwards(self):
        # Adding model 'VCBindFilter'
        VCDomain=db.mock_model(model_name="VCDomain",db_table="vc_vcdomain")
        VRF=db.mock_model(model_name="VRF",db_table="ip_vrf")
        VCFilter=db.mock_model(model_name="VCFilter",db_table="vc_vcfilter")
        db.create_table('vc_vcbindfilter', (
            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
            ('vc_domain', models.ForeignKey(VCDomain,verbose_name="VC Domain")),
            ('vrf', models.ForeignKey(VRF,verbose_name="VRF")),
            ('prefix', CIDRField("Prefix")),
            ('vc_filter', models.ForeignKey(VCFilter,verbose_name="VC Filter")),
        ))
        db.send_create_signal('vc', ['VCBindFilter'])

        # Adding field 'VCDomain.enable_vc_bind_filter'
        db.add_column('vc_vcdomain', 'enable_vc_bind_filter', models.BooleanField("Enable VC Bind filter",default=False))
Ejemplo n.º 6
0
class PrefixTablePrefix(models.Model):
    class Meta:
        verbose_name = _("Prefix")
        verbose_name_plural = _("Prefixes")
        db_table = "main_prefixtableprefix"
        unique_together = [("table", "afi", "prefix")]
        ordering = ["table", "afi", "prefix"]

    table = models.ForeignKey(PrefixTable,
        verbose_name=_("Prefix Table"))
    afi = models.CharField(_("Address Family"), max_length=1,
            choices=[("4", _("IPv4")), ("6", _("IPv6"))])
    prefix = CIDRField(_("Prefix"))

    def __unicode__(self):
        return u"%s %s" % (self.table.name, self.prefix)

    def save(self, *args, **kwargs):
        # Set AFI
        self.afi = IP.prefix(self.prefix).afi
        return super(PrefixTablePrefix, self).save(*args, **kwargs)
Ejemplo n.º 7
0
    def migrate(self):
        # Adding model 'VCBindFilter'
        VCDomain = self.db.mock_model(model_name="VCDomain",
                                      db_table="vc_vcdomain")
        VRF = self.db.mock_model(model_name="VRF", db_table="ip_vrf")
        VCFilter = self.db.mock_model(model_name="VCFilter",
                                      db_table="vc_vcfilter")
        self.db.create_table(
            "vc_vcbindfilter",
            (
                ("id",
                 models.AutoField(
                     verbose_name="ID", primary_key=True, auto_created=True)),
                (
                    "vc_domain",
                    models.ForeignKey(VCDomain,
                                      verbose_name="VC Domain",
                                      on_delete=models.CASCADE),
                ),
                ("vrf",
                 models.ForeignKey(
                     VRF, verbose_name="VRF", on_delete=models.CASCADE)),
                ("prefix", CIDRField("Prefix")),
                (
                    "vc_filter",
                    models.ForeignKey(VCFilter,
                                      verbose_name="VC Filter",
                                      on_delete=models.CASCADE),
                ),
            ),
        )

        # Adding field 'VCDomain.enable_vc_bind_filter'
        self.db.add_column(
            "vc_vcdomain",
            "enable_vc_bind_filter",
            models.BooleanField("Enable VC Bind filter", default=False),
        )
Ejemplo n.º 8
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()
Ejemplo n.º 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()
Ejemplo n.º 10
0
class PrefixAccess(NOCModel):
    class Meta(object):
        verbose_name = _("Prefix Access")
        verbose_name_plural = _("Prefix Access")
        db_table = "ip_prefixaccess"
        app_label = "ip"
        unique_together = [("user", "vrf", "afi", "prefix")]
        ordering = ["user", "vrf", "afi", "prefix"]

    user = models.ForeignKey(User,
                             verbose_name=_("User"),
                             on_delete=models.CASCADE)
    vrf = models.ForeignKey(VRF,
                            verbose_name=_("VRF"),
                            on_delete=models.CASCADE)
    afi = models.CharField(_("Address Family"),
                           max_length=1,
                           choices=AFI_CHOICES)
    prefix = CIDRField(_("Prefix"))
    can_view = models.BooleanField(_("Can View"), default=False)
    can_change = models.BooleanField(_("Can Change"), default=False)

    def __str__(self):
        perms = []
        if self.can_view:
            perms += ["View"]
        if self.can_change:
            perms += ["Change"]
        return "%s: %s(%s): %s: %s" % (
            self.user.username,
            self.vrf.name,
            self.afi,
            self.prefix,
            ", ".join(perms),
        )

    def clean(self):
        """
        Field validation
        :return:
        """
        super(PrefixAccess, self).clean()
        # Check prefix is of AFI type
        if self.afi == "4":
            check_ipv4_prefix(self.prefix)
        elif self.afi == "6":
            check_ipv6_prefix(self.prefix)

    @classmethod
    def user_can_view(cls, user, vrf, afi, prefix):
        """
        Check user has read access to prefix
        :param user:
        :param vrf:
        :param afi:
        :param prefix:
        :return:
        """
        if user.is_superuser:
            return True
        if isinstance(prefix, Prefix):
            prefix = prefix.prefix
        else:
            prefix = str(prefix)
        if "/" not in prefix:
            if afi == "4":
                prefix += "/32"
            else:
                prefix += "/128"
        return (PrefixAccess.objects.filter(vrf=vrf,
                                            afi=afi,
                                            user=user,
                                            can_view=True).extra(
                                                where=["prefix >>= %s"],
                                                params=[prefix]).exists())

    @classmethod
    def user_can_change(cls, user, vrf, afi, prefix):
        """
        Check user has write access to prefix
        :param cls:
        :param user:
        :param vrf:
        :param afi:
        :param prefix:
        :return:
        """
        if user.is_superuser:
            return True
        if isinstance(prefix, Prefix):
            prefix = prefix.prefix
        else:
            prefix = str(prefix)
        if "/" not in prefix:
            if afi == "4":
                prefix += "/32"
            else:
                prefix += "/128"
        return (PrefixAccess.objects.filter(vrf=vrf,
                                            afi=afi,
                                            user=user,
                                            can_change=True).extra(
                                                where=["prefix >>= %s"],
                                                params=[prefix]).exists())

    @classmethod
    def read_Q(cls, user, field="prefix", table=""):
        """
        Returns django Q with read restrictions.
        Q can be applied to prefix
        :param user:
        :param field:
        :param table:
        :return:
        """
        if user.is_superuser:
            return Q()  # No restrictions
        vaccess = defaultdict(set)  # (vrf, afi) -> {prefix}
        for pa in PrefixAccess.objects.filter(user=user):
            vaccess[pa.vrf.id, pa.afi].add(pa.prefix)
        if not vaccess:
            return SQL("0 = 1")  # False
        stmt = []
        for vrf, afi in vaccess:
            for p in vaccess[vrf, afi]:
                stmt += [
                    "(%s = %d AND %s = '%s' AND %s <<= '%s')" % (
                        "%s.vrf_id" % table if table else "vrf_id",
                        vrf,
                        "%s.afi" % table if table else "afi",
                        afi,
                        "%s.%s" % (table, field) if table else field,
                        p,
                    )
                ]
        return SQL(reduce(lambda x, y: "%s OR %s" % (x, y), stmt))
Ejemplo n.º 11
0
    def migrate(self):
        AFI_CHOICES = [("4", "IPv4"), ("6", "IPv6")]
        # Style
        Style = self.db.mock_model(model_name="Style", db_table="main_style")
        # VRF Group
        self.db.add_column(
            "ip_vrfgroup",
            "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",
            ),
        )
        self.db.execute("ALTER TABLE ip_vrfgroup ALTER COLUMN description TYPE TEXT")
        self.db.execute("ALTER TABLE ip_vrfgroup ALTER COLUMN description DROP NOT NULL")
        self.db.execute("ALTER TABLE ip_vrfgroup ALTER COLUMN description SET DEFAULT 'V'")
        self.db.add_column(
            "ip_vrfgroup", "tags", AutoCompleteTagsField("Tags", null=True, blank=True)
        )
        # VRF
        self.db.add_column("ip_vrf", "is_active", models.BooleanField("Is Active", default=True))
        self.db.add_column("ip_vrf", "afi_ipv4", models.BooleanField("IPv4", default=True))
        self.db.add_column("ip_vrf", "afi_ipv6", models.BooleanField("IPv6", default=False))
        self.db.execute("ALTER TABLE ip_vrf ALTER COLUMN description TYPE TEXT")
        self.db.execute("ALTER TABLE ip_vrf ALTER COLUMN description DROP NOT NULL")
        self.db.execute("ALTER TABLE ip_vrf ALTER COLUMN description SET DEFAULT 'V'")
        self.db.add_column(
            "ip_vrf",
            "style",
            models.ForeignKey(
                Style, verbose_name="Style", blank=True, null=True, on_delete=models.CASCADE
            ),
        )
        self.db.add_column(
            "ip_vrf", "allocated_till", models.DateField("Allocated till", null=True, blank=True)
        )
        # Prefix
        VRF = self.db.mock_model(model_name="VRF", db_table="ip_vrf")
        AS = self.db.mock_model(model_name="AS", db_table="peer_as")
        VC = self.db.mock_model(model_name="VC", db_table="vc_vc")
        ManagedObject = self.db.mock_model(model_name="ManagedObject", db_table="sa_managedobject")
        Prefix = self.db.mock_model(model_name="Prefix", db_table="ip_prefix")

        self.db.create_table(
            "ip_prefix",
            (
                ("id", models.AutoField(verbose_name="ID", primary_key=True, auto_created=True)),
                (
                    "parent",
                    models.ForeignKey(
                        Prefix,
                        related_name="children_set",
                        verbose_name="Parent",
                        null=True,
                        blank=True,
                        on_delete=models.CASCADE,
                    ),
                ),
                ("vrf", models.ForeignKey(VRF, verbose_name="VRF", on_delete=models.CASCADE)),
                ("afi", models.CharField("Address Family", max_length=1, choices=AFI_CHOICES)),
                ("prefix", CIDRField("Prefix")),
                ("asn", models.ForeignKey(AS, verbose_name="AS", on_delete=models.CASCADE)),
                (
                    "vc",
                    models.ForeignKey(
                        VC, verbose_name="VC", null=True, blank=True, on_delete=models.CASCADE
                    ),
                ),
                ("description", models.TextField("Description", blank=True, null=True)),
                ("tags", AutoCompleteTagsField("Tags", null=True, blank=True)),
                ("tt", models.IntegerField("TT", blank=True, null=True)),
                (
                    "style",
                    models.ForeignKey(
                        Style, verbose_name="Style", blank=True, null=True, on_delete=models.CASCADE
                    ),
                ),
                ("allocated_till", models.DateField("Allocated till", null=True, blank=True)),
            ),
        )
        self.db.create_index("ip_prefix", ["vrf_id", "afi", "prefix"], unique=True)
        # Address
        self.db.create_table(
            "ip_address",
            (
                ("id", models.AutoField(verbose_name="ID", primary_key=True, auto_created=True)),
                (
                    "prefix",
                    models.ForeignKey(Prefix, verbose_name="Prefix", on_delete=models.CASCADE),
                ),
                ("vrf", models.ForeignKey(VRF, verbose_name="VRF", on_delete=models.CASCADE)),
                ("afi", models.CharField("Address Family", max_length=1, choices=AFI_CHOICES)),
                ("address", INETField("Address")),
                ("fqdn", models.CharField("FQDN", max_length=255)),
                ("mac", MACField("MAC", null=True, blank=True)),
                ("auto_update_mac", models.BooleanField("Auto Update MAC", default=False)),
                (
                    "managed_object",
                    models.ForeignKey(
                        ManagedObject,
                        verbose_name="Managed Object",
                        null=True,
                        blank=True,
                        related_name="address_set",
                        on_delete=models.CASCADE,
                    ),
                ),
                ("description", models.TextField("Description", blank=True, null=True)),
                ("tags", AutoCompleteTagsField("Tags", null=True, blank=True)),
                ("tt", models.IntegerField("TT", blank=True, null=True)),
                (
                    "style",
                    models.ForeignKey(
                        Style, verbose_name="Style", blank=True, null=True, on_delete=models.CASCADE
                    ),
                ),
                ("allocated_till", models.DateField("Allocated till", null=True, blank=True)),
            ),
        )
        self.db.create_index("ip_address", ["prefix_id", "vrf_id", "afi", "address"], unique=True)
        # PrefixAccess
        User = self.db.mock_model(model_name="User", db_table="auth_user")
        self.db.create_table(
            "ip_prefixaccess",
            (
                ("id", models.AutoField(verbose_name="ID", primary_key=True, auto_created=True)),
                ("user", models.ForeignKey(User, verbose_name="User", on_delete=models.CASCADE)),
                ("vrf", models.ForeignKey(VRF, verbose_name="VRF", on_delete=models.CASCADE)),
                ("afi", models.CharField("Address Family", max_length=1, choices=AFI_CHOICES)),
                ("prefix", CIDRField("Prefix")),
                ("can_view", models.BooleanField("Can View", default=False)),
                ("can_change", models.BooleanField("Can Change", default=False)),
            ),
        )
        self.db.create_index("ip_prefixaccess", ["user_id", "vrf_id", "afi", "prefix"], unique=True)
        # AddressRange
        self.db.create_table(
            "ip_addressrange",
            (
                ("id", models.AutoField(verbose_name="ID", primary_key=True, auto_created=True)),
                ("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", INETField("Address")),
                ("to_address", INETField("Address")),
                ("description", models.TextField("Description", blank=True, null=True)),
                ("is_locked", models.BooleanField("Is Active", default=True)),
                (
                    "action",
                    models.CharField(
                        "FQDN 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),
                ),
                (
                    "reverse_nses",
                    models.CharField("Reverse NSes", max_length=255, null=True, blank=True),
                ),
                ("tags", AutoCompleteTagsField("Tags", null=True, blank=True)),
                ("tt", models.IntegerField("TT", blank=True, null=True)),
                ("allocated_till", models.DateField("Allocated till", null=True, blank=True)),
            ),
        )
        self.db.create_index(
            "ip_addressrange", ["vrf_id", "afi", "from_address", "to_address"], unique=True
        )

        # PrefixBookmark
        self.db.create_table(
            "ip_prefixbookmark",
            (
                ("id", models.AutoField(verbose_name="ID", primary_key=True, auto_created=True)),
                ("user", models.ForeignKey(User, verbose_name="User", on_delete=models.CASCADE)),
                (
                    "prefix",
                    models.ForeignKey(Prefix, verbose_name="Prefix", on_delete=models.CASCADE),
                ),
            ),
        )
        self.db.create_index("ip_prefixbookmark", ["user_id", "prefix_id"], unique=True)
Ejemplo n.º 12
0
    def forwards(self):
        AFI_CHOICES = [
            ("4", "IPv4"),
            ("6", "IPv6")
        ]
        # Style
        Style = db.mock_model(model_name="Style", db_table="main_style", db_tablespace="", pk_field_name="id",
                              pk_field_type=models.AutoField)
        # VRF Group
        db.add_column(
            "ip_vrfgroup",
            "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"))
        db.alter_column("ip_vrfgroup", "description",
                        models.TextField("Description", blank=True, null=True, default="V"))
        db.add_column("ip_vrfgroup", "tags", AutoCompleteTagsField("Tags", null=True, blank=True))
        # VRF
        db.add_column("ip_vrf", "is_active", models.BooleanField("Is Active", default=True))
        db.add_column("ip_vrf", "afi_ipv4", models.BooleanField("IPv4", default=True))
        db.add_column("ip_vrf", "afi_ipv6", models.BooleanField("IPv6", default=False))
        db.alter_column("ip_vrf", "description", models.TextField("Description", blank=True, null=True, default="V"))
        db.add_column("ip_vrf", "style", models.ForeignKey(Style, verbose_name="Style", blank=True, null=True))
        db.add_column("ip_vrf", "allocated_till", models.DateField("Allocated till", null=True, blank=True))
        # Prefix
        VRF = db.mock_model(model_name="VRF", db_table="ip_vrf", db_tablespace="", pk_field_name="id",
                            pk_field_type=models.AutoField)
        AS = db.mock_model(model_name="AS", db_table="peer_as", db_tablespace="", pk_field_name="id",
                           pk_field_type=models.AutoField)
        VC = db.mock_model(model_name="VC", db_table="vc_vc", db_tablespace="", pk_field_name="id",
                           pk_field_type=models.AutoField)
        ManagedObject = db.mock_model(model_name="ManagedObject", db_table="sa_managedobject", db_tablespace="",
                                      pk_field_name="id", pk_field_type=models.AutoField)
        Prefix = db.mock_model(model_name="Prefix", db_table="ip_prefix", db_tablespace="", pk_field_name="id",
                               pk_field_type=models.AutoField)

        db.create_table("ip_prefix", (
            ("id", models.AutoField(verbose_name="ID", primary_key=True, auto_created=True)),
            ("parent",
             models.ForeignKey(Prefix, related_name="children_set", verbose_name="Parent", null=True, blank=True)),
            ("vrf", models.ForeignKey(VRF, verbose_name="VRF")),
            ("afi", models.CharField("Address Family", max_length=1, choices=AFI_CHOICES)),
            ("prefix", CIDRField("Prefix")),
            ("asn", models.ForeignKey(AS, verbose_name="AS")),
            ("vc", models.ForeignKey(VC, verbose_name="VC", null=True, blank=True)),
            ("description", models.TextField("Description", blank=True, null=True)),
            ("tags", AutoCompleteTagsField("Tags", null=True, blank=True)),
            ("tt", models.IntegerField("TT", blank=True, null=True)),
            ("style", models.ForeignKey(Style, verbose_name="Style", blank=True, null=True)),
            ("allocated_till", models.DateField("Allocated till", null=True, blank=True))
        ))
        db.create_index("ip_prefix", ["vrf_id", "afi", "prefix"], unique=True, db_tablespace="")
        # Address
        db.create_table("ip_address", (
            ("id", models.AutoField(verbose_name="ID", primary_key=True, auto_created=True)),
            ("prefix", models.ForeignKey(Prefix, verbose_name="Prefix")),
            ("vrf", models.ForeignKey(VRF, verbose_name="VRF")),
            ("afi", models.CharField("Address Family", max_length=1, choices=AFI_CHOICES)),
            ("address", INETField("Address")),
            ("fqdn", models.CharField("FQDN", max_length=255)),
            ("mac", MACField("MAC", null=True, blank=True)),
            ("auto_update_mac", models.BooleanField("Auto Update MAC", default=False)),
            ("managed_object", models.ForeignKey(ManagedObject, verbose_name="Managed Object", null=True, blank=True,
                                                 related_name="address_set")),
            ("description", models.TextField("Description", blank=True, null=True)),
            ("tags", AutoCompleteTagsField("Tags", null=True, blank=True)),
            ("tt", models.IntegerField("TT", blank=True, null=True)),
            ("style", models.ForeignKey(Style, verbose_name="Style", blank=True, null=True)),
            ("allocated_till", models.DateField("Allocated till", null=True, blank=True)),
        ))
        db.create_index("ip_address", ["prefix_id", "vrf_id", "afi", "address"], unique=True, db_tablespace="")
        # PrefixAccess
        User = db.mock_model(model_name="User", db_table="auth_user", db_tablespace="", pk_field_name="id",
                             pk_field_type=models.AutoField)
        db.create_table("ip_prefixaccess", (
            ("id", models.AutoField(verbose_name="ID", primary_key=True, auto_created=True)),
            ("user", models.ForeignKey(User, verbose_name="User")),
            ("vrf", models.ForeignKey(VRF, verbose_name="VRF")),
            ("afi", models.CharField("Address Family", max_length=1, choices=AFI_CHOICES)),
            ("prefix", CIDRField("Prefix")),
            ("can_view", models.BooleanField("Can View", default=False)),
            ("can_change", models.BooleanField("Can Change", default=False)),
        ))
        db.create_index("ip_prefixaccess", ["user_id", "vrf_id", "afi", "prefix"], unique=True, db_tablespace="")
        # AddressRange
        db.create_table("ip_addressrange", (
            ("id", models.AutoField(verbose_name="ID", primary_key=True, auto_created=True)),
            ("name", models.CharField("Name", max_length=64, unique=True)),
            ("is_active", models.BooleanField("Is Active", default=True)),
            ("vrf", models.ForeignKey(VRF, verbose_name="VRF")),
            ("afi", models.CharField("Address Family", max_length=1, choices=AFI_CHOICES)),
            ("from_address", INETField("Address")),
            ("to_address", INETField("Address")),
            ("description", models.TextField("Description", blank=True, null=True)),
            ("is_locked", models.BooleanField("Is Active", default=True)),
            ("action", models.CharField(
                "FQDN 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)),
            ("reverse_nses", models.CharField("Reverse NSes", max_length=255, null=True, blank=True)),
            ("tags", AutoCompleteTagsField("Tags", null=True, blank=True)),
            ("tt", models.IntegerField("TT", blank=True, null=True)),
            ("allocated_till", models.DateField("Allocated till", null=True, blank=True)),
        ))
        db.create_index("ip_addressrange", ["vrf_id", "afi", "from_address", "to_address"], unique=True,
                        db_tablespace="")

        # PrefixBookmark
        db.create_table("ip_prefixbookmark", (
            ("id", models.AutoField(verbose_name="ID", primary_key=True, auto_created=True)),
            ("user", models.ForeignKey(User, verbose_name="User")),
            ("prefix", models.ForeignKey(Prefix, verbose_name="Prefix"))
        ))
        db.create_index("ip_prefixbookmark", ["user_id", "prefix_id"], unique=True, db_tablespace="")

        db.send_create_signal("ip", ["Prefix", "Address", "PrefixBookmark"])