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"])
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")), ), )
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)
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")
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))
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)
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), )
class Prefix(models.Model): """ Allocated prefix """ class Meta(object): verbose_name = _("Prefix") verbose_name_plural = _("Prefixes") db_table = "ip_prefix" app_label = "ip" unique_together = [("vrf", "afi", "prefix")] parent = models.ForeignKey( "self", related_name="children_set", verbose_name=_("Parent"), null=True, blank=True) vrf = CachedForeignKey( VRF, verbose_name=_("VRF"), default=VRF.get_global ) afi = models.CharField( _("Address Family"), max_length=1, choices=AFI_CHOICES) prefix = CIDRField(_("Prefix")) name = models.CharField( _("Name"), max_length=255, null=True, blank=True ) profile = DocumentReferenceField( PrefixProfile, null=False, blank=False ) asn = CachedForeignKey( AS, verbose_name=_("AS"), help_text=_("Autonomous system granted with prefix"), null=True, blank=True ) project = CachedForeignKey( Project, verbose_name="Project", on_delete=models.SET_NULL, null=True, blank=True, related_name="prefix_set") vc = models.ForeignKey( VC, verbose_name=_("VC"), null=True, blank=True, on_delete=models.SET_NULL, help_text=_("VC bound to prefix")) description = models.TextField( _("Description"), blank=True, null=True) tags = TagsField("Tags", null=True, blank=True) tt = models.IntegerField( "TT", blank=True, null=True, help_text=_("Ticket #")) state = DocumentReferenceField( State, null=True, blank=True ) allocated_till = models.DateField( _("Allocated till"), null=True, blank=True, help_text=_("Prefix temporary allocated till the date")) ipv6_transition = models.OneToOneField( "self", related_name="ipv4_transition", null=True, blank=True, limit_choices_to={"afi": "6"}, on_delete=models.SET_NULL) prefix_discovery_policy = models.CharField( _("Prefix Discovery Policy"), max_length=1, choices=[ ("P", "Profile"), ("E", "Enable"), ("D", "Disable") ], default="P", blank=False, null=False ) address_discovery_policy = models.CharField( _("Address Discovery Policy"), max_length=1, choices=[ ("P", "Profile"), ("E", "Enable"), ("D", "Disable") ], default="P", blank=False, null=False ) source = models.CharField( "Source", max_length=1, choices=[ ("M", "Manual"), ("i", "Interface"), ("w", "Whois"), ("n", "Neighbor") ], null=False, blank=False, default="M" ) csv_ignored_fields = ["parent"] _id_cache = cachetools.TTLCache(maxsize=1000, ttl=60) def __unicode__(self): return u"%s(%s): %s" % (self.vrf.name, self.afi, self.prefix) @classmethod @cachetools.cachedmethod(operator.attrgetter("_id_cache"), lock=lambda _: id_lock) def get_by_id(cls, id): mo = Prefix.objects.filter(id=id)[:1] if mo: return mo[0] else: return None def get_absolute_url(self): return site.reverse("ip:ipam:vrf_index", self.vrf.id, self.afi, self.prefix) @property def has_transition(self): """ Check prefix has ipv4/ipv6 transition :return: """ if self.is_ipv4: return bool(self.ipv6_transition) else: try: # pylint: disable=pointless-statement self.ipv4_transition # noqa return True except Prefix.DoesNotExist: return False @classmethod def get_parent(cls, vrf, afi, prefix): """ Get nearest closing prefix """ r = Prefix.objects.filter( vrf=vrf, afi=str(afi) ).extra( select={ "masklen": "masklen(prefix)" }, where=["prefix >> %s"], params=[str(prefix)], order_by=["-masklen"] )[:1] if r: return r[0] return None @property def is_ipv4(self): return self.afi == "4" @property def is_ipv6(self): return self.afi == "6" @property def is_root(self): """ Returns true if the prefix is a root of VRF """ return ((self.is_ipv4 and self.prefix == "0.0.0.0/0") or (self.is_ipv6 and self.prefix == "::/0")) def clean(self): """ Field validation """ super(Prefix, self).clean() # Set defaults self.afi = "6" if ":" in self.prefix else "4" # Check prefix is of AFI type if self.is_ipv4: check_ipv4_prefix(self.prefix) elif self.is_ipv6: check_ipv6_prefix(self.prefix) # Set defaults if not self.vrf: self.vrf = VRF.get_global() if not self.is_root: # Set proper parent self.parent = Prefix.get_parent(self.vrf, self.afi, self.prefix) # Check root prefix have no parent if self.is_root and self.parent: raise ValidationError("Root prefix cannot have parent") def save(self, **kwargs): """ Save prefix """ self.clean() super(Prefix, self).save(**kwargs) # Rebuild tree if necessary # Reconnect children children prefixes c = connection.cursor() c.execute( """ UPDATE %s SET parent_id=%%s WHERE vrf_id=%%s AND afi=%%s AND prefix << %%s AND parent_id=%%s """ % Prefix._meta.db_table, [ self.id, self.vrf.id, self.afi, self.prefix, self.parent.id if self.parent else None ] ) # Reconnect children addresses c.execute( """ UPDATE %s SET prefix_id=%%s WHERE prefix_id=%%s AND address << %%s """ % Address._meta.db_table, [ self.id, self.parent.id if self.parent else None, self.prefix ] ) def delete(self, *args, **kwargs): """ Delete prefix """ if self.is_root and not getattr(self, "_disable_delete_protection", False): raise ValidationError("Cannot delete root prefix") # Reconnect children prefixes self.children_set.update(parent=self.parent) # Reconnect children addresses self.address_set.update(prefix=self.parent) # Unlink dual-stack allocations # self.clear_transition() # Remove bookmarks self.prefixbookmark_set.all().delete() # Finally delete super(Prefix, self).delete(*args, **kwargs) def delete_recursive(self): """ Delete prefix and all descendancies """ # Unlink dual-stack allocations # self.clear_transition() # Recursive delete # Get nested prefixes ids = Prefix.objects.filter( vrf=self.vrf, afi=self.afi ).extra( where=["prefix <<= %s"], params=[self.prefix] ).values_list("id", flat=True) # zones = set() for a in Address.objects.filter(prefix__in=ids): zones.add(a.address) zones.add(a.fqdn) # Delete nested addresses Address.objects.filter(prefix__in=ids).delete() # Delete nested prefixes Prefix.objects.filter(id__in=ids).delete() # Delete permissions PrefixAccess.objects.filter( vrf=self.vrf, afi=self.afi ).extra( where=["prefix <<= %s"], params=[self.prefix] ) # Touch dns zones for z in zones: DNSZone.touch(z) @property def maintainers(self): """ List of persons having write access @todo: PostgreSQL-independent implementation """ return User.objects.raw( """ SELECT id,username,first_name,last_name FROM %s u WHERE is_active=TRUE AND (is_superuser=TRUE OR EXISTS(SELECT id FROM %s a WHERE user_id=u.id AND vrf_id=%%s AND afi=%%s AND prefix>>=%%s AND can_change=TRUE )) ORDER BY username """ % (User._meta.db_table, PrefixAccess._meta.db_table), [ self.vrf.id, self.afi, self.prefix ] ) @property def short_description(self): """ Returns first line of description :return: """ if self.description: return self.description.split("\n", 1)[0].strip() return "" @property def netmask(self): """ returns Netmask for IPv4 :return: """ if self.is_ipv4: return IPv4(self.prefix).netmask.address return None @property def broadcast(self): """ Returns Broadcast for IPv4 :return: """ if self.is_ipv4: return IPv4(self.prefix).last.address return None @property def wildcard(self): """ Returns Cisco wildcard for IPv4 :return: """ if self.is_ipv4: return IPv4(self.prefix).wildcard.address return "" @property def size(self): """ Returns IPv4 prefix size :return: """ if self.is_ipv4: return IPv4(self.prefix).size return None def can_view(self, user): """ Returns True if user has view access :param user: :return: """ return PrefixAccess.user_can_view( user, self.vrf, self.afi, self.prefix) def can_change(self, user): """ Returns True if user has change access :param user: :return: """ return PrefixAccess.user_can_change( user, self.vrf, self.afi, self.prefix) def has_bookmark(self, user): """ Check the user has bookmark on prefix :param user: :return: """ from .prefixbookmark import PrefixBookmark try: PrefixBookmark.objects.get(user=user, prefix=self) return True except PrefixBookmark.DoesNotExist: return False def toggle_bookmark(self, user): """ Toggle user bookmark. Returns new bookmark state :param user: :return: """ from .prefixbookmark import PrefixBookmark b, created = PrefixBookmark.objects.get_or_create(user=user, prefix=self) if created: return True b.delete() return False def get_index(self): """ Full-text search """ content = [self.prefix] card = "Prefix %s" % self.prefix if self.description: content += [self.description] card += " (%s)" % self.description r = { "id": "ip.prefix:%s" % self.id, "title": self.prefix, "content": "\n".join(content), "card": card } if self.tags: r["tags"] = self.tags return r @classmethod def get_search_result_url(cls, obj_id): return "/api/card/view/prefix/%s/" % obj_id @property def address_ranges(self): """ All prefix-related address ranges :return: """ return list( AddressRange.objects.raw( """ SELECT * FROM ip_addressrange WHERE vrf_id=%s AND afi=%s AND is_active=TRUE AND ( from_address << %s OR to_address << %s OR %s BETWEEN from_address AND to_address ) ORDER BY from_address, to_address """, [ self.vrf.id, self.afi, self.prefix, self.prefix, self.prefix ] ) ) def rebase(self, vrf, new_prefix): """ Rebase prefix to a new location :param vrf: :param new_prefix: :return: """ # b = IP.prefix(self.prefix) nb = IP.prefix(new_prefix) # Validation if vrf == self.vrf and self.prefix == new_prefix: raise ValueError("Cannot rebase to self") if b.afi != nb.afi: raise ValueError("Cannot change address family during rebase") if b.mask < nb.mask: raise ValueError("Cannot rebase to prefix of lesser size") # Rebase prefix and all nested prefixes # Parents are left untouched for p in Prefix.objects.filter( vrf=self.vrf, afi=self.afi ).extra( where=["prefix <<= %s"], params=[self.prefix] ): np = IP.prefix(p.prefix).rebase(b, nb).prefix # Prefix.objects.filter(pk=p.pk).update(prefix=np, vrf=vrf) p.prefix = np p.vrf = vrf p.save() # Raise events # Rebase addresses # Parents are left untouched for a in Address.objects.filter( vrf=self.vrf, afi=self.afi ).extra( where=["address <<= %s"], params=[self.prefix] ): na = IP.prefix(a.address).rebase(b, nb).address # Address.objects.filter(pk=a.pk).update(address=na, vrf=vrf) a.address = na a.vrf = vrf a.save() # Raise events # Rebase permissions # move all permissions to the nested blocks for pa in PrefixAccess.objects.filter( vrf=self.vrf ).extra( where=["prefix <<= %s"], params=[self.prefix] ): np = IP.prefix(pa.prefix).rebase(b, nb).prefix PrefixAccess.objects.filter(pk=pa.pk).update( prefix=np, vrf=vrf) # create permissions for covered blocks for pa in PrefixAccess.objects.filter( vrf=self.vrf ).extra( where=["prefix >> %s"], params=[self.prefix] ): PrefixAccess( user=pa.user, vrf=vrf, afi=pa.afi, prefix=new_prefix, can_view=pa.can_view, can_change=pa.can_change ).save() # @todo: Rebase bookmarks # @todo: Update caches # Return rebased prefix return Prefix.objects.get(pk=self.pk) # Updated object @property def nested_prefix_set(self): """ Queryset returning all nested prefixes inside the prefix """ return Prefix.objects.filter(vrf=self.vrf, afi=self.afi).extra( where=["prefix <<= %s"], params=[self.prefix]) @property def nested_address_set(self): """ Queryset returning all nested addresses inside the prefix """ return Address.objects.filter(vrf=self.vrf, afi=self.afi).extra( where=["address <<= %s"], params=[self.prefix]) def iter_free(self): """ Generator returning all available free prefixes inside :return: """ for fp in IP.prefix(self.prefix).iter_free( [p.prefix for p in self.children_set.all()]): yield str(fp) @property def effective_address_discovery(self): if self.address_discovery_policy == "P": return self.profile.address_discovery_policy return self.address_discovery_policy @property def effective_prefix_discovery(self): if self.prefix_discovery_policy == "P": return self.profile.prefix_discovery_policy return self.prefix_discovery_policy @property def usage(self): if self.is_ipv4: usage = getattr(self, "_usage_cache", None) if usage is not None: # Use update_prefixes_usage results return usage size = IPv4(self.prefix).size if not size: return 100.0 n_ips = Address.objects.filter(prefix=self).count() n_pfx = sum( IPv4(p).size for p in Prefix.objects.filter(parent=self).only("prefix").values_list("prefix", flat=True) ) if n_ips: if size > 2: # Not /31 or /32 size -= 2 # Exclude broadcast and network return float(n_ips + n_pfx) * 100.0 / float(size) else: return None @property def usage_percent(self): u = self.usage if u is None: return "" return "%.2f%%" % u @staticmethod def update_prefixes_usage(prefixes): """ Bulk calculate and update prefixes usages :param prefixes: List of Prefix instances :return: """ # Filter IPv4 only ipv4_prefixes = [p for p in prefixes if p.is_ipv4] # Calculate nested prefixrs usage = defaultdict(int) for parent, prefix in Prefix.objects.filter( parent__in=ipv4_prefixes ).values_list("parent", "prefix"): ln = int(prefix.split("/")[1]) usage[parent] += 2 ** (32 - ln) # Calculate nested addresses has_address = set() for parent, count in Address.objects.filter( prefix__in=ipv4_prefixes ).values("prefix").annotate( count=models.Count("prefix") ).values_list("prefix", "count"): usage[parent] += count has_address.add(parent) # Update usage cache for p in ipv4_prefixes: ln = int(p.prefix.split("/")[1]) size = 2 ** (32 - ln) if p.id in has_address and size > 2: # Not /31 or /32 size -= 2 # Exclude broadcast and network p._usage_cache = float(usage[p.id]) * 100.0 / float(size) def is_empty(self): """ Check prefix is empty and does not contain nested prefixes and addresses :return: """ if Prefix.objects.filter(parent=self).count() > 0: return False if Address.objects.filter(prefix=self).count() > 0: return False return True def disable_delete_protection(self): """ Disable root delete protection :return: """ self._disable_delete_protection = True def get_effective_as(self): """ Return effective AS (first found upwards) :return: AS instance or None """ if self.asn: return self.asn if not self.parent: return None return self.parent.get_effective_as()
class AddressRange(NOCModel): class Meta(object): verbose_name = _("Address Range") db_table = "ip_addressrange" app_label = "ip" unique_together = [("vrf", "afi", "from_address", "to_address")] name = models.CharField(_("Name"), max_length=64, unique=True) is_active = models.BooleanField(_("Is Active"), default=True) vrf = models.ForeignKey(VRF, verbose_name=_("VRF"), on_delete=models.CASCADE) afi = models.CharField(_("Address Family"), max_length=1, choices=AFI_CHOICES) from_address = CIDRField(_("From Address")) to_address = CIDRField(_("To address")) description = models.TextField(_("Description"), blank=True, null=True) is_locked = models.BooleanField( _("Is Locked"), default=False, help_text=_("Check to deny address creation or editing within the range"), ) action = models.CharField( _("Action"), max_length=1, choices=[ ("N", _("Do nothing")), ("G", _("Generate FQDNs")), ("D", _("Partial reverse zone delegation")), ], default="N", ) fqdn_template = models.CharField( _("FQDN Template"), max_length=255, null=True, blank=True, help_text=_("Template to generate FQDNs when 'Action' set to 'Generate FQDNs'"), ) reverse_nses = models.CharField( _("Reverse NSes"), max_length=255, null=True, blank=True, help_text=_( "Comma-separated list of NSes to partial reverse zone delegation when " "'Action' set to 'Partial reverse zone delegation" ), ) tags = TagsField(_("Tags"), null=True, blank=True) tt = models.IntegerField("TT", blank=True, null=True, help_text=_("Ticket #")) allocated_till = models.DateField( _("Allocated till"), null=True, blank=True, help_text=_("VRF temporary allocated till the date"), ) def __str__(self): return "%s (IPv%s): %s -- %s" % ( self.vrf.name, self.afi, self.from_address, self.to_address, ) def iter_changed_datastream(self, changed_fields=None): if not config.datastream.enable_dnszone: return from noc.dns.models.dnszone import DNSZone if self.action == "D": zone = DNSZone.get_reverse_for_address(self.from_address) if zone: yield "dnszone", zone.id def clean(self): """ Field validation """ super(AddressRange, self).clean() # Check prefix is of AFI type if self.afi == "4": check_ipv4(self.from_address) check_ipv4(self.to_address) elif self.afi == "6": check_ipv6(self.from_address) check_ipv6(self.to_address) def save(self, *args, **kwargs): def generate_fqdns(): # Prepare FQDN template t = Template(self.fqdn_template) # Sync FQDNs sn = 0 for ip in self.addresses: # Generage FQDN vars = {"afi": self.afi, "vrf": self.vrf, "range": self, "n": sn} sn += 1 if self.afi == "4": i = ip.address.split(".") vars["ip"] = i # ip.0 .. ip.3 # ip1, ip2, ip3, ip4 for backward compatibility for n, i in enumerate(i): vars["ip%d" % (n + 1)] = i elif self.afi == "6": vars["ip"] = ip.digits # ip.0 .. ip.31 fqdn = t.render(Context(vars)) description = "Generated by address range '%s'" % self.name # Create or update address record when necessary a, created = Address.objects.get_or_create( vrf=self.vrf, afi=self.afi, address=ip.address ) if created: a.fqdn = fqdn a.description = description a.save() elif a.fqdn != fqdn or a.description != a.description: a.fqdn = fqdn a.description = description a.save() created = self.id is None if not created: # Get old values old = AddressRange.objects.get(id=self.id) super(AddressRange, self).save(*args, **kwargs) if created: # New if self.action == "G": generate_fqdns() else: # Changed if old.action == "G" and self.action != "G": # Drop all auto-generated IPs Address.objects.filter( vrf=self.vrf, afi=self.afi, address__gte=self.from_address, address__lte=self.to_address, ).delete() elif old.action != "G" and self.action == "G": # Generate IPs generate_fqdns() elif self.action == "G": # Check for boundaries change if IP.prefix(old.from_address) < IP.prefix(self.from_address): # Lower boundary raised up. Clean up addresses falled out of range Address.objects.filter( vrf=self.vrf, afi=self.afi, address__gte=old.from_address, address__lt=self.to_address, ).delete() if IP.prefix(old.to_address) > IP.prefix(self.to_address): # Upper boundary is lowered. Clean up addressess falled out of range Address.objects.filter( vrf=self.vrf, afi=self.afi, address__gt=self.to_address, address__lte=old.to_address, ).delete() # Finally recheck FQDNs generate_fqdns() @property def short_description(self): """ First line of description """ if self.description: return self.description.split("\n", 1)[0].strip() else: return "" @property def addresses(self): """ Generator returning all addresses in range """ return IP.prefix(self.from_address).iter_address(until=IP.prefix(self.to_address)) @classmethod def get_overlapping_ranges(cls, vrf, afi, from_address, to_address): """ Returns a list of overlapping ranges :param vrf: :param afi: :param from_address: :param to_address: :return: """ return AddressRange.objects.raw( """ SELECT * FROM ip_addressrange WHERE vrf_id=%(vrf)s AND afi=%(afi)s AND is_active AND ( from_address BETWEEN %(from_address)s AND %(to_address)s OR to_address BETWEEN %(from_address)s AND %(to_address)s OR %(from_address)s BETWEEN from_address AND to_address OR %(to_address)s BETWEEN from_address AND to_address ) """, {"vrf": vrf.id, "afi": afi, "from_address": from_address, "to_address": to_address}, ) @property def overlapping_ranges(self): """ Returns a queryset with overlapped ranges :return: """ return self.get_overlapping_ranges(self.vrf, self.afi, self.from_address, self.to_address) @classmethod def address_is_locked(cls, vrf, afi, address): """ Check wrether address is locked by any range """ return AddressRange.objects.filter( vrf=vrf, afi=afi, is_locked=True, is_active=True, from_address__lte=address, to_address__gte=address, ).exists()
class 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))
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)
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"])