This file is indexed.

/usr/lib/python2.7/dist-packages/maasserver/models/dhcplease.py is in python-django-maas 1.5.4+bzr2294-0ubuntu1.2.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# Copyright 2013 Canonical Ltd.  This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).

"""Node IP/MAC mappings as leased from the workers' DHCP servers."""

from __future__ import (
    absolute_import,
    print_function,
    unicode_literals,
    )

str = None

__metaclass__ = type
__all__ = [
    'DHCPLease',
    ]


from django.db import connection
from django.db.models import (
    ForeignKey,
    IPAddressField,
    Manager,
    Model,
    )
from django.db.models.signals import post_delete
from django.dispatch import receiver
from maasserver import DefaultMeta
from maasserver.fields import MACAddressField
from maasserver.models.cleansave import CleanSave
from maasserver.models.macaddress import MACAddress
from maasserver.utils import strip_domain


class DHCPLeaseManager(Manager):
    """Utility that manages :class:`DHCPLease` objects.

    This will be a large and busy part of the database.  Try to perform
    operations in bulk, using this manager class, where at all possible.
    """

    def _delete_obsolete_leases(self, nodegroup, current_leases):
        """Delete leases for `nodegroup` that aren't in `current_leases`."""
        cursor = connection.cursor()
        clauses = ["nodegroup_id = %s" % nodegroup.id]
        if len(current_leases) > 0:
            leases = tuple(current_leases.items())
        if len(current_leases) == 0:
            pass
        elif len(current_leases) == 1:
            clauses.append(cursor.mogrify("(ip, mac) <> %s", leases))
        else:
            clauses.append(cursor.mogrify("(ip, mac) NOT IN %s", [leases]))
        cursor.execute(
            "DELETE FROM maasserver_dhcplease WHERE %s"
            % " AND ".join(clauses)),

    def _get_leased_ips(self, nodegroup):
        """Query the currently leased IP addresses for `nodegroup`."""
        cursor = connection.cursor()
        cursor.execute(
            "SELECT ip FROM maasserver_dhcplease WHERE nodegroup_id = %s"
            % nodegroup.id)
        return frozenset(ip for ip, in cursor.fetchall())

    def _add_missing_leases(self, nodegroup, leases):
        """Add items from `leases` that aren't in the database yet.

        This is assumed to be run right after _delete_obsolete_leases,
        so that a lease from `leases` is in the database if and only if
        `nodegroup` has a DHCPLease with the same `ip` field.  There
        can't be any DHCPLease entries with the same `ip` as in `leases`
        but a different `mac`.

        :return: Iterable of newly-leased IP addresses.
        """
        leased_ips = self._get_leased_ips(nodegroup)
        new_leases = tuple(
            (nodegroup.id, ip, mac)
            for ip, mac in leases.items() if ip not in leased_ips)
        if len(new_leases) > 0:
            cursor = connection.cursor()
            new_tuples = ", ".join(
                cursor.mogrify("%s", [lease]) for lease in new_leases)
            cursor.execute("""
                INSERT INTO maasserver_dhcplease (nodegroup_id, ip, mac)
                VALUES %s
                """ % new_tuples)
        return [ip for nodegroup_id, ip, mac in new_leases]

    def update_leases(self, nodegroup, leases):
        """Refresh our knowledge of a node group's IP mappings.

        This deletes entries that are no longer current, adds new ones,
        and updates or replaces ones that have changed.

        :param nodegroup: The node group that these updates are for.
        :param leases: A dict describing all current IP/MAC mappings as
            managed by the node group's DHCP server.  Keys are IP
            addresses, values are MAC addresses.  Any :class:`DHCPLease`
            entries for `nodegroup` that are not in `leases` will be
            deleted.
        :return: Iterable of IP addresses that were newly leased.
        """
        # Avoid circular imports.
        from maasserver import dns

        self._delete_obsolete_leases(nodegroup, leases)
        new_leases = self._add_missing_leases(nodegroup, leases)
        if len(new_leases) > 0:
            dns.change_dns_zones([nodegroup])
        return new_leases

    def get_hostname_ip_mapping(self, nodegroup):
        """Return a mapping {hostnames -> ips} for the currently leased
        IP addresses for the nodes in `nodegroup`.

        For each node, this will consider only the oldest `MACAddress` that
        has a `DHCPLease`.

        Any domain will be stripped from the hostnames.
        """
        cursor = connection.cursor()

        # The "DISTINCT ON" gives us the first matching row for any
        # given hostname, in the query's ordering.
        # The ordering must start with the hostname so that the database
        # can do this efficiently.  The next ordering criterion is the
        # MACAddress id, so that if there are multiple rows with the
        # same hostname, we get the one with the oldest MACAddress.
        #
        # If this turns out to be inefficient, be sure to try selecting
        # on node.nodegroup_id instead of lease.nodegroup_id.  It has
        # the same effect but may perform differently.
        cursor.execute("""
            SELECT DISTINCT ON (node.hostname)
                node.hostname, lease.ip
            FROM maasserver_macaddress AS mac
            JOIN maasserver_node AS node ON node.id = mac.node_id
            JOIN maasserver_dhcplease AS lease ON lease.mac = mac.mac_address
            WHERE lease.nodegroup_id = %s
            ORDER BY node.hostname, mac.id
            """, (nodegroup.id, ))
        return dict(
            (strip_domain(hostname), ip)
            for hostname, ip in cursor.fetchall()
            )


class DHCPLease(CleanSave, Model):
    """A known mapping of an IP address to a MAC address.

    These correspond to the latest-known DHCP leases handed out to nodes
    (or potential nodes -- they may not have been enlisted yet!) by the
    node group worker's DHCP server.
    """

    class Meta(DefaultMeta):
        """Needed for South to recognize this model."""

    objects = DHCPLeaseManager()

    nodegroup = ForeignKey('maasserver.NodeGroup', null=False, editable=False)
    ip = IPAddressField(null=False, editable=False, unique=True)
    mac = MACAddressField(null=False, editable=False, unique=False)

    def __unicode__(self):
        return "%s->%s" % (self.ip, self.mac)


# Register a signal receiver so that whenever a MAC address is deleted,
# the corresponding DHCPLease is deleted too.
@receiver(post_delete, sender=MACAddress)
def delete_lease(sender, instance, **kwargs):
    DHCPLease.objects.filter(mac=instance.mac_address).delete()