Merge lp:~gmb/maas/add-ptr-records-bug-1382190 into lp:~maas-committers/maas/trunk

Proposed by Graham Binns
Status: Rejected
Rejected by: Graham Binns
Proposed branch: lp:~gmb/maas/add-ptr-records-bug-1382190
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 278 lines (+151/-5)
6 files modified
src/maasserver/dns/tests/test_zonegenerator.py (+8/-0)
src/maasserver/dns/zonegenerator.py (+4/-1)
src/maasserver/models/dhcplease.py (+29/-1)
src/maasserver/models/tests/test_dhcplease.py (+64/-3)
src/maasserver/utils/__init__.py (+19/-0)
src/maasserver/utils/tests/test_utils.py (+27/-0)
To merge this branch: bzr merge lp:~gmb/maas/add-ptr-records-bug-1382190
Reviewer Review Type Date Requested Status
Christian Reis (community) Needs Information
Andres Rodriguez (community) Approve
Julian Edwards (community) Approve
Review via email: mp+239493@code.launchpad.net

Commit message

Add DNS PTR entries in both forward and reverse zones for hosts which are not registered in MAAS. This is particularly useful for LXC containers, which can't be treated as nodes by MAAS, but which sometimes require a resolvable hostname.

There is a lag of up to 60 seconds between the DHCP lease being acquired by the host and the DNS entries appearing, but that's reasonably acceptable as the kinds of hosts that this is aimed at take a while to spin up, and the DNS entries should be in place by the time they need them.

Description of the change

I'm not wholly sure about some bits of this change, but it's late, I'm tired, and I'm happy to fix things up on Monday.

Particular things that niggle me:

 - Naming of the method on DHCPLeaseManager (yeah, yeah, three hard
   problems, etc.)
 - The query in get_dynamic_hostname_ip_mapping().
 - There's probably a builtin somewhere that does the same as
   make_dynamic_hostname_from_ip_address(), but I couldn't find it.

To post a comment you must log in.
Revision history for this message
Graham Binns (gmb) wrote :

Oh, and: I've tested this on my NUCs as follows:

$ juju bootstrap
$ juju deploy mysql --to lxc:0

Check /etc/bind/maas/zone.<whatever your network looks like> and zone.maas.

zone.maas should contain a forward entry for dynamic-<IP of LXC>.maas; the reverse zone file should contain the PTR record.

Revision history for this message
Graham Binns (gmb) wrote :

Oh, and: I've tested this on my NUCs as follows:

$ juju bootstrap
$ juju deploy mysql --to lxc:0

Check /etc/bind/maas/zone.<whatever your network looks like> and zone.maas.

zone.maas should contain a forward entry for dynamic-<IP of LXC>.maas; the reverse zone file should contain the PTR record.

Revision history for this message
Graham Binns (gmb) wrote :

Oh, and: I've tested this on my NUCs as follows:

$ juju bootstrap
$ juju deploy mysql --to lxc:0

Check /etc/bind/maas/zone.<whatever your network looks like> and zone.maas.

zone.maas should contain a forward entry for dynamic-<IP of LXC>.maas; the reverse zone file should contain the PTR record.

Revision history for this message
Graham Binns (gmb) wrote :

Oh, and: I've tested this on my NUCs as follows:

$ juju bootstrap
$ juju deploy mysql --to lxc:0

Check /etc/bind/maas/zone.<whatever your network looks like> and zone.maas.

zone.maas should contain a forward entry for dynamic-<IP of LXC>.maas; the reverse zone file should contain the PTR record.

Revision history for this message
Christian Reis (kiko) wrote :

Incidentally, is there some guarantee that this will take "up to 60 seconds"?

Revision history for this message
Julian Edwards (julian-edwards) wrote :

On Thursday 23 Oct 2014 23:44:50 you wrote:
> Incidentally, is there some guarantee that this will take "up to 60
> seconds"?

Yes, that's how often the lease scanner runs.

Revision history for this message
Julian Edwards (julian-edwards) wrote :

Considering this was a late hour change it looks pretty cromulent to me! Minor nits inline.

review: Approve
Revision history for this message
Andres Rodriguez (andreserl) wrote :

And this needs a backport to 1.7

review: Approve
Revision history for this message
Christian Reis (kiko) wrote :

I have some minor comments below, but what I'm missing is the GENERATE piece; did we decide not to use that in the end, or is there something more magical I'm missing?

Revision history for this message
Christian Reis (kiko) wrote :

I came back to the bug and understood your point. But there may be a critical issue: the first LXC takes time, but the second and beyond, as Julian noted, can be created very fast, particularly if we are running btrfs clones. Are we kidding ourselves here by adding code which will race with LXC?

review: Needs Information
Revision history for this message
Julian Edwards (julian-edwards) wrote :

On Friday 24 Oct 2014 17:43:04 you wrote:
> > + # Avoid circular imports.
>
> Does this usually require an XXX in MAAS?

No, XXXes follow the old Launchpad protocol of referencing a bug. Avoiding
circular imports is not a bug, just a nuisance, and we prefer to leave a small
comment.

Revision history for this message
Julian Edwards (julian-edwards) wrote :

Me, on #juju-dev:

<bigjools> hey folks, what's the quickest you've seen an LXC come up, given its image is already cached?
<stokachu> bigjools: 32 seconds

Revision history for this message
Graham Binns (gmb) wrote :

On 27 October 2014 00:59, Julian Edwards <email address hidden> wrote:
>
> <bigjools> hey folks, what's the quickest you've seen an LXC come up, given its image is already cached?
> <stokachu> bigjools: 32 seconds

Okay, we definitely need to test this further. One thing, though: it's
not LXC that we care about here so much as Juju: can we give a
container DNS before the juju agent comes up and the charm starts to
need it?

I'll test this this morning.

Revision history for this message
Raphaël Badin (rvb) wrote :

> I came back to the bug and understood your point. But there may be a critical
> issue: the first LXC takes time, but the second and beyond, as Julian noted,
> can be created very fast, particularly if we are running btrfs clones. Are we
> kidding ourselves here by adding code which will race with LXC?

The leases parser can be slow when the leases file is huge and this is why we only run it every minute. Now, the actual parsing is only done when the file has changed so if we could measure the performance of the parser on huge leases files (like the one we get from OIL) maybe it would make sense to run the parser more often (like every 10s) to avoid races.

Revision history for this message
Graham Binns (gmb) wrote :

> Me, on #juju-dev:
>
> <bigjools> hey folks, what's the quickest you've seen an LXC come up, given
> its image is already cached?
> <stokachu> bigjools: 32 seconds

I've been testing this for a couple of hours on my local MAAS. AFAICT although LXC containers come up very quickly, Juju agents (and charms) on those containers don't; in my tests it took well over five minutes from a `juju deploy` or `juju add-unit` command for the Juju agent on a container to transition out of "pending", and even longer for the charm to start. That's more than enough time for DNS to be updated by MAAS.

Revision history for this message
Graham Binns (gmb) wrote :

Sorry, "well over five minutes" is a bit misleading; I've just had one agent come up in 04:50. Still plenty of time for MAAS to sort out DNS for the container though.

Revision history for this message
Gavin Panella (allenap) wrote :

If the container is rebooted, how long does that take before it comes back up?

Does Juju start up a lot quicker after a reboot? IIRC, on first installation, Juju dist-upgrades the machine, which would account for much of the slow start. If it's not doing that after a reboot it might come up much quicker, and thus outrun MAAS.

Does a RabbitMQ server, for example, deployed via charm, actually have to wait for Juju to start after a reboot? Does Upstart bring it up? The point again being that it could start before MAAS has a chance to refresh DNS.

There's a race here whichever way we look at it. Unless MAAS can be made fast enough to always win that race, we're going to have unreliable behaviour. I don't think we can make MAAS quick enough right now, and so pre-generating names is the only viable approach.

Revision history for this message
Graham Binns (gmb) wrote :

On 27 October 2014 11:25, Gavin Panella <email address hidden> wrote:
> There's a race here whichever way we look at it. Unless MAAS can be made fast enough to always win that race, we're going to have unreliable behaviour. I don't think we can make MAAS quick enough right now, and so pre-generating names is the only viable approach.

I agree, but I'd rather get this approach tested in a real-world
environment to see if it's sufficient, rather than just dumping it and
going back to the old, less efficient, and more constrained way of
doing things.

Revision history for this message
Gavin Panella (allenap) wrote :

> > I came back to the bug and understood your point. But there may be a
> > critical issue: the first LXC takes time, but the second and beyond,
> > as Julian noted, can be created very fast, particularly if we are
> > running btrfs clones. Are we kidding ourselves here by adding code
> > which will race with LXC?
>
> The leases parser can be slow when the leases file is huge and this is
> why we only run it every minute. Now, the actual parsing is only done
> when the file has changed so if we could measure the performance of
> the parser on huge leases files (like the one we get from OIL) maybe
> it would make sense to run the parser more often (like every 10s) to
> avoid races.

With a limited set of MAC addresses -- like in a network with a fixed
number of machines -- the lease parser can be quite fast because it can
consider only the last lease for a particular MAC address.

When the set of MAC addresses is large and volatile -- like when there
are a lot of containers, and they're coming and going -- the lease
parser has to process a lot more of the leases file, and can get very
slow.

At core, I think we would all agree that the lease parser is slow and
deserves to be profiled and tuned, or rewritten. However, it is well
defined and tested so we have a good reference against which to prove a
new parser.

Another option is to incrementally parse the leases file. Monitor it
(via polling or inotify) and parse only what changes. It's guaranteed to
be append-only, so it doesn't need to be particularly clever code,
though it is rotated every hour. QA on this is likely to be the bulk of
the work, to make sure it works correctly during start, stop, crash,
rotate, continuous running, upgrade, and so on.

Revision history for this message
Gavin Panella (allenap) wrote :

> > I came back to the bug and understood your point. But there may be a
> > critical issue: the first LXC takes time, but the second and beyond,
> > as Julian noted, can be created very fast, particularly if we are
> > running btrfs clones. Are we kidding ourselves here by adding code
> > which will race with LXC?
>
> The leases parser can be slow when the leases file is huge and this is
> why we only run it every minute. Now, the actual parsing is only done
> when the file has changed so if we could measure the performance of
> the parser on huge leases files (like the one we get from OIL) maybe
> it would make sense to run the parser more often (like every 10s) to
> avoid races.

With a limited set of MAC addresses -- like in a network with a fixed
number of machines -- the lease parser can be quite fast because it can
consider only the last lease for a particular MAC address.

When the set of MAC addresses is large and volatile -- like when there
are a lot of containers, and they're coming and going -- the lease
parser has to process a lot more of the leases file, and can get very
slow.

At core, I think we would all agree that the lease parser is slow and
deserves to be profiled and tuned, or rewritten. However, it is well
defined and tested so we have a good reference against which to prove a
new parser.

Another option is to incrementally parse the leases file. Monitor it
(via polling or inotify) and parse only what changes. It's guaranteed to
be append-only, so it doesn't need to be particularly clever code,
though it is rotated every hour. QA on this is likely to be the bulk of
the work, to make sure it works correctly during start, stop, crash,
rotate, continuous running, upgrade, and so on.

Revision history for this message
Christian Reis (kiko) wrote :

I don't understand why generating IPs using a GENERATE expression is more constrained. Is there a reason why you chose to not use that (and wildcard DNS IPv6) apart from the fact that it is painful to handle netmasks that aren't a multiple of 8?

Generally I also see it as a negative depending on the leases file for yet another critical piece of MAAS functionality.

Revision history for this message
Graham Binns (gmb) :
Revision history for this message
Graham Binns (gmb) wrote :

On 27 October 2014 11:56, Christian Reis <email address hidden> wrote:
> I don't understand why generating IPs using a GENERATE expression is more constrained. Is there a reason why you chose to not use that (and wildcard DNS IPv6) apart from the fact that it is painful to handle netmasks that aren't a multiple of 8?

It makes it painful to handle any network bigger than a /16, (although
as Gavin pointed out, that makes whoever's setting up the /8 or
whatever a bit crazy, and we can't code against crazy). The suggestion
was that we'd re-introduce the max-size of /16 for an IPv4 network to
accommodate this. Wildcard DNS for IPv6 is lovely, though, and I'd be
happy to use it.

> Generally I also see it as a negative depending on the leases file for yet another critical piece of MAAS functionality.

That's a fair point.

Revision history for this message
Christian Reis (kiko) wrote :

On Mon, Oct 27, 2014 at 12:03:23PM -0000, Graham Binns wrote:
> On 27 October 2014 11:56, Christian Reis <email address hidden> wrote:
> > I don't understand why generating IPs using a GENERATE expression is more constrained. Is there a reason why you chose to not use that (and wildcard DNS IPv6) apart from the fact that it is painful to handle netmasks that aren't a multiple of 8?
>
> It makes it painful to handle any network bigger than a /16, (although
> as Gavin pointed out, that makes whoever's setting up the /8 or
> whatever a bit crazy, and we can't code against crazy). The suggestion
> was that we'd re-introduce the max-size of /16 for an IPv4 network to
> accommodate this. Wildcard DNS for IPv6 is lovely, though, and I'd be
> happy to use it.

I'm +1 on restricting the dynamic pool to the max size of a /16.
--
Christian Robottom Reis | [+1] 612 888 4935 | http://launchpad.net/~kiko
Canonical VP Hyperscale | [+55 16] 9 9112 6430 | http://async.com.br/~kiko

Revision history for this message
Graham Binns (gmb) wrote :

> On Mon, Oct 27, 2014 at 12:03:23PM -0000, Graham Binns wrote:
> > On 27 October 2014 11:56, Christian Reis <email address hidden> wrote:
> > > I don't understand why generating IPs using a GENERATE expression is more
> constrained. Is there a reason why you chose to not use that (and wildcard DNS
> IPv6) apart from the fact that it is painful to handle netmasks that aren't a
> multiple of 8?
> >
> > It makes it painful to handle any network bigger than a /16, (although
> > as Gavin pointed out, that makes whoever's setting up the /8 or
> > whatever a bit crazy, and we can't code against crazy). The suggestion
> > was that we'd re-introduce the max-size of /16 for an IPv4 network to
> > accommodate this. Wildcard DNS for IPv6 is lovely, though, and I'd be
> > happy to use it.
>
> I'm +1 on restricting the dynamic pool to the max size of a /16.

Okay. I'm on it.

Revision history for this message
Andres Rodriguez (andreserl) wrote :

Ok, what about the time it takes to actually install everything needed
before actually running the charm?
On Oct 26, 2014 8:59 PM, "Julian Edwards" <email address hidden>
wrote:

> Me, on #juju-dev:
>
> <bigjools> hey folks, what's the quickest you've seen an LXC come up,
> given its image is already cached?
> <stokachu> bigjools: 32 seconds
>
> --
>
> https://code.launchpad.net/~gmb/maas/add-ptr-records-bug-1382190/+merge/239493
> You are reviewing the proposed merge of
> lp:~gmb/maas/add-ptr-records-bug-1382190 into lp:maas.
>

Revision history for this message
Graham Binns (gmb) wrote :

> If the container is rebooted, how long does that take before it comes back up?
>
> Does Juju start up a lot quicker after a reboot? IIRC, on first installation,
> Juju dist-upgrades the machine, which would account for much of the slow
> start. If it's not doing that after a reboot it might come up much quicker,
> and thus outrun MAAS.
>
> Does a RabbitMQ server, for example, deployed via charm, actually have to wait
> for Juju to start after a reboot? Does Upstart bring it up? The point again
> being that it could start before MAAS has a chance to refresh DNS.

I've tested this locally this afternoon and rebooting the container is very, very fast… Rabbit seemed to come up almost immediately (though it didn't break, because the DHCP lease was still current and the container's MAC hadn't changed).

I think that this approach, whilst neat and elegant, *does* in fact run the risk of breakages down the line, maybe if an LXC is stopped and then restarted across lease boundaries.

The fix that Kiko et. al. are suggesting is less pleasing to the eye, but it has the advantage of being far more robust in the face of failure, and also causes far less churn of the DNS zone files.

I'm coming to the conclusion that, unless there's significant evidence to the contrary, they're right this approach is not quite up to the standard we require for robustness.

Revision history for this message
Christian Reis (kiko) wrote :

Here's what I am thinking. If we are able to implement the GENERATE/wildcard approach and are happy with it by this Wednesday then we will take that into 1.7RC1 on Thursday. If we are not at the point where it fits, we could land the patch in this MP onto 1.7 and work on the proper fix for the next point release. This latter scenario means that there will be a corner case in 1.7 (that the LXC container may race with MAAS) that we'd release note accordingly.

Revision history for this message
Graham Binns (gmb) wrote :

On 27 October 2014 15:04, Christian Reis <email address hidden> wrote:
> Here's what I am thinking. If we are able to implement the GENERATE/wildcard approach and are happy with it by this Wednesday then we will take that into 1.7RC1 on Thursday. If we are not at the point where it fits, we could land the patch in this MP onto 1.7 and work on the proper fix for the next point release. This latter scenario means that there will be a corner case in 1.7 (that the LXC container may race with MAAS) that we'd release note accordingly.

I'm happy with that. Thanks for clarifying your thoughts.

I'm already working on a branch to create the $GENERATE lines; should
be done by lunchtime tomorrow with that. I'll then do the wildcard
entries.

Revision history for this message
Julian Edwards (julian-edwards) wrote :

On Monday 27 Oct 2014 12:09:17 you wrote:
> On Mon, Oct 27, 2014 at 12:03:23PM -0000, Graham Binns wrote:
> > On 27 October 2014 11:56, Christian Reis <email address hidden> wrote:
> > > I don't understand why generating IPs using a GENERATE expression is
> > > more constrained. Is there a reason why you chose to not use that (and
> > > wildcard DNS IPv6) apart from the fact that it is painful to handle
> > > netmasks that aren't a multiple of 8?>
> > It makes it painful to handle any network bigger than a /16, (although
> > as Gavin pointed out, that makes whoever's setting up the /8 or
> > whatever a bit crazy, and we can't code against crazy). The suggestion
> > was that we'd re-introduce the max-size of /16 for an IPv4 network to
> > accommodate this. Wildcard DNS for IPv6 is lovely, though, and I'd be
> > happy to use it.
>
> I'm +1 on restricting the dynamic pool to the max size of a /16.

I think we need to avoid doing this.

For a /16 network, it added a huge amount of overhead and load onto the
process that writes the DNS zone files as it's now having to write the pre-
generated entries *every time another entry changes*.

We are still yet to explore a couple more options:

1. Juju starts using the API to get a static IP and passes it to cloud-init.

2. Use DDNS on the DHCPD to update our own DNS server directly, since MAAS
doesn't need to know the IPs itself for LXCs.

I think #2 is a trivial solution here, it's a simple piece of config.

Revision history for this message
Julian Edwards (julian-edwards) wrote :

On Monday 27 Oct 2014 11:45:09 you wrote:
> Another option is to incrementally parse the leases file. Monitor it
> (via polling or inotify) and parse only what changes. It's guaranteed to
> be append-only, so it doesn't need to be particularly clever code,
> though it is rotated every hour. QA on this is likely to be the bulk of
> the work, to make sure it works correctly during start, stop, crash,
> rotate, continuous running, upgrade, and so on.

Right, I've said this for a while now :)

Make a separate daemon or thread that uses inotify to keep up with the leases
file; watch for file rewrites (which is harded coded in dhcpd as one hour);
keep track of active leases in the daemon.

However, I reckon we can get rid of the leases parser altogether if we use
DDNS as per my previous suggestion. I'll experiment with this today to see
what I can do.

Revision history for this message
Julian Edwards (julian-edwards) wrote :

I just played around with DDNS. It turns out you can't use it with zones where you are using rndc to reload the zone, the rndc command is refused :(

This seems odd. Anyway I tried adding a new zone and it all gets complex quite quickly, so I'm going to discount this as a quick solution.

Revision history for this message
Christian Reis (kiko) wrote :

> On Monday 27 Oct 2014 12:09:17 you wrote:
> > I'm +1 on restricting the dynamic pool to the max size of a /16.
>
> I think we need to avoid doing this.
>
> For a /16 network, it added a huge amount of overhead and load onto the
> process that writes the DNS zone files as it's now having to write the pre-
> generated entries *every time another entry changes*.

I don't think that's actually true if you are using GENERATE (for IPv4) and wildcard (for IPv6), is it?

> 1. Juju starts using the API to get a static IP and passes it to cloud-init.

We considered this, and it should happen, but it won't happen for 1.7.

> 2. Use DDNS on the DHCPD to update our own DNS server directly, since MAAS
> doesn't need to know the IPs itself for LXCs.
>
> I think #2 is a trivial solution here, it's a simple piece of config.

This may be a good solution, but have we ever seen that working?

Revision history for this message
Julian Edwards (julian-edwards) wrote :

On Tuesday 28 Oct 2014 12:52:05 you wrote:
> > On Monday 27 Oct 2014 12:09:17 you wrote:
> > > I'm +1 on restricting the dynamic pool to the max size of a /16.
> >
> > I think we need to avoid doing this.
> >
> > For a /16 network, it added a huge amount of overhead and load onto the
> > process that writes the DNS zone files as it's now having to write the
> > pre-
> > generated entries *every time another entry changes*.
>
> I don't think that's actually true if you are using GENERATE (for IPv4) and
> wildcard (for IPv6), is it?

It should be OK for that, but I thought gmb had eschewed it in favour of
writing zone files? Or did that flip again.

> > 1. Juju starts using the API to get a static IP and passes it to
> > cloud-init.
> We considered this, and it should happen, but it won't happen for 1.7.

Right. I am worried as to how long we'll have to support this legacy GENERATE
stuff.

>
> > 2. Use DDNS on the DHCPD to update our own DNS server directly, since MAAS
> > doesn't need to know the IPs itself for LXCs.
> >
> > I think #2 is a trivial solution here, it's a simple piece of config.
>
> This may be a good solution, but have we ever seen that working?

As per my other emails, it's a PITA to get working.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/dns/tests/test_zonegenerator.py'
2--- src/maasserver/dns/tests/test_zonegenerator.py 2014-09-15 14:28:28 +0000
3+++ src/maasserver/dns/tests/test_zonegenerator.py 2014-10-27 11:52:58 +0000
4@@ -36,6 +36,7 @@
5 from maasserver.models import Config
6 from maasserver.testing.factory import factory
7 from maasserver.testing.testcase import MAASServerTestCase
8+from maasserver.utils import make_dynamic_hostname_from_ip_address
9 from maastesting.fakemethod import FakeMethod
10 from maastesting.matchers import (
11 MockAnyCall,
12@@ -199,10 +200,17 @@
13 node2 = factory.make_node_with_mac_attached_to_nodegroupinterface(
14 nodegroup=nodegroup)
15 staticip = factory.make_StaticIPAddress(mac=node2.get_primary_mac())
16+ # Create dynamic mapping for a host that's not registered as a Node
17+ # in MAAS (e.g. an LXC container).
18+ non_node_lease = factory.make_DHCPLease(
19+ nodegroup=nodegroup, mac=factory.make_mac_address())
20+ non_node_hostname = make_dynamic_hostname_from_ip_address(
21+ non_node_lease.ip)
22
23 expected_mapping = {
24 node1.hostname: [lease.ip],
25 node2.hostname: [staticip.ip],
26+ non_node_hostname: [non_node_lease.ip],
27 }
28 self.assertEqual(
29 expected_mapping, get_hostname_ip_mapping(nodegroup))
30
31=== modified file 'src/maasserver/dns/zonegenerator.py'
32--- src/maasserver/dns/zonegenerator.py 2014-08-28 12:00:15 +0000
33+++ src/maasserver/dns/zonegenerator.py 2014-10-27 11:52:58 +0000
34@@ -84,9 +84,12 @@
35 from maasserver.models.staticipaddress import StaticIPAddress
36 from maasserver.models.dhcplease import DHCPLease
37 mapping = DHCPLease.objects.get_hostname_ip_mapping(nodegroup)
38- static_mapping = StaticIPAddress.objects.get_hostname_ip_mapping(nodegroup)
39+ dynamic_mapping = DHCPLease.objects.get_dynamic_hostname_ip_mapping(
40+ nodegroup)
41+ mapping.update(dynamic_mapping)
42 # The static mapping entries take precedence over the dynamic mapping
43 # entries.
44+ static_mapping = StaticIPAddress.objects.get_hostname_ip_mapping(nodegroup)
45 mapping.update(static_mapping)
46 return mapping
47
48
49=== modified file 'src/maasserver/models/dhcplease.py'
50--- src/maasserver/models/dhcplease.py 2014-09-01 13:32:35 +0000
51+++ src/maasserver/models/dhcplease.py 2014-10-27 11:52:58 +0000
52@@ -33,7 +33,10 @@
53 )
54 from maasserver.models.cleansave import CleanSave
55 from maasserver.models.macaddress import MACAddress
56-from maasserver.utils import strip_domain
57+from maasserver.utils import (
58+ make_dynamic_hostname_from_ip_address,
59+ strip_domain,
60+ )
61
62
63 class DHCPLeaseManager(Manager):
64@@ -106,8 +109,13 @@
65 deleted.
66 :return: Iterable of IP addresses that were newly leased.
67 """
68+ # Avoid circular imports.
69+ from maasserver.dns.config import change_dns_zones
70+
71 self._delete_obsolete_leases(nodegroup, leases)
72 new_leases = self._add_missing_leases(nodegroup, leases)
73+ if len(new_leases) > 0:
74+ change_dns_zones(nodegroup)
75 return new_leases
76
77 def get_hostname_ip_mapping(self, nodegroup):
78@@ -146,6 +154,26 @@
79 for hostname, ip in cursor.fetchall()
80 }
81
82+ def get_dynamic_hostname_ip_mapping(self, nodegroup):
83+ """Return a mapping {hostnames -> ips} for the currently leased
84+ IP addresses for MAC addresses not attached to a Node in `nodegroup`.
85+
86+ This allows us to generate DNS records on-the-fly for hosts
87+ which take a dynamic address but aren't treated as Nodes in MAAS
88+ (e.g. LXC containers)..
89+
90+ Any domain will be stripped from the hostnames.
91+ """
92+ mac_addresses_with_nodes = [
93+ mac.mac_address for mac in MACAddress.objects.all()]
94+ leases_on_cluster = DHCPLease.objects.filter(nodegroup=nodegroup)
95+ dynamic_leases = leases_on_cluster.exclude(
96+ mac__in=mac_addresses_with_nodes)
97+ return {
98+ make_dynamic_hostname_from_ip_address(lease.ip): [lease.ip]
99+ for lease in dynamic_leases
100+ }
101+
102
103 class DHCPLease(CleanSave, Model):
104 """A known mapping of an IP address to a MAC address.
105
106=== modified file 'src/maasserver/models/tests/test_dhcplease.py'
107--- src/maasserver/models/tests/test_dhcplease.py 2014-09-18 12:44:38 +0000
108+++ src/maasserver/models/tests/test_dhcplease.py 2014-10-27 11:52:58 +0000
109@@ -24,7 +24,16 @@
110 from maasserver.models import DHCPLease
111 from maasserver.testing.factory import factory
112 from maasserver.testing.testcase import MAASServerTestCase
113-from maasserver.utils import ignore_unused
114+from maasserver.utils import (
115+ ignore_unused,
116+ make_dynamic_hostname_from_ip_address,
117+ )
118+from maastesting.matchers import MockCalledOnceWith
119+from testtools.matchers import (
120+ Contains,
121+ Equals,
122+ Not,
123+ )
124
125
126 def get_leases(nodegroup):
127@@ -166,10 +175,20 @@
128 map_leases(nodegroup))
129
130 def test_update_leases_does_not_update_dns_zone_if_nothing_added(self):
131- self.patch(dns, 'change_dns_zones')
132+ self.patch(dns.config, 'change_dns_zones')
133 nodegroup = factory.make_NodeGroup()
134 DHCPLease.objects.update_leases(nodegroup, {})
135- self.assertFalse(dns.change_dns_zones.called)
136+ self.assertFalse(dns.config.change_dns_zones.called)
137+
138+ def test_update_leases_updates_dns_zone_if_leases_added(self):
139+ change_dns_zones = self.patch(dns.config, 'change_dns_zones')
140+ nodegroup = factory.make_NodeGroup()
141+ ip_address = factory.make_ipv4_address()
142+ mac_address = factory.make_mac_address()
143+ DHCPLease.objects.update_leases(
144+ nodegroup, {ip_address: mac_address})
145+ self.assertThat(
146+ change_dns_zones, MockCalledOnceWith(nodegroup))
147
148 def test_get_hostname_ip_mapping_returns_mapping(self):
149 nodegroup = factory.make_NodeGroup()
150@@ -255,3 +274,45 @@
151 mapping = DHCPLease.objects.get_hostname_ip_mapping(
152 another_nodegroup)
153 self.assertEqual({}, mapping)
154+
155+
156+class TestDHCPLeaseManager_GetDynamicHostnameMapping(MAASServerTestCase):
157+
158+ def test_get_dynamic_hostname_ip_mapping_returns_mapping(self):
159+ nodegroup = factory.make_NodeGroup()
160+ expected_mapping = {}
161+ for _ in range(3):
162+ mac = factory.make_MAC()
163+ lease = factory.make_DHCPLease(
164+ nodegroup=nodegroup, mac=unicode(mac))
165+ hostname = make_dynamic_hostname_from_ip_address(lease.ip)
166+ expected_mapping[hostname] = [lease.ip]
167+ mapping = DHCPLease.objects.get_dynamic_hostname_ip_mapping(nodegroup)
168+ self.assertEqual(expected_mapping, mapping)
169+
170+ def test_get_dynamic_hostname_ip_mapping_ignores_known_nodes(self):
171+ nodegroup = factory.make_NodeGroup()
172+ expected_mapping = {}
173+
174+ node = factory.make_Node(nodegroup=nodegroup)
175+ node_mac = factory.make_MACAddress(node=node)
176+ factory.make_DHCPLease(
177+ nodegroup=nodegroup, mac=node_mac.mac_address)
178+
179+ mac = factory.make_MAC()
180+ lease = factory.make_DHCPLease(
181+ nodegroup=nodegroup, mac=unicode(mac))
182+ hostname = make_dynamic_hostname_from_ip_address(lease.ip)
183+ expected_mapping[hostname] = [lease.ip]
184+
185+ mapping = DHCPLease.objects.get_dynamic_hostname_ip_mapping(nodegroup)
186+ self.expectThat(mapping, Equals(expected_mapping))
187+
188+ def test_get_dynamic_hostname_ip_mapping_considers_given_nodegroup(self):
189+ nodegroup = factory.make_NodeGroup()
190+ mac = factory.make_MAC()
191+ factory.make_DHCPLease(nodegroup=nodegroup, mac=unicode(mac))
192+ another_nodegroup = factory.make_NodeGroup()
193+ mapping = DHCPLease.objects.get_dynamic_hostname_ip_mapping(
194+ another_nodegroup)
195+ self.assertEqual({}, mapping)
196
197=== modified file 'src/maasserver/utils/__init__.py'
198--- src/maasserver/utils/__init__.py 2014-10-04 08:36:12 +0000
199+++ src/maasserver/utils/__init__.py 2014-10-27 11:52:58 +0000
200@@ -19,6 +19,7 @@
201 'get_db_state',
202 'get_local_cluster_UUID',
203 'ignore_unused',
204+ 'make_dynamic_hostname_from_ip_address',
205 'make_validation_error_message',
206 'strip_domain',
207 'synchronised',
208@@ -35,6 +36,10 @@
209 from maasserver.enum import NODEGROUPINTERFACE_MANAGEMENT
210 from maasserver.exceptions import NodeGroupMisconfiguration
211 from maasserver.utils.orm import get_one
212+from netaddr import (
213+ AddrConversionError,
214+ IPAddress,
215+ )
216 from provisioningserver.utils.text import make_bullet_list
217
218
219@@ -214,3 +219,17 @@
220 The message takes the form of a textual bullet-list.
221 """
222 return make_bullet_list(gen_validation_error_messages(error))
223+
224+
225+def make_dynamic_hostname_from_ip_address(ip):
226+ ip_address = IPAddress(ip)
227+ try:
228+ address = ip_address.ipv4().format()
229+ except AddrConversionError:
230+ address = ip_address.ipv6().format()
231+ hostname = address.lstrip("::")
232+ hostname = hostname.replace("::", "-")
233+ hostname = hostname.replace(":", "-")
234+ return "dynamic-%s" % hostname
235+ else:
236+ return "dynamic-%s" % address.replace('.', '-')
237
238=== modified file 'src/maasserver/utils/tests/test_utils.py'
239--- src/maasserver/utils/tests/test_utils.py 2014-10-04 08:36:12 +0000
240+++ src/maasserver/utils/tests/test_utils.py 2014-10-27 11:52:58 +0000
241@@ -36,6 +36,7 @@
242 find_nodegroup,
243 get_db_state,
244 get_local_cluster_UUID,
245+ make_dynamic_hostname_from_ip_address,
246 make_validation_error_message,
247 strip_domain,
248 synchronised,
249@@ -348,3 +349,29 @@
250 "* alice: bob\n"
251 "* foo: bar",
252 make_validation_error_message(error))
253+
254+
255+class TestMakeDynamicHostnameFromIPAddress(MAASTestCase):
256+
257+ def test_makes_hostname_from_ipv4_address(self):
258+ ip_address = factory.make_ipv4_address()
259+ ip_parts = ip_address.split('.')
260+ expected_hostname = "dynamic-%s-%s-%s-%s" % tuple(ip_parts)
261+ self.assertEqual(
262+ expected_hostname,
263+ make_dynamic_hostname_from_ip_address(ip_address))
264+
265+ def test_makes_hostname_from_ipv6_address(self):
266+ ip_address = factory.make_ipv6_address()
267+ ip_parts = ip_address.split(':')
268+ expected_hostname = "dynamic-%s" % "-".join(ip_parts)
269+ self.assertEqual(
270+ expected_hostname,
271+ make_dynamic_hostname_from_ip_address(ip_address))
272+
273+ def test_makes_hostname_from_truncated_ipv6_address(self):
274+ ip_address = '::ffff:ffff:ffff:ffff:7'
275+ expected_hostname = "dynamic-ffff-ffff-ffff-ffff-7"
276+ self.assertEqual(
277+ expected_hostname,
278+ make_dynamic_hostname_from_ip_address(ip_address))