Merge ~mpontillo/maas:default-domain-selection into maas:master

Proposed by Mike Pontillo
Status: Merged
Approved by: Mike Pontillo
Approved revision: cd947d8785cc97c533b7bb2be2f43c679bbd258b
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~mpontillo/maas:default-domain-selection
Merge into: maas:master
Diff against target: 215 lines (+140/-3)
7 files modified
src/maasserver/migrations/builtin/maasserver/0023_add_ttl_field.py (+2/-2)
src/maasserver/migrations/builtin/maasserver/0097_node_chassis_storage_hints.py (+1/-1)
src/maasserver/migrations/builtin/maasserver/0155_add_globaldefaults_model.py (+45/-0)
src/maasserver/models/__init__.py (+2/-0)
src/maasserver/models/domain.py (+5/-0)
src/maasserver/models/globaldefault.py (+48/-0)
src/maasserver/models/tests/test_globaldefault.py (+37/-0)
Reviewer Review Type Date Requested Status
Alberto Donato (community) Approve
Blake Rouse (community) Approve
MAAS Lander Approve
Review via email: mp+342768@code.launchpad.net

Commit message

Add GlobalDefault model.

 * Use GlobalDefault model for get_default_domain().

Description of the change

The first step in allowing the default domain to be changed is creating somewhere in MAAS that tracks "global default" objects.

This branch creates a model for that and switches the Domain object over to its usage.

We could also use this concept for (at least) fabrics and availability zones.

To post a comment you must log in.
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b default-domain-selection lp:~mpontillo/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci-jenkins.internal:8080/job/maas/job/branch-tester/2327/console
COMMIT: a1a270141ee6c56a3c4c3ba6e454b0c89b609bde

review: Needs Fixing
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b default-domain-selection lp:~mpontillo/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci-jenkins.internal:8080/job/maas/job/branch-tester/2328/console
COMMIT: 7ddca341507677b43c9fadb478a396e2dacacfb2

review: Needs Fixing
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b default-domain-selection lp:~mpontillo/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: cd947d8785cc97c533b7bb2be2f43c679bbd258b

review: Approve
Revision history for this message
Alberto Donato (ack) wrote :

Why do we need an extra model (which ends up having a single instance) just to track which is the default domain?
Can't we add a "default" flag to the domain instead (and have a contraint that only allow to have exactly one entry with default=True)?
We might have a similar case with default user groups and resource pools, if we'll want to changing the default. Having to add an extra model seems cumbersome

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

I agree!

On Fri, Apr 6, 2018 at 6:04 AM Alberto Donato <email address hidden>
wrote:

> Review: Needs Information
>
> Why do we need an extra model (which ends up having a single instance)
> just to track which is the default domain?
> Can't we add a "default" flag to the domain instead (and have a contraint
> that only allow to have exactly one entry with default=True)?
> We might have a similar case with default user groups and resource pools,
> if we'll want to changing the default. Having to add an extra model seems
> cumbersome
> --
> https://code.launchpad.net/~mpontillo/maas/+git/maas/+merge/342768
> Your team MAAS Committers is subscribed to branch maas:master.
>
> Launchpad-Message-Rationale: Subscriber @maas-committers
> Launchpad-Message-For: maas-committers
> Launchpad-Notification-Type: code-review
> Launchpad-Branch: ~maas-committers/maas/+git/maas:master
> Launchpad-Project: maas
>
--
Andres Rodriguez (RoAkSoAx)
Ubuntu Server Developer
MSc. Telecom & Networking
Systems Engineer

Revision history for this message
Mike Pontillo (mpontillo) wrote :

We discussed this at standup (well, mainly Blake and myself) and agreed on this option for a few reasons:

(1) We didn't like how changing one row in the domain table would require changing multiple rows (to set the default) to ensure referential integrity. We don't have a good existing pattern to do this with maas-obj-form or similar, so there's little benefit in adding an extraneous (and somewhat strangely modeled) row to the domain table.

(2) We wanted "can't delete the default record" to be able to be enforced by database constraints. The foreign key approach is a natural way to do this, and a clean way to model it.

(3) In the future we think it would be useful if different "groups" in MAAS (such as tenants - if we ever do multitenancy, or multitenancy "light", LDAP groups, etc) can specify their own default domain (or other default objects). Modeling global defaults in their own table allows us to, in the future, specify different defaults for different situations.

Revision history for this message
Blake Rouse (blake-rouse) wrote :

Looks good.

Sucks to have a table with just 1 row, but it does add a proper database constraint and makes setting the default a single action.

review: Approve
Revision history for this message
Alberto Donato (ack) wrote :

fair enough

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :
Revision history for this message
Mike Pontillo (mpontillo) wrote :

Dang randomly-failing full page reload tests again. Retrying.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/maasserver/migrations/builtin/maasserver/0023_add_ttl_field.py b/src/maasserver/migrations/builtin/maasserver/0023_add_ttl_field.py
2index 9442f8d..fa542b3 100644
3--- a/src/maasserver/migrations/builtin/maasserver/0023_add_ttl_field.py
4+++ b/src/maasserver/migrations/builtin/maasserver/0023_add_ttl_field.py
5@@ -44,11 +44,11 @@ class Migration(migrations.Migration):
6 migrations.AlterField(
7 model_name='dnsresource',
8 name='domain',
9- field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, default=maasserver.models.dnsresource.get_default_domain, to='maasserver.Domain'),
10+ field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='maasserver.Domain'),
11 ),
12 migrations.AlterField(
13 model_name='node',
14 name='domain',
15- field=models.ForeignKey(default=maasserver.models.node.get_default_domain, null=False, to='maasserver.Domain', on_delete=django.db.models.deletion.PROTECT),
16+ field=models.ForeignKey(null=False, to='maasserver.Domain', on_delete=django.db.models.deletion.PROTECT),
17 ),
18 ]
19diff --git a/src/maasserver/migrations/builtin/maasserver/0097_node_chassis_storage_hints.py b/src/maasserver/migrations/builtin/maasserver/0097_node_chassis_storage_hints.py
20index a2cfb27..08abeab 100644
21--- a/src/maasserver/migrations/builtin/maasserver/0097_node_chassis_storage_hints.py
22+++ b/src/maasserver/migrations/builtin/maasserver/0097_node_chassis_storage_hints.py
23@@ -58,7 +58,7 @@ class Migration(migrations.Migration):
24 migrations.AlterField(
25 model_name='node',
26 name='domain',
27- field=models.ForeignKey(to='maasserver.Domain', null=True, default=maasserver.models.node.get_default_domain, blank=True, on_delete=django.db.models.deletion.PROTECT),
28+ field=models.ForeignKey(to='maasserver.Domain', null=True, blank=True, on_delete=django.db.models.deletion.PROTECT),
29 ),
30 migrations.AlterField(
31 model_name='node',
32diff --git a/src/maasserver/migrations/builtin/maasserver/0155_add_globaldefaults_model.py b/src/maasserver/migrations/builtin/maasserver/0155_add_globaldefaults_model.py
33new file mode 100644
34index 0000000..85a2ca8
35--- /dev/null
36+++ b/src/maasserver/migrations/builtin/maasserver/0155_add_globaldefaults_model.py
37@@ -0,0 +1,45 @@
38+# -*- coding: utf-8 -*-
39+# Generated by Django 1.11.11 on 2018-04-06 00:26
40+from __future__ import unicode_literals
41+
42+from django.db import (
43+ migrations,
44+ models,
45+)
46+import django.db.models.deletion
47+import maasserver.models.cleansave
48+import maasserver.models.dnsresource
49+import maasserver.models.node
50+
51+
52+class Migration(migrations.Migration):
53+
54+ dependencies = [
55+ ('maasserver', '0154_link_usergroup_role'),
56+ ]
57+
58+ operations = [
59+ migrations.CreateModel(
60+ name='GlobalDefault',
61+ fields=[
62+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
63+ ('created', models.DateTimeField(editable=False)),
64+ ('updated', models.DateTimeField(editable=False)),
65+ ('domain', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='maasserver.Domain')),
66+ ],
67+ options={
68+ 'abstract': False,
69+ },
70+ bases=(maasserver.models.cleansave.CleanSave, models.Model, object),
71+ ),
72+ migrations.AlterField(
73+ model_name='dnsresource',
74+ name='domain',
75+ field=models.ForeignKey(default=maasserver.models.dnsresource.get_default_domain, on_delete=django.db.models.deletion.PROTECT, to='maasserver.Domain'),
76+ ),
77+ migrations.AlterField(
78+ model_name='node',
79+ name='domain',
80+ field=models.ForeignKey(blank=True, default=maasserver.models.node.get_default_domain, null=True, on_delete=django.db.models.deletion.PROTECT, to='maasserver.Domain'),
81+ ),
82+ ]
83diff --git a/src/maasserver/models/__init__.py b/src/maasserver/models/__init__.py
84index 339a08c..75d2f34 100644
85--- a/src/maasserver/models/__init__.py
86+++ b/src/maasserver/models/__init__.py
87@@ -34,6 +34,7 @@ __all__ = [
88 'FileStorage',
89 'Filesystem',
90 'FilesystemGroup',
91+ 'GlobalDefault',
92 'Interface',
93 'IPRange',
94 'ISCSIBlockDevice',
95@@ -134,6 +135,7 @@ from maasserver.models.filesystemgroup import (
96 RAID,
97 VolumeGroup,
98 )
99+from maasserver.models.globaldefault import GlobalDefault
100 from maasserver.models.interface import (
101 BondInterface,
102 BridgeInterface,
103diff --git a/src/maasserver/models/domain.py b/src/maasserver/models/domain.py
104index 480d8b8..653c033 100644
105--- a/src/maasserver/models/domain.py
106+++ b/src/maasserver/models/domain.py
107@@ -95,6 +95,11 @@ class DomainManager(Manager, DomainQueriesMixin):
108 return queryset
109
110 def get_default_domain(self):
111+ # Circular imports.
112+ from maasserver.models.globaldefault import GlobalDefault
113+ return GlobalDefault.objects.instance().domain
114+
115+ def get_or_create_default_domain(self):
116 """Return the default domain."""
117 now = datetime.datetime.now()
118 domain, _ = self.get_or_create(
119diff --git a/src/maasserver/models/globaldefault.py b/src/maasserver/models/globaldefault.py
120new file mode 100644
121index 0000000..7b37052
122--- /dev/null
123+++ b/src/maasserver/models/globaldefault.py
124@@ -0,0 +1,48 @@
125+# Copyright 2018 Canonical Ltd. This software is licensed under the
126+# GNU Affero General Public License version 3 (see the file LICENSE).
127+
128+"""Global default objects."""
129+
130+__all__ = [
131+ "GlobalDefault",
132+ ]
133+
134+from datetime import datetime
135+
136+from django.db.models import (
137+ ForeignKey,
138+ Manager,
139+ PROTECT,
140+)
141+from maasserver.models.cleansave import CleanSave
142+from maasserver.models.domain import Domain
143+from maasserver.models.timestampedmodel import TimestampedModel
144+from provisioningserver.logger import get_maas_logger
145+
146+
147+maaslog = get_maas_logger("default")
148+
149+
150+class GlobalDefaultManager(Manager):
151+
152+ def instance(self):
153+ now = datetime.now()
154+ instance, _ = self.get_or_create(
155+ id=0,
156+ defaults=dict(
157+ id=0,
158+ domain=Domain.objects.get_or_create_default_domain(),
159+ created=now,
160+ updated=now
161+ )
162+ )
163+ return instance
164+
165+
166+class GlobalDefault(CleanSave, TimestampedModel):
167+ """Represents global default objects in MAAS."""
168+
169+ objects = GlobalDefaultManager()
170+
171+ domain = ForeignKey(
172+ Domain, null=False, blank=False, editable=True, on_delete=PROTECT)
173diff --git a/src/maasserver/models/tests/test_globaldefault.py b/src/maasserver/models/tests/test_globaldefault.py
174new file mode 100644
175index 0000000..2545d82
176--- /dev/null
177+++ b/src/maasserver/models/tests/test_globaldefault.py
178@@ -0,0 +1,37 @@
179+# Copyright 2018 Canonical Ltd. This software is licensed under the
180+# GNU Affero General Public License version 3 (see the file LICENSE).
181+
182+"""Test GlobalDefault objects."""
183+
184+from maasserver.models import (
185+ Domain,
186+ GlobalDefault,
187+)
188+from maasserver.testing.factory import factory
189+from maasserver.testing.testcase import MAASServerTestCase
190+from testtools.matchers import Equals
191+
192+
193+class TestGlobalDefault(MAASServerTestCase):
194+ """Tests for :class:`GlobalDefault`."""
195+
196+ def test_get_instance_creates_instance_id_0_if_none_exits(self):
197+ instance = GlobalDefault.objects.instance()
198+ self.assertThat(instance.id, Equals(0))
199+
200+ def test_get_instance_returns_existing_id_0(self):
201+ GlobalDefault.objects.instance()
202+ instance = GlobalDefault.objects.instance()
203+ self.assertThat(instance.id, Equals(0))
204+
205+ def test_returns_default_domain(self):
206+ instance = GlobalDefault.objects.instance()
207+ self.assertThat(instance.domain, Equals(
208+ Domain.objects.get_default_domain()))
209+
210+ def test_default_domain_changes_take_effect(self):
211+ instance = GlobalDefault.objects.instance()
212+ instance.domain = factory.make_Domain()
213+ instance.save()
214+ self.assertThat(instance.domain, Equals(
215+ Domain.objects.get_default_domain()))

Subscribers

People subscribed via source and target branches