Merge lp:~blake-rouse/maas/add-boot-type-to-node into lp:~maas-committers/maas/trunk
- add-boot-type-to-node
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Blake Rouse |
Approved revision: | no longer in the source branch. |
Merged at revision: | 2574 |
Proposed branch: | lp:~blake-rouse/maas/add-boot-type-to-node |
Merge into: | lp:~maas-committers/maas/trunk |
Diff against target: |
760 lines (+399/-177) 6 files modified
src/maasserver/enum.py (+14/-0) src/maasserver/migrations/0091_add_boot_type_to_node.py (+338/-0) src/maasserver/models/node.py (+15/-42) src/maasserver/models/tests/test_node.py (+14/-66) src/maasserver/node_action.py (+7/-29) src/maasserver/tests/test_node_action.py (+11/-40) |
To merge this branch: | bzr merge lp:~blake-rouse/maas/add-boot-type-to-node |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Raphaël Badin (community) | Approve | ||
Review via email: mp+227234@code.launchpad.net |
Commit message
Converts the use-fastpath-
Description of the change
Adds a boot_type field to the node model. This field is used instead of the "use-fastpath-
The migration handles reading the "use-fastpath-
Raphaël Badin (rvb) wrote : | # |
btw, we probably want to make sure boot_type is exposed on the API (read/write).
Blake Rouse (blake-rouse) wrote : | # |
should_
I will do a branch to make sure that boot_type is exposed over the API.
Thanks!
Jeroen T. Vermeulen (jtv) wrote : | # |
We just hit trouble with this migration: http://
I suspect the error may be because the migration updates a field on the model which, in the schema revision the migration works on, hasn't actually been added yet.
If it weren't for that, I think the migration would still fail for an additional reason: as it turns out you can't add a column to a database table and then start populating that column in the same transaction. You either do it right away by setting a default, or you do it in a separate transaction. (A migration runs as a transaction, of course.)
On a final nitpick, do we know that updating every node individually and querying the tags table for each will perform sufficiently well on very large numbers of nodes?
Jeroen T. Vermeulen (jtv) wrote : | # |
Filed that problem as bug 1343425.
Blake Rouse (blake-rouse) wrote : | # |
Thanks. Shows you are working on it. Is that correct?
On Jul 23, 2014 5:28 PM, "Jeroen T. Vermeulen" <email address hidden> wrote:
> Filed that problem as bug 1343425.
> --
>
> https:/
> You are the owner of lp:~blake-rouse/maas/add-boot-type-to-node.
>
Jeroen T. Vermeulen (jtv) wrote : | # |
Yes, I'm working on that bug.
By the way, some important things to be aware of when working on database migrations: the normal things you do in development, such as "make sampledata," perform the migrations on an empty database. So any code you have in there for existing data (“for all existing nodes...”) simply won't be exercised. Yet an existing installation will run through them on upgrade.
Django/South let you step manually through the consecutive schema versions — forwards and, where the migrations themselves support it, backwards. So there is a way to verify that a migration works on existing data: ‘make sampledata’ to populate some of the tables. Then start a development instance of your branch in one shell, using ‘make run’. Ensure that you have data that will exercise the migration properly, whether through the UI or a postgres shell by running ‘make dbharness’. For convenience I'll show how to migrate backwards first and then forwards, so you'll start with data to exercise the backwards migration. Once you're sure your data is right, step through your migration backwards using maas-region-admin's ‘migrate’ sub-command, such as:
./bin/
That command will try to take you back to schema version 0090, which precedes your migration 0091. (As it happens this crashes because the backwards step in migration 0091 gets the return type from get_or_create wrong, and passes a tuple instead of a model object.)
Now you're in a state similar to what an installed maas would have before installing your upgrade. Again verify that the data is as you would expect, pre-migration, and that migrating forwards will exercise your migration code well. Then, apply your migration with a command like:
./bin/
Now you're in your post-migration state. Verify that the migration did what you expected it to. (In this case it would crash for the reasons I gave in my earlier comment.)
Another possible problem just occurred to me: the migration code uses node.tags — which is regular model-based ORM. However while the schema migrations walk you through consecutive database schema versions, they don't change the model code. The model code will always be the current version as received with the migrations, which will usually be for a newer schema version than the version your migration was written for. Future model code won't necessarily work with that schema version. That's why we're supposed to use the ‘orm’ object for accessing the database in migrations, not the model code.
So if (arbitrary example) we were to rename node.tags to node.tags_set at the model level, I _think_ your migration would break again.
Jeroen T. Vermeulen (jtv) wrote : | # |
Did some looking around the South documentation and the Internet at large, and it looks like node.tags is still the proper way to refer to a node's tags.
(In my version of the migration, I go the other way — tag.node_set — in order to eliminate the loop that queries tags for every node.)
Preview Diff
1 | === modified file 'src/maasserver/enum.py' |
2 | --- src/maasserver/enum.py 2014-07-09 16:28:38 +0000 |
3 | +++ src/maasserver/enum.py 2014-07-18 14:52:34 +0000 |
4 | @@ -95,6 +95,20 @@ |
5 | ADMIN = 'admin_node' |
6 | |
7 | |
8 | +class NODE_BOOT: |
9 | + """Types of booting methods a node can use.""" |
10 | + FASTPATH = 'fastpath' #: http://launchpad.net/curtin |
11 | + DEBIAN = 'di' |
12 | + |
13 | + |
14 | +# Django choices for NODE_BOOT: sequence of tuples (key, UI |
15 | +# representation). |
16 | +NODE_BOOT_CHOICES = ( |
17 | + (NODE_BOOT.FASTPATH, "Fastpath Installer"), |
18 | + (NODE_BOOT.DEBIAN, "Debian Installer"), |
19 | +) |
20 | + |
21 | + |
22 | class PRESEED_TYPE: |
23 | """Types of preseed documents that can be generated.""" |
24 | DEFAULT = '' |
25 | |
26 | === added file 'src/maasserver/migrations/0091_add_boot_type_to_node.py' |
27 | --- src/maasserver/migrations/0091_add_boot_type_to_node.py 1970-01-01 00:00:00 +0000 |
28 | +++ src/maasserver/migrations/0091_add_boot_type_to_node.py 2014-07-18 14:52:34 +0000 |
29 | @@ -0,0 +1,338 @@ |
30 | +from maasserver.enum import NODE_BOOT |
31 | +from maasserver.models.timestampedmodel import now |
32 | +from south.db import db |
33 | +from south.v2 import SchemaMigration |
34 | + |
35 | + |
36 | +class Migration(SchemaMigration): |
37 | + |
38 | + def forwards(self, orm): |
39 | + # Adding field 'Node.boot_type' |
40 | + db.add_column(u'maasserver_node', 'boot_type', |
41 | + self.gf('django.db.models.fields.CharField')(default=u'fastpath', max_length=20), |
42 | + keep_default=False) |
43 | + |
44 | + # Set the boot_type based on the tag set on Node. |
45 | + for node in orm['maasserver.Node'].objects.all(): |
46 | + if node.tags.filter(name="use-fastpath-installer").exists(): |
47 | + node.boot_type = NODE_BOOT.FASTPATH |
48 | + else: |
49 | + node.boot_type = NODE_BOOT.DEBIAN |
50 | + node.save() |
51 | + |
52 | + # Delete the "use-fastpath-installer" tag, as it is no longer used |
53 | + orm['maasserver.Tag'].objects.filter( |
54 | + name="use-fastpath-installer").delete() |
55 | + |
56 | + |
57 | + def backwards(self, orm): |
58 | + # Create the "use-fastpath-installer" tag. |
59 | + current_time = now() |
60 | + fpi_tag = orm['maasserver.Tag'].objects.get_or_create( |
61 | + name="use-fastpath-installer", defaults={ |
62 | + 'created': current_time, |
63 | + 'updated': current_time, |
64 | + }) |
65 | + |
66 | + # Add the "use-fastpath-installer" tag, to nodes that use that |
67 | + # boot_type. |
68 | + for node in orm['maasserver.Node'].objects.all(): |
69 | + if node.boot_type == NODE_BOOT.FASTPATH: |
70 | + node.tags.add(fpi_tag) |
71 | + elif node.boot_type == NODE_BOOT.DEBIAN: |
72 | + if fpi_tag in node.tags.all(): |
73 | + node.tags.remove(fpi_tag) |
74 | + node.save() |
75 | + |
76 | + # Deleting field 'Node.boot_type' |
77 | + db.delete_column(u'maasserver_node', 'boot_type') |
78 | + |
79 | + |
80 | + models = { |
81 | + u'auth.group': { |
82 | + 'Meta': {'object_name': 'Group'}, |
83 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
84 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), |
85 | + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) |
86 | + }, |
87 | + u'auth.permission': { |
88 | + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, |
89 | + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
90 | + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), |
91 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
92 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) |
93 | + }, |
94 | + u'auth.user': { |
95 | + 'Meta': {'object_name': 'User'}, |
96 | + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
97 | + 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}), |
98 | + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
99 | + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), |
100 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
101 | + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
102 | + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
103 | + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
104 | + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
105 | + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
106 | + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), |
107 | + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), |
108 | + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) |
109 | + }, |
110 | + u'contenttypes.contenttype': { |
111 | + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, |
112 | + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
113 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
114 | + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
115 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) |
116 | + }, |
117 | + u'maasserver.bootimage': { |
118 | + 'Meta': {'unique_together': "((u'nodegroup', u'osystem', u'architecture', u'subarchitecture', u'release', u'purpose', u'label'),)", 'object_name': 'BootImage'}, |
119 | + 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
120 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
121 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
122 | + 'label': ('django.db.models.fields.CharField', [], {'default': "u'release'", 'max_length': '255'}), |
123 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}), |
124 | + 'osystem': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
125 | + 'purpose': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
126 | + 'release': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
127 | + 'subarchitecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
128 | + 'supported_subarches': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
129 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}), |
130 | + 'xinstall_path': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'null': 'True', 'blank': 'True'}), |
131 | + 'xinstall_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '30', 'null': 'True', 'blank': 'True'}) |
132 | + }, |
133 | + u'maasserver.bootsource': { |
134 | + 'Meta': {'object_name': 'BootSource'}, |
135 | + 'cluster': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}), |
136 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
137 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
138 | + 'keyring_data': ('maasserver.fields.EditableBinaryField', [], {'blank': 'True'}), |
139 | + 'keyring_filename': ('django.db.models.fields.FilePathField', [], {'max_length': '100', 'blank': 'True'}), |
140 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}), |
141 | + 'url': ('django.db.models.fields.URLField', [], {'max_length': '200'}) |
142 | + }, |
143 | + u'maasserver.bootsourceselection': { |
144 | + 'Meta': {'object_name': 'BootSourceSelection'}, |
145 | + 'arches': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}), |
146 | + 'boot_source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.BootSource']"}), |
147 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
148 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
149 | + 'labels': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}), |
150 | + 'release': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}), |
151 | + 'subarches': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}), |
152 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
153 | + }, |
154 | + u'maasserver.componenterror': { |
155 | + 'Meta': {'object_name': 'ComponentError'}, |
156 | + 'component': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}), |
157 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
158 | + 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000'}), |
159 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
160 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
161 | + }, |
162 | + u'maasserver.config': { |
163 | + 'Meta': {'object_name': 'Config'}, |
164 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
165 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
166 | + 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'}) |
167 | + }, |
168 | + u'maasserver.dhcplease': { |
169 | + 'Meta': {'object_name': 'DHCPLease'}, |
170 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
171 | + 'ip': ('maasserver.fields.MAASIPAddressField', [], {'unique': 'True', 'max_length': '39'}), |
172 | + 'mac': ('maasserver.fields.MACAddressField', [], {}), |
173 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}) |
174 | + }, |
175 | + u'maasserver.downloadprogress': { |
176 | + 'Meta': {'object_name': 'DownloadProgress'}, |
177 | + 'bytes_downloaded': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), |
178 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
179 | + 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}), |
180 | + 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
181 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
182 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}), |
183 | + 'size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), |
184 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
185 | + }, |
186 | + u'maasserver.filestorage': { |
187 | + 'Meta': {'unique_together': "((u'filename', u'owner'),)", 'object_name': 'FileStorage'}, |
188 | + 'content': ('metadataserver.fields.BinaryField', [], {'blank': 'True'}), |
189 | + 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
190 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
191 | + 'key': ('django.db.models.fields.CharField', [], {'default': "u'a95c24b8-0dbb-11e4-a6c6-bcee7b78dc5b'", 'unique': 'True', 'max_length': '36'}), |
192 | + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}) |
193 | + }, |
194 | + u'maasserver.licensekey': { |
195 | + 'Meta': {'unique_together': "((u'osystem', u'distro_series'),)", 'object_name': 'LicenseKey'}, |
196 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
197 | + 'distro_series': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
198 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
199 | + 'license_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
200 | + 'osystem': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
201 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
202 | + }, |
203 | + u'maasserver.macaddress': { |
204 | + 'Meta': {'object_name': 'MACAddress'}, |
205 | + 'cluster_interface': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['maasserver.NodeGroupInterface']", 'null': 'True', 'blank': 'True'}), |
206 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
207 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
208 | + 'ip_addresses': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.StaticIPAddress']", 'symmetrical': 'False', 'through': u"orm['maasserver.MACStaticIPAddressLink']", 'blank': 'True'}), |
209 | + 'mac_address': ('maasserver.fields.MACAddressField', [], {'unique': 'True'}), |
210 | + 'networks': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Network']", 'symmetrical': 'False', 'blank': 'True'}), |
211 | + 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}), |
212 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
213 | + }, |
214 | + u'maasserver.macstaticipaddresslink': { |
215 | + 'Meta': {'unique_together': "((u'ip_address', u'mac_address'),)", 'object_name': 'MACStaticIPAddressLink'}, |
216 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
217 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
218 | + 'ip_address': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.StaticIPAddress']", 'unique': 'True'}), |
219 | + 'mac_address': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.MACAddress']"}), |
220 | + 'nic_alias': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), |
221 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
222 | + }, |
223 | + u'maasserver.network': { |
224 | + 'Meta': {'object_name': 'Network'}, |
225 | + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), |
226 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
227 | + 'ip': ('maasserver.fields.MAASIPAddressField', [], {'unique': 'True', 'max_length': '39'}), |
228 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), |
229 | + 'netmask': ('maasserver.fields.MAASIPAddressField', [], {'max_length': '39'}), |
230 | + 'vlan_tag': ('django.db.models.fields.PositiveSmallIntegerField', [], {'unique': 'True', 'null': 'True', 'blank': 'True'}) |
231 | + }, |
232 | + u'maasserver.node': { |
233 | + 'Meta': {'object_name': 'Node'}, |
234 | + 'agent_name': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'null': 'True', 'blank': 'True'}), |
235 | + 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '31'}), |
236 | + 'boot_type': ('django.db.models.fields.CharField', [], {'default': "u'fastpath'", 'max_length': '20'}), |
237 | + 'cpu_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
238 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
239 | + 'distro_series': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}), |
240 | + 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
241 | + 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'unique': 'True', 'max_length': '255', 'blank': 'True'}), |
242 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
243 | + 'license_key': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}), |
244 | + 'memory': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
245 | + 'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
246 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}), |
247 | + 'osystem': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}), |
248 | + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}), |
249 | + 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}), |
250 | + 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}), |
251 | + 'routers': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'macaddr'", 'null': 'True', 'blank': 'True'}), |
252 | + 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}), |
253 | + 'storage': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
254 | + 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-a95b43ea-0dbb-11e4-a6c6-bcee7b78dc5b'", 'unique': 'True', 'max_length': '41'}), |
255 | + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}), |
256 | + 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'null': 'True'}), |
257 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}), |
258 | + 'zone': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Zone']", 'on_delete': 'models.SET_DEFAULT'}) |
259 | + }, |
260 | + u'maasserver.nodegroup': { |
261 | + 'Meta': {'object_name': 'NodeGroup'}, |
262 | + 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}), |
263 | + 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'unique': 'True'}), |
264 | + 'cluster_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100', 'blank': 'True'}), |
265 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
266 | + 'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
267 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
268 | + 'maas_url': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
269 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}), |
270 | + 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
271 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}), |
272 | + 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'}) |
273 | + }, |
274 | + u'maasserver.nodegroupinterface': { |
275 | + 'Meta': {'unique_together': "((u'nodegroup', u'name'),)", 'object_name': 'NodeGroupInterface'}, |
276 | + 'broadcast_ip': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
277 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
278 | + 'foreign_dhcp_ip': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
279 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
280 | + 'interface': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
281 | + 'ip': ('maasserver.fields.MAASIPAddressField', [], {'max_length': '39'}), |
282 | + 'ip_range_high': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
283 | + 'ip_range_low': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
284 | + 'management': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
285 | + 'name': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
286 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}), |
287 | + 'router_ip': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
288 | + 'static_ip_range_high': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
289 | + 'static_ip_range_low': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
290 | + 'subnet_mask': ('maasserver.fields.MAASIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
291 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
292 | + }, |
293 | + u'maasserver.sshkey': { |
294 | + 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'}, |
295 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
296 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
297 | + 'key': ('django.db.models.fields.TextField', [], {}), |
298 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}), |
299 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}) |
300 | + }, |
301 | + u'maasserver.sslkey': { |
302 | + 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSLKey'}, |
303 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
304 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
305 | + 'key': ('django.db.models.fields.TextField', [], {}), |
306 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}), |
307 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}) |
308 | + }, |
309 | + u'maasserver.staticipaddress': { |
310 | + 'Meta': {'object_name': 'StaticIPAddress'}, |
311 | + 'alloc_type': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
312 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
313 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
314 | + 'ip': ('maasserver.fields.MAASIPAddressField', [], {'unique': 'True', 'max_length': '39'}), |
315 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}), |
316 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}) |
317 | + }, |
318 | + u'maasserver.tag': { |
319 | + 'Meta': {'object_name': 'Tag'}, |
320 | + 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}), |
321 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
322 | + 'definition': ('django.db.models.fields.TextField', [], {'blank': 'True'}), |
323 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
324 | + 'kernel_opts': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), |
325 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}), |
326 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
327 | + }, |
328 | + u'maasserver.userprofile': { |
329 | + 'Meta': {'object_name': 'UserProfile'}, |
330 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
331 | + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'}) |
332 | + }, |
333 | + u'maasserver.zone': { |
334 | + 'Meta': {'ordering': "[u'name']", 'object_name': 'Zone'}, |
335 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
336 | + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), |
337 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
338 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}), |
339 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
340 | + }, |
341 | + u'piston.consumer': { |
342 | + 'Meta': {'object_name': 'Consumer'}, |
343 | + 'description': ('django.db.models.fields.TextField', [], {}), |
344 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
345 | + 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), |
346 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
347 | + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), |
348 | + 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}), |
349 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': u"orm['auth.User']"}) |
350 | + }, |
351 | + u'piston.token': { |
352 | + 'Meta': {'object_name': 'Token'}, |
353 | + 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), |
354 | + 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
355 | + 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Consumer']"}), |
356 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
357 | + 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
358 | + 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), |
359 | + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), |
360 | + 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1405606037L'}), |
361 | + 'token_type': ('django.db.models.fields.IntegerField', [], {}), |
362 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': u"orm['auth.User']"}), |
363 | + 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'}) |
364 | + } |
365 | + } |
366 | + |
367 | + complete_apps = ['maasserver'] |
368 | |
369 | === modified file 'src/maasserver/models/node.py' |
370 | --- src/maasserver/models/node.py 2014-07-16 02:34:10 +0000 |
371 | +++ src/maasserver/models/node.py 2014-07-18 14:52:34 +0000 |
372 | @@ -51,6 +51,8 @@ |
373 | logger, |
374 | ) |
375 | from maasserver.enum import ( |
376 | + NODE_BOOT, |
377 | + NODE_BOOT_CHOICES, |
378 | NODE_PERMISSION, |
379 | NODE_STATUS, |
380 | NODE_STATUS_CHOICES, |
381 | @@ -500,6 +502,8 @@ |
382 | :ivar status: This `Node`'s status. See the vocabulary |
383 | :class:`NODE_STATUS`. |
384 | :ivar owner: This `Node`'s owner if it's in use, None otherwise. |
385 | + :ivar boot_type: This `Node`'s booting method. See the vocabulary |
386 | + :class:`NODE_BOOT`. |
387 | :ivar osystem: This `Node`'s booting operating system, if it's blank then |
388 | the default_osystem will be used. |
389 | :ivar distro_series: This `Node`'s booting distro series, if |
390 | @@ -531,6 +535,9 @@ |
391 | owner = ForeignKey( |
392 | User, default=None, blank=True, null=True, editable=False) |
393 | |
394 | + boot_type = CharField( |
395 | + max_length=20, choices=NODE_BOOT_CHOICES, default=NODE_BOOT.FASTPATH) |
396 | + |
397 | osystem = CharField( |
398 | max_length=20, blank=True, default='') |
399 | |
400 | @@ -1158,58 +1165,24 @@ |
401 | def should_use_traditional_installer(self): |
402 | """Should this node be installed with the traditional installer? |
403 | |
404 | - By default, nodes should be installed with the traditional installer, |
405 | - so this returns `True` when no `use-fastpath-installer` tag has been |
406 | - defined. |
407 | + By default, nodes should be installed with the Fast Path installer. |
408 | """ |
409 | - return not self.should_use_fastpath_installer() |
410 | + return self.boot_type == NODE_BOOT.DEBIAN |
411 | |
412 | def should_use_fastpath_installer(self): |
413 | """Should this node be installed with the Fast Path installer? |
414 | |
415 | - By default, nodes should be installed with the traditional installer, |
416 | - so this returns `True` when the `use-fastpath-installer` has been |
417 | - defined and `False` when it hasn't. |
418 | + By default, nodes should be installed with the Fast Path installer. |
419 | """ |
420 | - return self.tags.filter(name="use-fastpath-installer").exists() |
421 | + return self.boot_type == NODE_BOOT.FASTPATH |
422 | |
423 | def use_traditional_installer(self): |
424 | - """Set this node to be installed with the traditional installer. |
425 | - |
426 | - By default, nodes should be installed with the Traditional installer. |
427 | - |
428 | - :raises: :class:`RuntimeError` when the `use-traditional-installer` |
429 | - tag is defined *with* an expression. The reason is that the tag |
430 | - evaluation machinery will eventually ignore whatever changes you |
431 | - make with this method. |
432 | - """ |
433 | - uti_tag, _ = Tag.objects.get_or_create( |
434 | - name="use-fastpath-installer") |
435 | - if uti_tag.is_defined: |
436 | - raise RuntimeError( |
437 | - "The use-fastpath-installer tag is defined with an " |
438 | - "expression. This expression must instead be updated to set " |
439 | - "this node to install with the Debian installer.") |
440 | - self.tags.remove(uti_tag) |
441 | + """Set this node to be installed with the traditional installer.""" |
442 | + self.boot_type = NODE_BOOT.DEBIAN |
443 | |
444 | def use_fastpath_installer(self): |
445 | - """Set this node to be installed with the Fast Path Installer. |
446 | - |
447 | - By default, nodes should be installed with the Traditional Installer. |
448 | - |
449 | - :raises: :class:`RuntimeError` when the `use-fastpath-installer` |
450 | - tag is defined *with* an expression. The reason is that the tag |
451 | - evaluation machinery will eventually ignore whatever changes you |
452 | - make with this method. |
453 | - """ |
454 | - uti_tag, _ = Tag.objects.get_or_create( |
455 | - name="use-fastpath-installer") |
456 | - if uti_tag.is_defined: |
457 | - raise RuntimeError( |
458 | - "The use-fastpath-installer tag is defined with an " |
459 | - "expression. This expression must instead be updated to set " |
460 | - "this node to install with the fast installer.") |
461 | - self.tags.add(uti_tag) |
462 | + """Set this node to be installed with the Fast Path Installer.""" |
463 | + self.boot_type = NODE_BOOT.FASTPATH |
464 | |
465 | def split_arch(self): |
466 | """Return architecture and subarchitecture, as a tuple.""" |
467 | |
468 | === modified file 'src/maasserver/models/tests/test_node.py' |
469 | --- src/maasserver/models/tests/test_node.py 2014-07-16 14:12:13 +0000 |
470 | +++ src/maasserver/models/tests/test_node.py 2014-07-18 14:52:34 +0000 |
471 | @@ -23,6 +23,7 @@ |
472 | from maasserver.clusterrpc.power_parameters import get_power_types |
473 | from maasserver.enum import ( |
474 | IPADDRESS_TYPE, |
475 | + NODE_BOOT, |
476 | NODE_PERMISSION, |
477 | NODE_STATUS, |
478 | NODE_STATUS_CHOICES, |
479 | @@ -41,7 +42,6 @@ |
480 | MACAddress, |
481 | Node, |
482 | node as node_module, |
483 | - Tag, |
484 | ) |
485 | from maasserver.models.node import ( |
486 | generate_hostname, |
487 | @@ -1025,28 +1025,26 @@ |
488 | expected_hostname = '%s.%s' % (hostname_without_domain, domain) |
489 | self.assertEqual(expected_hostname, node.fqdn) |
490 | |
491 | - def test_should_use_traditional_installer_by_default(self): |
492 | + def test_boot_type_has_fastpath_set_by_default(self): |
493 | node = factory.make_node() |
494 | + self.assertEqual(NODE_BOOT.FASTPATH, node.boot_type) |
495 | + |
496 | + def test_should_use_traditional_installer_returns_True_for_debian(self): |
497 | + node = factory.make_node(boot_type=NODE_BOOT.DEBIAN) |
498 | self.assertTrue(node.should_use_traditional_installer()) |
499 | |
500 | - def test_should_not_use_fastpath_installer_by_default(self): |
501 | - node = factory.make_node() |
502 | - self.assertFalse(node.should_use_fastpath_installer()) |
503 | - |
504 | - def test_should_use_traditional_installer_not_when_tag_applies(self): |
505 | - node = factory.make_node() |
506 | - tag = factory.make_tag(name="use-fastpath-installer") |
507 | - tag.save() |
508 | - node.tags.add(tag) |
509 | + def test_should_use_traditional_installer_returns_False_for_fastpath(self): |
510 | + node = factory.make_node(boot_type=NODE_BOOT.FASTPATH) |
511 | self.assertFalse(node.should_use_traditional_installer()) |
512 | |
513 | - def test_should_use_fastpath_installer_when_tag_applies(self): |
514 | - node = factory.make_node() |
515 | - tag = factory.make_tag(name="use-fastpath-installer") |
516 | - tag.save() |
517 | - node.tags.add(tag) |
518 | + def test_should_use_fastpath_installer_returns_True_for_fastpath(self): |
519 | + node = factory.make_node(boot_type=NODE_BOOT.FASTPATH) |
520 | self.assertTrue(node.should_use_fastpath_installer()) |
521 | |
522 | + def test_should_use_fastpath_installer_returns_False_for_debian(self): |
523 | + node = factory.make_node(boot_type=NODE_BOOT.DEBIAN) |
524 | + self.assertFalse(node.should_use_fastpath_installer()) |
525 | + |
526 | def test_use_xxx_installer(self): |
527 | # use_fastpath_installer() and use_traditional_installer() can be used |
528 | # to affect what the should_use_xxx_installer() methods return. |
529 | @@ -1058,56 +1056,6 @@ |
530 | self.assertTrue(node.should_use_fastpath_installer()) |
531 | self.assertFalse(node.should_use_traditional_installer()) |
532 | |
533 | - def test_use_traditional_installer_dissociates_tag_from_node(self): |
534 | - # use_traditional_installer removes any association with the |
535 | - # use-fastpath-installer tag. The tag is created even if it did not |
536 | - # previously exist. If it does already exist, it is not deleted. |
537 | - find_tag = lambda: list( |
538 | - Tag.objects.filter(name="use-fastpath-installer")) |
539 | - node = factory.make_node() |
540 | - node.use_traditional_installer() |
541 | - self.assertNotEqual([], find_tag()) |
542 | - node.use_fastpath_installer() |
543 | - node.use_traditional_installer() |
544 | - self.assertNotEqual([], find_tag()) |
545 | - |
546 | - def test_use_fastpath_installer_associates_tag_with_node(self): |
547 | - # use_traditional_installer() creates the use-traditional-installer |
548 | - # tag when it is first needed, and associates it with the node. |
549 | - find_tag = lambda: list( |
550 | - Tag.objects.filter(name="use-fastpath-installer")) |
551 | - self.assertEqual([], find_tag()) |
552 | - node = factory.make_node() |
553 | - node.use_fastpath_installer() |
554 | - self.assertNotEqual([], find_tag()) |
555 | - |
556 | - def test_use_traditional_installer_complains_when_tag_has_expression(self): |
557 | - # use_traditional_installer() complains when the use-fastpath-installer |
558 | - # tag exists and is defined with an expression. |
559 | - node = factory.make_node() |
560 | - factory.make_tag( |
561 | - name="use-fastpath-installer", |
562 | - definition="//something") |
563 | - error = self.assertRaises( |
564 | - RuntimeError, node.use_traditional_installer) |
565 | - self.assertIn( |
566 | - "The use-fastpath-installer tag is defined with an expression", |
567 | - unicode(error)) |
568 | - |
569 | - def test_use_fastpath_installer_complains_when_tag_has_expression(self): |
570 | - # use_fastpath_installer() complains when the |
571 | - # use-fastpath-installer tag exists and is defined with an |
572 | - # expression. |
573 | - node = factory.make_node() |
574 | - factory.make_tag( |
575 | - name="use-fastpath-installer", |
576 | - definition="//something") |
577 | - error = self.assertRaises( |
578 | - RuntimeError, node.use_fastpath_installer) |
579 | - self.assertIn( |
580 | - "The use-fastpath-installer tag is defined with an expression", |
581 | - unicode(error)) |
582 | - |
583 | def test_split_arch_returns_arch_as_tuple(self): |
584 | main_arch = factory.make_name('arch') |
585 | sub_arch = factory.make_name('subarch') |
586 | |
587 | === modified file 'src/maasserver/node_action.py' |
588 | --- src/maasserver/node_action.py 2014-07-11 13:28:41 +0000 |
589 | +++ src/maasserver/node_action.py 2014-07-18 14:52:34 +0000 |
590 | @@ -35,6 +35,7 @@ |
591 | |
592 | from django.core.urlresolvers import reverse |
593 | from maasserver.enum import ( |
594 | + NODE_BOOT, |
595 | NODE_PERMISSION, |
596 | NODE_STATUS, |
597 | NODE_STATUS_CHOICES_DICT, |
598 | @@ -48,7 +49,6 @@ |
599 | Node, |
600 | SSHKey, |
601 | ) |
602 | -from maasserver.models.tag import Tag |
603 | from maasserver.utils import map_enum |
604 | |
605 | # All node statuses. |
606 | @@ -212,25 +212,14 @@ |
607 | |
608 | def is_permitted(self): |
609 | permitted = super(UseCurtin, self).is_permitted() |
610 | - return permitted and self.node.should_use_traditional_installer() |
611 | + return permitted and self.node.boot_type == NODE_BOOT.DEBIAN |
612 | |
613 | def execute(self, allow_redirect=True): |
614 | """See `NodeAction.execute`.""" |
615 | - self.node.use_fastpath_installer() |
616 | + self.node.boot_type = NODE_BOOT.FASTPATH |
617 | + self.node.save() |
618 | return "Node marked as using curtin for install." |
619 | |
620 | - def inhibit(self): |
621 | - """Inhibit if ``use-fastpath-installer`` uses an expression.""" |
622 | - tag, _ = Tag.objects.get_or_create(name="use-fastpath-installer") |
623 | - if tag.is_defined: |
624 | - return dedent("""\ |
625 | - The use-fastpath-installer tag is defined with an |
626 | - expression. This expression must instead be updated to set |
627 | - this node to install with the fast installer. |
628 | - """) |
629 | - else: |
630 | - return None |
631 | - |
632 | |
633 | class UseDI(NodeAction): |
634 | """Set this node to use d-i for installation.""" |
635 | @@ -242,25 +231,14 @@ |
636 | |
637 | def is_permitted(self): |
638 | permitted = super(UseDI, self).is_permitted() |
639 | - return permitted and self.node.should_use_fastpath_installer() |
640 | + return permitted and self.node.boot_type == NODE_BOOT.FASTPATH |
641 | |
642 | def execute(self, allow_redirect=True): |
643 | """See `NodeAction.execute`.""" |
644 | - self.node.use_traditional_installer() |
645 | + self.node.boot_type = NODE_BOOT.DEBIAN |
646 | + self.node.save() |
647 | return "Node marked as using the Debian installer." |
648 | |
649 | - def inhibit(self): |
650 | - """Inhibit if ``use-fastpath-installer`` uses an expression.""" |
651 | - tag, _ = Tag.objects.get_or_create(name="use-fastpath-installer") |
652 | - if tag.is_defined: |
653 | - return dedent("""\ |
654 | - The use-fastpath-installer tag is defined with an |
655 | - expression. This expression must instead be updated to set |
656 | - this node to install with the Debian installer. |
657 | - """) |
658 | - else: |
659 | - return None |
660 | - |
661 | |
662 | class StartNode(NodeAction): |
663 | """Acquire and start a node.""" |
664 | |
665 | === modified file 'src/maasserver/tests/test_node_action.py' |
666 | --- src/maasserver/tests/test_node_action.py 2014-07-16 14:12:13 +0000 |
667 | +++ src/maasserver/tests/test_node_action.py 2014-07-18 14:52:34 +0000 |
668 | @@ -18,6 +18,7 @@ |
669 | |
670 | from django.core.urlresolvers import reverse |
671 | from maasserver.enum import ( |
672 | + NODE_BOOT, |
673 | NODE_PERMISSION, |
674 | NODE_STATUS, |
675 | NODE_STATUS_CHOICES, |
676 | @@ -316,75 +317,45 @@ |
677 | |
678 | class TestUseCurtinNodeAction(MAASServerTestCase): |
679 | |
680 | - def test_sets_tag(self): |
681 | + def test_sets_boot_type(self): |
682 | user = factory.make_user() |
683 | - node = factory.make_node(owner=user) |
684 | - node.use_traditional_installer() |
685 | + node = factory.make_node(owner=user, boot_type=NODE_BOOT.DEBIAN) |
686 | action = UseCurtin(node, user) |
687 | self.assertTrue(action.is_permitted()) |
688 | action.execute() |
689 | - self.assertTrue(node.should_use_fastpath_installer()) |
690 | + self.assertEqual(NODE_BOOT.FASTPATH, node.boot_type) |
691 | |
692 | def test_requires_edit_permission(self): |
693 | user = factory.make_user() |
694 | - node = factory.make_node() |
695 | - node.use_traditional_installer() |
696 | + node = factory.make_node(boot_type=NODE_BOOT.DEBIAN) |
697 | self.assertFalse(UseCurtin(node, user).is_permitted()) |
698 | |
699 | def test_not_permitted_if_already_uses_curtin(self): |
700 | - node = factory.make_node() |
701 | - node.use_fastpath_installer() |
702 | + node = factory.make_node(boot_type=NODE_BOOT.FASTPATH) |
703 | user = factory.make_admin() |
704 | self.assertFalse(UseCurtin(node, user).is_permitted()) |
705 | |
706 | - def test_inhibited_if_use_fastpath_installer_tag_uses_expr(self): |
707 | - make_use_fastpath_installer_tag_with_expression() |
708 | - node = factory.make_node() |
709 | - user = factory.make_admin() |
710 | - self.assertDocTestMatches( |
711 | - """\ |
712 | - The use-fastpath-installer tag is defined with an |
713 | - expression. This expression must instead be updated to set |
714 | - this node to install with the fast installer. |
715 | - """, |
716 | - UseCurtin(node, user).inhibit()) |
717 | - |
718 | |
719 | class TestUseDINodeAction(MAASServerTestCase): |
720 | |
721 | - def test_sets_tag(self): |
722 | + def test_sets_boot_type(self): |
723 | user = factory.make_user() |
724 | - node = factory.make_node(owner=user) |
725 | - node.use_fastpath_installer() |
726 | + node = factory.make_node(owner=user, boot_type=NODE_BOOT.FASTPATH) |
727 | action = UseDI(node, user) |
728 | self.assertTrue(action.is_permitted()) |
729 | action.execute() |
730 | - self.assertTrue(node.should_use_traditional_installer()) |
731 | + self.assertEqual(NODE_BOOT.DEBIAN, node.boot_type) |
732 | |
733 | def test_requires_edit_permission(self): |
734 | user = factory.make_user() |
735 | - node = factory.make_node() |
736 | - node.use_fastpath_installer() |
737 | + node = factory.make_node(boot_type=NODE_BOOT.FASTPATH) |
738 | self.assertFalse(UseDI(node, user).is_permitted()) |
739 | |
740 | def test_not_permitted_if_already_uses_di(self): |
741 | - node = factory.make_node() |
742 | - node.use_traditional_installer() |
743 | + node = factory.make_node(boot_type=NODE_BOOT.DEBIAN) |
744 | user = factory.make_admin() |
745 | self.assertFalse(UseDI(node, user).is_permitted()) |
746 | |
747 | - def test_inhibited_if_use_fastpath_installer_tag_uses_expr(self): |
748 | - make_use_fastpath_installer_tag_with_expression() |
749 | - node = factory.make_node() |
750 | - user = factory.make_admin() |
751 | - self.assertDocTestMatches( |
752 | - """\ |
753 | - The use-fastpath-installer tag is defined with an |
754 | - expression. This expression must instead be updated to set |
755 | - this node to install with the Debian installer. |
756 | - """, |
757 | - UseDI(node, user).inhibit()) |
758 | - |
759 | |
760 | class TestMarkBrokenAction(MAASServerTestCase): |
761 |
Looks good. I've got a couple of comment (see inline) but more importantly:
Now that boot_type is a property on the node, we don't really need the should_ use_fastpath_ installer/ should_ use_traditional _installer encapsulations anymore.
Actually, if you exclude the tests, should_ use_fastpath_ installer is used only once and should_ use_traditional _installer is not even used anymore (!).