Ejemplo n.º 1
0
class Domain(CleanSave, TimestampedModel):
    """A `Domain`.

    :ivar name: The DNS stuffix for this zone
    :ivar authoritative: MAAS manages this (forward) DNS zone.
    :ivar objects: An instance of the class :class:`DomainManager`.
    """
    class Meta(DefaultMeta):
        """Needed for South to recognize this model."""

        verbose_name = "Domain"
        verbose_name_plural = "Domains"

    objects = DomainManager()

    name = DomainNameField(
        max_length=256,
        editable=True,
        null=False,
        blank=False,
        unique=True,
        validators=[validate_domain_name],
    )

    # We manage the forward zone.
    authoritative = NullBooleanField(default=True,
                                     db_index=True,
                                     editable=True)

    # Default TTL for this Domain.
    # If None and not overridden lower, then we will use the global default.
    ttl = PositiveIntegerField(default=None, null=True, blank=True)

    def update_kms_srv(self, kms_host=-1):
        # avoid recursive imports
        from maasserver.models import DNSData, DNSResource

        # Since None and '' are both valid values, we use -1 as the "I want the
        # default value" indicator, and fetch the Config value accordingly.
        if kms_host == -1:
            kms_host = Config.objects.get_config("windows_kms_host")
        if kms_host is None or kms_host == "":
            # No more Config.windows_kms_host, so we need to delete the kms
            # host entries that we may have created.  The for loop is over 0 or
            # 1 DNSResource records
            for dnsrr in self.dnsresource_set.filter(name="_vlmcs._tcp"):
                dnsrr.dnsdata_set.filter(
                    rrtype="SRV", rrdata__startswith="0 0 1688 ").delete()
        else:
            # force kms_host to be an FQDN (with trailing dot.)
            validate_domain_name(kms_host)
            if not kms_host.endswith("."):
                kms_host += "."
            # The windows_kms_host config parameter only manages priority 0,
            # weight 0, port 1688.  To do something different, use the
            # dnsresources api.
            srv_data = "0 0 1688 %s" % kms_host
            dnsrr, _ = DNSResource.objects.get_or_create(domain_id=self.id,
                                                         name="_vlmcs._tcp",
                                                         defaults={})
            srv, created = DNSData.objects.update_or_create(
                dnsresource_id=dnsrr.id,
                rrtype="SRV",
                rrdata__startswith="0 0 1688 ",
                defaults=dict(rrdata=srv_data),
            )

    def get_base_ttl(self, rrtype, default_ttl):
        # If there is a Resource Record set, which has a non-None TTL, then it
        # wins.  Otherwise our ttl if we have one, or the passed-in default.
        from maasserver.models import DNSData

        rrset = (DNSData.objects.filter(
            rrtype=rrtype, ttl__isnull=False).filter(
                Q(dnsresource__name="@") | Q(dnsresource__name="")).filter(
                    dnsresource__domain_id=self.id))
        if rrset.count() > 0:
            return rrset.first().ttl
        elif self.ttl is not None:
            return self.ttl
        else:
            return default_ttl

    @property
    def resource_count(self):
        """How many DNSResource names are attached to this domain."""
        from maasserver.models.dnsresource import DNSResource

        return DNSResource.objects.filter(domain_id=self.id).count()

    @property
    def resource_record_count(self):
        """How many total Resource Records come from non-Nodes."""
        count = 0
        for resource in self.dnsresource_set.all():
            count += len(resource.ip_addresses.all())
            count += len(resource.dnsdata_set.all())
        return count

    def add_delegations(self, mapping, ns_host_name, dns_ip_list, default_ttl):
        """Find any subdomains that need to be added to this domain, and add
        them.

        This function updates the mapping to add delegations and any needed
        glue records for any domains that are descendants of this one.  These
        are not in the database, because they may be multi-lable (foo.bar.maas
        and maas are domains, but bar.maas isn't), and we don't want to allow
        multi-label elements in the model, due to the extreme complexity it
        introduces.
        """

        # Recursive includes.
        from maasserver.models.dnsresource import separate_fqdn

        subdomains = Domain.objects.filter(name__endswith="." + self.name)
        possible = subdomains[:]
        # Anything with an intervening domain should not be delegated from
        # this domain.
        for middle in possible:
            subdomains = subdomains.exclude(name__endswith="." + middle.name)
        for subdomain in subdomains:
            nsttl = subdomain.get_base_ttl("NS", default_ttl)
            ttl = subdomain.get_base_ttl("A", default_ttl)
            # Strip off this domain name from the end of the resource name.
            name = subdomain.name[:-len(self.name) - 1]
            # If we are authoritative for the subdomain, then generate the NS
            # and any needed glue records.  These will automatically be in the
            # child zone.
            if subdomain.authoritative:
                mapping[name].rrset.add((nsttl, "NS", ns_host_name))
                if ns_host_name.endswith("." + self.name):
                    # The ns_host_name lives in a subdomain of this subdomain,
                    # and we are authoritative for that.  We need to add glue
                    # to this subdomain.
                    ns_name = separate_fqdn(ns_host_name, "NS", self.name)[0]
                    for addr in dns_ip_list:
                        if IPAddress(addr).version == 4:
                            mapping[ns_name].rrset.add((ttl, "A", addr))
                        else:
                            mapping[ns_name].rrset.add((ttl, "AAAA", addr))
            # Also return any NS RRset from the dnsdata for the '@' label in
            # that zone.  Add glue records for NS hosts as needed.
            for lhs in subdomain.dnsresource_set.filter(name="@"):
                for data in lhs.dnsdata_set.filter(rrtype="NS"):
                    mapping[name].rrset.add((ttl, data.rrtype, data.rrdata))
                    # Figure out if we need to add glue, and generate it if
                    # needed.
                    if data.rrdata == "@":
                        # This glue is the responsibility of the admin.
                        continue
                    if not data.rrdata.endswith("."):
                        # Non-qualified NSRR, append the domain.
                        fqdn = "%s.%s." % (data.rrdata, subdomain.name)
                    elif not data.rrdata.endswith("%s." % subdomain.name):
                        continue
                    else:
                        # NSRR is an FQDN in or under subdomain.
                        fqdn = data.rrdata
                    # If we get here, then the NS host is in subdomain, or some
                    # subdomain thereof, and is not '@' in the subdomain.
                    # Strip the trailing dot, and split the FQDN.
                    h_name, d_name = separate_fqdn(fqdn[:-1], "NS")
                    # Make sure we look in the right domain for the addresses.
                    if d_name == subdomain.name:
                        nsrrset = subdomain.dnsresource_set.filter(name=h_name)
                    else:
                        nsdomain = Domain.objects.filter(name=d_name)
                        if not nsdomain.exists():
                            continue
                        else:
                            nsdomain = nsdomain[0]
                        nsrrset = nsdomain.dnsresource_set.filter(name=h_name)
                        h_name = fqdn[:-len(subdomain.name) - 2]
                    for nsrr in nsrrset:
                        for addr in nsrr.get_addresses():
                            if IPAddress(addr).version == 4:
                                mapping[h_name].rrset.add((ttl, "A", addr))
                            else:
                                mapping[h_name].rrset.add((ttl, "AAAA", addr))

    def __str__(self):
        return "name=%s" % self.get_name()

    def __unicode__(self):
        return "name=%s" % self.get_name()

    def is_default(self):
        """Returns True if this is the default domain, False otherwise."""
        # Iterate over cached objects. (There should be just one, in fact.)
        for defaults in self.globaldefault_set.all():
            if defaults.domain_id == self.id:
                return True
        return False

    def get_name(self):
        """Return the name of the domain."""
        return self.name

    def delete(self):
        if self.is_default():
            raise ValidationError(
                "This domain is the default domain, it cannot be deleted.")
        super().delete()

    def save(self, *args, **kwargs):
        created = self.id is None
        super().save(*args, **kwargs)
        if created:
            self.update_kms_srv()
        # If there is a DNSResource in our parent domain that matches this
        # domain name, the migrate the DNSResource to the new domain.
        parent = Domain.objects.filter(name=".".join(self.name.split(".")[1:]))
        if parent.exists():
            me = parent[0].dnsresource_set.filter(name=self.name.split(".")[0])
            for rr in me:
                rr.name = "@"
                rr.domain = self
                rr.save()

    def clean_name(self):
        # Automatically strip any trailing dot from the domain name.
        if self.name is not None and self.name.endswith("."):
            self.name = self.name[:-1]

    def clean(self, *args, **kwargs):
        super().clean(*args, **kwargs)
        self.clean_name()

    def render_json_for_related_rrdata(self,
                                       for_list=False,
                                       include_dnsdata=True,
                                       as_dict=False,
                                       user=None):
        """Render a representation of this domain's related non-IP data,
        suitable for converting to JSON.

        :return: data"""
        from maasserver.models import DNSData, StaticIPAddress

        if include_dnsdata is True:
            rr_mapping = DNSData.objects.get_hostname_dnsdata_mapping(
                self, raw_ttl=True)
        else:
            # Circular imports.
            from maasserver.models.dnsdata import HostnameRRsetMapping

            rr_mapping = defaultdict(HostnameRRsetMapping)
        # Smash the IP Addresses in the rrset mapping, so that the far end
        # only needs to worry about one thing.
        ip_mapping = StaticIPAddress.objects.get_hostname_ip_mapping(
            self, raw_ttl=True)
        for hostname, info in ip_mapping.items():
            if (user is not None and not user.is_superuser
                    and info.user_id is not None and info.user_id != user.id):
                continue
            entry = rr_mapping[hostname[:-len(self.name) - 1]]
            entry.dnsresource_id = info.dnsresource_id
            if info.system_id is not None:
                entry.system_id = info.system_id
                entry.node_type = info.node_type
            if info.user_id is not None:
                entry.user_id = info.user_id
            for ip in info.ips:
                record_type = "AAAA" if IPAddress(ip).version == 6 else "A"
                entry.rrset.add((info.ttl, record_type, ip, None))
        if as_dict is True:
            result = OrderedDict()
        else:
            result = []
        for hostname, info in rr_mapping.items():
            data = [
                {
                    "name": hostname,
                    "system_id": info.system_id,
                    "node_type": info.node_type,
                    "user_id": info.user_id,
                    "dnsresource_id": info.dnsresource_id,
                    "ttl": ttl,
                    "rrtype": rrtype,
                    "rrdata": rrdata,
                    "dnsdata_id": dnsdata_id,
                } for ttl, rrtype, rrdata, dnsdata_id in info.rrset
                if (info.user_id is None or user is None or user.is_superuser
                    or (info.user_id is not None and info.user_id == user.id))
            ]
            if as_dict is True:
                existing = result.get(hostname, [])
                existing.extend(data)
                result[hostname] = existing
            else:
                result.extend(data)
        return result
Ejemplo n.º 2
0
class Discovery(CleanSave, ViewModel):
    """A `Discovery` object represents the combined data for a network entity
    that MAAS believes has been discovered.

    Note that this class is backed by the `maasserver_discovery` view. Any
    updates to this model must be reflected in `maasserver/dbviews.py` under
    the `maasserver_discovery` view.
    """
    class Meta(DefaultViewMeta):
        # When managed is False, Django will not create a migration for this
        # model class. This is required for model classes based on views.
        verbose_name = "Discovery"
        verbose_name_plural = "Discoveries"

    def __str__(self):
        return "<Discovery: %s at %s via %s>" % (
            self.ip,
            self.last_seen,
            self.observer_interface.get_log_string(),
        )

    discovery_id = CharField(max_length=256,
                             editable=False,
                             null=True,
                             blank=False,
                             unique=True)

    neighbour = ForeignKey(
        "Neighbour",
        unique=False,
        blank=False,
        null=False,
        editable=False,
        on_delete=DO_NOTHING,
    )

    # Observed IP address.
    ip = MAASIPAddressField(
        unique=False,
        null=True,
        editable=False,
        blank=True,
        default=None,
        verbose_name="IP",
    )

    mac_address = MACAddressField(unique=False,
                                  null=True,
                                  blank=True,
                                  editable=False)

    first_seen = DateTimeField(editable=False)

    last_seen = DateTimeField(editable=False)

    mdns = ForeignKey(
        "MDNS",
        unique=False,
        blank=True,
        null=True,
        editable=False,
        on_delete=DO_NOTHING,
    )

    # Hostname observed from mDNS-browse.
    hostname = CharField(max_length=256,
                         editable=False,
                         null=True,
                         blank=False,
                         unique=False)

    observer = ForeignKey(
        "Node",
        unique=False,
        blank=False,
        null=False,
        editable=False,
        on_delete=DO_NOTHING,
    )

    observer_system_id = CharField(max_length=41, unique=False, editable=False)

    # The hostname of the node that made the discovery.
    observer_hostname = DomainNameField(max_length=256,
                                        editable=False,
                                        null=True,
                                        blank=False,
                                        unique=False)

    # Rack interface the discovery was observed on.
    observer_interface = ForeignKey(
        "Interface",
        unique=False,
        blank=False,
        null=False,
        editable=False,
        on_delete=DO_NOTHING,
    )

    observer_interface_name = CharField(blank=False,
                                        editable=False,
                                        max_length=255)

    fabric = ForeignKey(
        "Fabric",
        unique=False,
        blank=False,
        null=False,
        editable=False,
        on_delete=DO_NOTHING,
    )

    fabric_name = CharField(max_length=256,
                            editable=False,
                            null=True,
                            blank=True,
                            unique=False)

    vlan = ForeignKey(
        "VLAN",
        unique=False,
        blank=False,
        null=False,
        editable=False,
        on_delete=DO_NOTHING,
    )

    vid = IntegerField(null=True, blank=True)

    # These will only be non-NULL if we found a related Subnet.
    subnet = ForeignKey(
        "Subnet",
        unique=False,
        blank=True,
        null=True,
        editable=False,
        on_delete=DO_NOTHING,
    )

    subnet_cidr = CIDRField(blank=True,
                            unique=False,
                            editable=False,
                            null=True)

    is_external_dhcp = NullBooleanField(blank=True,
                                        unique=False,
                                        editable=False,
                                        null=True)

    objects = DiscoveryManager()

    @property
    def mac_organization(self):
        return get_mac_organization(str(self.mac_address))