Merge lp:~rvb/maas/ui-update-fqdn-1.2 into lp:maas/1.2
- ui-update-fqdn-1.2
- Merge into 1.2
Proposed by
Raphaël Badin
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Raphaël Badin | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 1303 | ||||
Proposed branch: | lp:~rvb/maas/ui-update-fqdn-1.2 | ||||
Merge into: | lp:maas/1.2 | ||||
Diff against target: |
881 lines (+505/-58) 18 files modified
src/maasserver/forms.py (+9/-0) src/maasserver/migrations/0043_unique_hostname_preparation.py (+218/-0) src/maasserver/migrations/0044_node_hostname_unique.py (+201/-0) src/maasserver/models/node.py (+2/-1) src/maasserver/templates/maasserver/node_edit.html (+2/-0) src/maasserver/templates/maasserver/node_view.html (+4/-4) src/maasserver/templates/maasserver/nodes_listing.html (+9/-6) src/maasserver/testing/factory.py (+3/-3) src/maasserver/tests/test_api.py (+15/-7) src/maasserver/tests/test_dhcplease.py (+3/-3) src/maasserver/tests/test_dns.py (+1/-1) src/maasserver/tests/test_forms.py (+0/-12) src/maasserver/tests/test_node.py (+2/-3) src/maasserver/tests/test_node_constraint_filter.py (+2/-2) src/maasserver/tests/test_views_nodes.py (+18/-1) src/maasserver/tests/test_views_tags.py (+10/-14) src/maasserver/views/nodes.py (+2/-0) src/maasserver/views/tags.py (+4/-1) |
||||
To merge this branch: | bzr merge lp:~rvb/maas/ui-update-fqdn-1.2 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Raphaël Badin (community) | Approve | ||
Review via email: mp+134663@code.launchpad.net |
Commit message
Backport of 1335 (and required db migration merged in revision 1330).
Description of the change
Backport of 1335 (and required db migration merged in revision 1330).
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/maasserver/forms.py' |
2 | --- src/maasserver/forms.py 2012-11-16 07:43:42 +0000 |
3 | +++ src/maasserver/forms.py 2012-11-16 13:58:29 +0000 |
4 | @@ -152,6 +152,15 @@ |
5 | initial=ARCHITECTURE.i386, |
6 | error_messages={'invalid_choice': INVALID_ARCHITECTURE_MESSAGE}) |
7 | |
8 | + hostname = forms.CharField( |
9 | + label="Host name", required=False, help_text=( |
10 | + "The FQDN (Fully Qualified Domain Name) is derived from the " |
11 | + "host name: If the cluster controller for this node is managing " |
12 | + "DNS then the domain part in the host name (if any) is replaced " |
13 | + "by the domain defined on the cluster; if the cluster controller " |
14 | + "does not manage DNS, then the host name as entered will be the " |
15 | + "FQDN.")) |
16 | + |
17 | class Meta: |
18 | model = Node |
19 | |
20 | |
21 | === added file 'src/maasserver/migrations/0043_unique_hostname_preparation.py' |
22 | --- src/maasserver/migrations/0043_unique_hostname_preparation.py 1970-01-01 00:00:00 +0000 |
23 | +++ src/maasserver/migrations/0043_unique_hostname_preparation.py 2012-11-16 13:58:29 +0000 |
24 | @@ -0,0 +1,218 @@ |
25 | +# -*- coding: utf-8 -*- |
26 | +import datetime |
27 | + |
28 | +from django.db import models |
29 | +from django.db.models import Count |
30 | +from south.db import db |
31 | +from south.v2 import DataMigration |
32 | + |
33 | + |
34 | +class Migration(DataMigration): |
35 | + |
36 | + def forwards(self, orm): |
37 | + # Find the nodes with duplicated hostnames. |
38 | + duplicated_hostnames = orm['maasserver.node'].objects.values_list( |
39 | + 'hostname', flat=True).annotate( |
40 | + hostname_count=Count('hostname')).exclude(hostname_count=1) |
41 | + # Rename the nodes with duplicated hostnames. |
42 | + for duplicated_hostname in duplicated_hostnames: |
43 | + nodes_with_duplicated_hostnames = ( |
44 | + orm['maasserver.node'].objects.filter( |
45 | + hostname=duplicated_hostname)) |
46 | + # Rename all the nodes but one. |
47 | + for node in nodes_with_duplicated_hostnames[1:]: |
48 | + number = 1 |
49 | + while True: |
50 | + new_hostname = '%s-%d' % (node.hostname, number) |
51 | + if not orm['maasserver.node'].objects.filter(hostname=new_hostname).exists(): |
52 | + node.hostname = new_hostname |
53 | + node.save() |
54 | + break |
55 | + number += 1 |
56 | + |
57 | + |
58 | + def backwards(self, orm): |
59 | + pass |
60 | + |
61 | + models = { |
62 | + 'auth.group': { |
63 | + 'Meta': {'object_name': 'Group'}, |
64 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
65 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), |
66 | + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) |
67 | + }, |
68 | + 'auth.permission': { |
69 | + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, |
70 | + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
71 | + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), |
72 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
73 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) |
74 | + }, |
75 | + 'auth.user': { |
76 | + 'Meta': {'object_name': 'User'}, |
77 | + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
78 | + 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}), |
79 | + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
80 | + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), |
81 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
82 | + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
83 | + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
84 | + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
85 | + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
86 | + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
87 | + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), |
88 | + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), |
89 | + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) |
90 | + }, |
91 | + 'contenttypes.contenttype': { |
92 | + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, |
93 | + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
94 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
95 | + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
96 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) |
97 | + }, |
98 | + u'maasserver.bootimage': { |
99 | + 'Meta': {'unique_together': "((u'nodegroup', u'architecture', u'subarchitecture', u'release', u'purpose'),)", 'object_name': 'BootImage'}, |
100 | + 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
101 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
102 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}), |
103 | + 'purpose': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
104 | + 'release': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
105 | + 'subarchitecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}) |
106 | + }, |
107 | + u'maasserver.componenterror': { |
108 | + 'Meta': {'object_name': 'ComponentError'}, |
109 | + 'component': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}), |
110 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
111 | + 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000'}), |
112 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
113 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
114 | + }, |
115 | + u'maasserver.config': { |
116 | + 'Meta': {'object_name': 'Config'}, |
117 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
118 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
119 | + 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'}) |
120 | + }, |
121 | + u'maasserver.dhcplease': { |
122 | + 'Meta': {'object_name': 'DHCPLease'}, |
123 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
124 | + 'ip': ('django.db.models.fields.IPAddressField', [], {'unique': 'True', 'max_length': '15'}), |
125 | + 'mac': ('maasserver.fields.MACAddressField', [], {}), |
126 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}) |
127 | + }, |
128 | + u'maasserver.filestorage': { |
129 | + 'Meta': {'object_name': 'FileStorage'}, |
130 | + 'content': ('metadataserver.fields.BinaryField', [], {}), |
131 | + 'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), |
132 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) |
133 | + }, |
134 | + u'maasserver.macaddress': { |
135 | + 'Meta': {'object_name': 'MACAddress'}, |
136 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
137 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
138 | + 'mac_address': ('maasserver.fields.MACAddressField', [], {'unique': 'True'}), |
139 | + 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}), |
140 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
141 | + }, |
142 | + u'maasserver.node': { |
143 | + 'Meta': {'object_name': 'Node'}, |
144 | + 'after_commissioning_action': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
145 | + 'architecture': ('django.db.models.fields.CharField', [], {'default': "u'i386/generic'", 'max_length': '31'}), |
146 | + 'cpu_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
147 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
148 | + 'distro_series': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '10', 'null': 'True', 'blank': 'True'}), |
149 | + 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
150 | + 'hardware_details': ('maasserver.fields.XMLField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), |
151 | + 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
152 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
153 | + 'memory': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
154 | + 'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
155 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}), |
156 | + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), |
157 | + 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}), |
158 | + 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}), |
159 | + 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}), |
160 | + 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-6b5652fe-2412-11e2-9eff-9c4e363b1c94'", 'unique': 'True', 'max_length': '41'}), |
161 | + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}), |
162 | + 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Token']", 'null': 'True'}), |
163 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
164 | + }, |
165 | + u'maasserver.nodegroup': { |
166 | + 'Meta': {'object_name': 'NodeGroup'}, |
167 | + 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}), |
168 | + 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Token']", 'unique': 'True'}), |
169 | + 'cluster_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100', 'blank': 'True'}), |
170 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
171 | + 'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
172 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
173 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}), |
174 | + 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
175 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}), |
176 | + 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'}) |
177 | + }, |
178 | + u'maasserver.nodegroupinterface': { |
179 | + 'Meta': {'unique_together': "((u'nodegroup', u'interface'),)", 'object_name': 'NodeGroupInterface'}, |
180 | + 'broadcast_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
181 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
182 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
183 | + 'interface': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
184 | + 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), |
185 | + 'ip_range_high': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
186 | + 'ip_range_low': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
187 | + 'management': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
188 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}), |
189 | + 'router_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
190 | + 'subnet_mask': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
191 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
192 | + }, |
193 | + u'maasserver.sshkey': { |
194 | + 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'}, |
195 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
196 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
197 | + 'key': ('django.db.models.fields.TextField', [], {}), |
198 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}), |
199 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) |
200 | + }, |
201 | + u'maasserver.tag': { |
202 | + 'Meta': {'object_name': 'Tag'}, |
203 | + 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}), |
204 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
205 | + 'definition': ('django.db.models.fields.TextField', [], {'blank': 'True'}), |
206 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
207 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}), |
208 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
209 | + }, |
210 | + u'maasserver.userprofile': { |
211 | + 'Meta': {'object_name': 'UserProfile'}, |
212 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
213 | + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) |
214 | + }, |
215 | + 'piston.consumer': { |
216 | + 'Meta': {'object_name': 'Consumer'}, |
217 | + 'description': ('django.db.models.fields.TextField', [], {}), |
218 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
219 | + 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), |
220 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
221 | + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), |
222 | + 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}), |
223 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': "orm['auth.User']"}) |
224 | + }, |
225 | + 'piston.token': { |
226 | + 'Meta': {'object_name': 'Token'}, |
227 | + 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), |
228 | + 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
229 | + 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Consumer']"}), |
230 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
231 | + 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
232 | + 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), |
233 | + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), |
234 | + 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1351767230L'}), |
235 | + 'token_type': ('django.db.models.fields.IntegerField', [], {}), |
236 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': "orm['auth.User']"}), |
237 | + 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'}) |
238 | + } |
239 | + } |
240 | + |
241 | + complete_apps = ['maasserver'] |
242 | + symmetrical = True |
243 | |
244 | === added file 'src/maasserver/migrations/0044_node_hostname_unique.py' |
245 | --- src/maasserver/migrations/0044_node_hostname_unique.py 1970-01-01 00:00:00 +0000 |
246 | +++ src/maasserver/migrations/0044_node_hostname_unique.py 2012-11-16 13:58:29 +0000 |
247 | @@ -0,0 +1,201 @@ |
248 | +# -*- coding: utf-8 -*- |
249 | +import datetime |
250 | + |
251 | +from django.db import models |
252 | +from south.db import db |
253 | +from south.v2 import SchemaMigration |
254 | + |
255 | + |
256 | +class Migration(SchemaMigration): |
257 | + |
258 | + def forwards(self, orm): |
259 | + # Adding unique constraint on 'Node', fields ['hostname'] |
260 | + db.create_unique(u'maasserver_node', ['hostname']) |
261 | + |
262 | + |
263 | + def backwards(self, orm): |
264 | + # Removing unique constraint on 'Node', fields ['hostname'] |
265 | + db.delete_unique(u'maasserver_node', ['hostname']) |
266 | + |
267 | + |
268 | + models = { |
269 | + 'auth.group': { |
270 | + 'Meta': {'object_name': 'Group'}, |
271 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
272 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), |
273 | + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) |
274 | + }, |
275 | + 'auth.permission': { |
276 | + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, |
277 | + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
278 | + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), |
279 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
280 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) |
281 | + }, |
282 | + 'auth.user': { |
283 | + 'Meta': {'object_name': 'User'}, |
284 | + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
285 | + 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}), |
286 | + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
287 | + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), |
288 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
289 | + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
290 | + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
291 | + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
292 | + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
293 | + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
294 | + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), |
295 | + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), |
296 | + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) |
297 | + }, |
298 | + 'contenttypes.contenttype': { |
299 | + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, |
300 | + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
301 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
302 | + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
303 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) |
304 | + }, |
305 | + u'maasserver.bootimage': { |
306 | + 'Meta': {'unique_together': "((u'nodegroup', u'architecture', u'subarchitecture', u'release', u'purpose'),)", 'object_name': 'BootImage'}, |
307 | + 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
308 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
309 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}), |
310 | + 'purpose': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
311 | + 'release': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
312 | + 'subarchitecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}) |
313 | + }, |
314 | + u'maasserver.componenterror': { |
315 | + 'Meta': {'object_name': 'ComponentError'}, |
316 | + 'component': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}), |
317 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
318 | + 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000'}), |
319 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
320 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
321 | + }, |
322 | + u'maasserver.config': { |
323 | + 'Meta': {'object_name': 'Config'}, |
324 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
325 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
326 | + 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'}) |
327 | + }, |
328 | + u'maasserver.dhcplease': { |
329 | + 'Meta': {'object_name': 'DHCPLease'}, |
330 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
331 | + 'ip': ('django.db.models.fields.IPAddressField', [], {'unique': 'True', 'max_length': '15'}), |
332 | + 'mac': ('maasserver.fields.MACAddressField', [], {}), |
333 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}) |
334 | + }, |
335 | + u'maasserver.filestorage': { |
336 | + 'Meta': {'object_name': 'FileStorage'}, |
337 | + 'content': ('metadataserver.fields.BinaryField', [], {}), |
338 | + 'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), |
339 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) |
340 | + }, |
341 | + u'maasserver.macaddress': { |
342 | + 'Meta': {'object_name': 'MACAddress'}, |
343 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
344 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
345 | + 'mac_address': ('maasserver.fields.MACAddressField', [], {'unique': 'True'}), |
346 | + 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}), |
347 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
348 | + }, |
349 | + u'maasserver.node': { |
350 | + 'Meta': {'object_name': 'Node'}, |
351 | + 'after_commissioning_action': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
352 | + 'architecture': ('django.db.models.fields.CharField', [], {'default': "u'i386/generic'", 'max_length': '31'}), |
353 | + 'cpu_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
354 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
355 | + 'distro_series': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '10', 'null': 'True', 'blank': 'True'}), |
356 | + 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
357 | + 'hardware_details': ('maasserver.fields.XMLField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), |
358 | + 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'unique': 'True', 'max_length': '255', 'blank': 'True'}), |
359 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
360 | + 'memory': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
361 | + 'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
362 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}), |
363 | + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), |
364 | + 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}), |
365 | + 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}), |
366 | + 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}), |
367 | + 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-763aa1fc-2412-11e2-be98-9c4e363b1c94'", 'unique': 'True', 'max_length': '41'}), |
368 | + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}), |
369 | + 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Token']", 'null': 'True'}), |
370 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
371 | + }, |
372 | + u'maasserver.nodegroup': { |
373 | + 'Meta': {'object_name': 'NodeGroup'}, |
374 | + 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}), |
375 | + 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Token']", 'unique': 'True'}), |
376 | + 'cluster_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100', 'blank': 'True'}), |
377 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
378 | + 'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
379 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
380 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}), |
381 | + 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
382 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}), |
383 | + 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'}) |
384 | + }, |
385 | + u'maasserver.nodegroupinterface': { |
386 | + 'Meta': {'unique_together': "((u'nodegroup', u'interface'),)", 'object_name': 'NodeGroupInterface'}, |
387 | + 'broadcast_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
388 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
389 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
390 | + 'interface': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
391 | + 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), |
392 | + 'ip_range_high': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
393 | + 'ip_range_low': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
394 | + 'management': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
395 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}), |
396 | + 'router_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
397 | + 'subnet_mask': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
398 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
399 | + }, |
400 | + u'maasserver.sshkey': { |
401 | + 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'}, |
402 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
403 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
404 | + 'key': ('django.db.models.fields.TextField', [], {}), |
405 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}), |
406 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) |
407 | + }, |
408 | + u'maasserver.tag': { |
409 | + 'Meta': {'object_name': 'Tag'}, |
410 | + 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}), |
411 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
412 | + 'definition': ('django.db.models.fields.TextField', [], {'blank': 'True'}), |
413 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
414 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}), |
415 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
416 | + }, |
417 | + u'maasserver.userprofile': { |
418 | + 'Meta': {'object_name': 'UserProfile'}, |
419 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
420 | + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) |
421 | + }, |
422 | + 'piston.consumer': { |
423 | + 'Meta': {'object_name': 'Consumer'}, |
424 | + 'description': ('django.db.models.fields.TextField', [], {}), |
425 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
426 | + 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), |
427 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
428 | + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), |
429 | + 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}), |
430 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': "orm['auth.User']"}) |
431 | + }, |
432 | + 'piston.token': { |
433 | + 'Meta': {'object_name': 'Token'}, |
434 | + 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), |
435 | + 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
436 | + 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Consumer']"}), |
437 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
438 | + 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
439 | + 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), |
440 | + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), |
441 | + 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1351767248L'}), |
442 | + 'token_type': ('django.db.models.fields.IntegerField', [], {}), |
443 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': "orm['auth.User']"}), |
444 | + 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'}) |
445 | + } |
446 | + } |
447 | + |
448 | + complete_apps = ['maasserver'] |
449 | \ No newline at end of file |
450 | |
451 | === modified file 'src/maasserver/models/node.py' |
452 | --- src/maasserver/models/node.py 2012-11-16 07:43:42 +0000 |
453 | +++ src/maasserver/models/node.py 2012-11-16 13:58:29 +0000 |
454 | @@ -428,6 +428,7 @@ |
455 | :ivar power_type: The :class:`POWER_TYPE` that determines how this |
456 | node will be powered on. If not given, the default will be used as |
457 | configured in the `node_power_type` setting. |
458 | + :ivar nodegroup: The `NodeGroup` this `Node` belongs to. |
459 | :ivar tags: The list of :class:`Tag`s associated with this `Node`. |
460 | :ivar objects: The :class:`NodeManager`. |
461 | |
462 | @@ -440,7 +441,7 @@ |
463 | max_length=41, unique=True, default=generate_node_system_id, |
464 | editable=False) |
465 | |
466 | - hostname = CharField(max_length=255, default='', blank=True) |
467 | + hostname = CharField(max_length=255, default='', blank=True, unique=True) |
468 | |
469 | status = IntegerField( |
470 | max_length=10, choices=NODE_STATUS_CHOICES, editable=False, |
471 | |
472 | === modified file 'src/maasserver/templates/maasserver/node_edit.html' |
473 | --- src/maasserver/templates/maasserver/node_edit.html 2012-07-10 16:03:27 +0000 |
474 | +++ src/maasserver/templates/maasserver/node_edit.html 2012-11-16 13:58:29 +0000 |
475 | @@ -30,6 +30,7 @@ |
476 | {% endblock %} |
477 | |
478 | {% block content %} |
479 | + <div id="node-edit" class="block size7"> |
480 | <form action="." method="post" class="block auto-width"> |
481 | {% csrf_token %} |
482 | <ul> |
483 | @@ -62,4 +63,5 @@ |
484 | <input type="submit" value="Save node" class="right" /> |
485 | <a class="link-button" href="{% url 'node-view' node.system_id %}">Cancel</a> |
486 | </form> |
487 | + </div> |
488 | {% endblock %} |
489 | |
490 | === modified file 'src/maasserver/templates/maasserver/node_view.html' |
491 | --- src/maasserver/templates/maasserver/node_view.html 2012-10-05 17:42:12 +0000 |
492 | +++ src/maasserver/templates/maasserver/node_view.html 2012-11-16 13:58:29 +0000 |
493 | @@ -1,8 +1,8 @@ |
494 | {% extends "maasserver/base.html" %} |
495 | |
496 | {% block nav-active-settings %}active{% endblock %} |
497 | -{% block title %}Node: {{ node.hostname }}{% endblock %} |
498 | -{% block page-title %}Node: {{ node.hostname }}{% endblock %} |
499 | +{% block title %}Node: {{ node.fqdn }}{% endblock %} |
500 | +{% block page-title %}Node: {{ node.fqdn }}{% endblock %} |
501 | {% block layout-modifiers %}sidebar{% endblock %} |
502 | |
503 | {% block sidebar %} |
504 | @@ -39,8 +39,8 @@ |
505 | {% block content %} |
506 | <ul class="data-list"> |
507 | <li class="block size3 first"> |
508 | - <h4>Hostname</h4> |
509 | - <span>{{ node.hostname }}</span> |
510 | + <h4><acronym title="Fully Qualified Domain Name">FQDN</acronym></h4> |
511 | + <span>{{ node.fqdn }}</span> |
512 | </li> |
513 | <li class="block size3"> |
514 | <h4>MAC addresses</h4> |
515 | |
516 | === modified file 'src/maasserver/templates/maasserver/nodes_listing.html' |
517 | --- src/maasserver/templates/maasserver/nodes_listing.html 2012-10-10 08:13:01 +0000 |
518 | +++ src/maasserver/templates/maasserver/nodes_listing.html 2012-11-16 13:58:29 +0000 |
519 | @@ -2,19 +2,22 @@ |
520 | <table class="list"> |
521 | <thead> |
522 | <tr> |
523 | - <th>MAC</th> |
524 | - <th>Status</th> |
525 | + <th><acronym title="Fully Qualified Domain Name">FQDN</acronym></th> |
526 | + <th><acronym |
527 | + title="Media Access Control addresses">MAC</acronym></th> |
528 | </tr> |
529 | </thead> |
530 | {% for node in node_list %} |
531 | <tr class="node {% cycle 'even' 'odd' %}"> |
532 | <td> |
533 | <a href="{% url 'node-view' node.system_id %}"> |
534 | - {% for macaddress in node.macaddress_set.all reversed %} |
535 | - {{ macaddress }}{% if not forloop.last %},{% endif %} |
536 | - {% endfor %} |
537 | + {{ node.fqdn }} |
538 | </a> |
539 | - ({{ node.hostname }}) |
540 | + </td> |
541 | + <td> |
542 | + {% for macaddress in node.macaddress_set.all reversed %} |
543 | + {{ macaddress }}{% if not forloop.last %},{% endif %} |
544 | + {% endfor %} |
545 | </td> |
546 | <td>{{ node.display_status }}</td> |
547 | </tr> |
548 | |
549 | === modified file 'src/maasserver/testing/factory.py' |
550 | --- src/maasserver/testing/factory.py 2012-11-08 09:14:58 +0000 |
551 | +++ src/maasserver/testing/factory.py 2012-11-16 13:58:29 +0000 |
552 | @@ -96,11 +96,11 @@ |
553 | finally: |
554 | NODE_TRANSITIONS[None] = valid_initial_states |
555 | |
556 | - def make_node(self, mac=False, hostname='', set_hostname=False, |
557 | - status=None, architecture=ARCHITECTURE.i386, updated=None, |
558 | + def make_node(self, mac=False, hostname=None, status=None, |
559 | + architecture=ARCHITECTURE.i386, updated=None, |
560 | created=None, nodegroup=None, **kwargs): |
561 | # hostname=None is a valid value, hence the set_hostname trick. |
562 | - if hostname is '' and set_hostname: |
563 | + if hostname is None: |
564 | hostname = self.getRandomString(20) |
565 | if status is None: |
566 | status = NODE_STATUS.DEFAULT_STATUS |
567 | |
568 | === modified file 'src/maasserver/tests/test_api.py' |
569 | --- src/maasserver/tests/test_api.py 2012-11-16 07:43:42 +0000 |
570 | +++ src/maasserver/tests/test_api.py 2012-11-16 13:58:29 +0000 |
571 | @@ -1170,7 +1170,7 @@ |
572 | |
573 | def test_GET_returns_node(self): |
574 | # The api allows for fetching a single Node (using system_id). |
575 | - node = factory.make_node(set_hostname=True) |
576 | + node = factory.make_node() |
577 | response = self.client.get(self.get_node_uri(node)) |
578 | |
579 | self.assertEqual(httplib.OK, response.status_code) |
580 | @@ -1179,7 +1179,7 @@ |
581 | self.assertEqual(node.system_id, parsed_result['system_id']) |
582 | |
583 | def test_GET_returns_associated_tag(self): |
584 | - node = factory.make_node(set_hostname=True) |
585 | + node = factory.make_node() |
586 | tag = factory.make_tag() |
587 | node.tags.add(tag) |
588 | response = self.client.get(self.get_node_uri(node)) |
589 | @@ -1424,6 +1424,15 @@ |
590 | self.assertEqual(0, Node.objects.filter(hostname='diane').count()) |
591 | self.assertEqual(1, Node.objects.filter(hostname='francis').count()) |
592 | |
593 | + def test_PUT_omitted_hostname(self): |
594 | + hostname = factory.make_name('hostname') |
595 | + node = factory.make_node(hostname=hostname, owner=self.logged_in_user) |
596 | + response = self.client.put( |
597 | + self.get_node_uri(node), |
598 | + {'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES)}) |
599 | + self.assertEqual(httplib.OK, response.status_code, response.content) |
600 | + self.assertTrue(Node.objects.filter(hostname=hostname).exists()) |
601 | + |
602 | def test_PUT_ignores_unknown_fields(self): |
603 | node = factory.make_node( |
604 | owner=self.logged_in_user, |
605 | @@ -1677,7 +1686,7 @@ |
606 | def test_DELETE_deletes_node(self): |
607 | # The api allows to delete a Node. |
608 | self.become_admin() |
609 | - node = factory.make_node(set_hostname=True, owner=self.logged_in_user) |
610 | + node = factory.make_node(owner=self.logged_in_user) |
611 | system_id = node.system_id |
612 | response = self.client.delete(self.get_node_uri(node)) |
613 | |
614 | @@ -1699,14 +1708,14 @@ |
615 | |
616 | def test_DELETE_deletes_node_fails_if_not_admin(self): |
617 | # Only superusers can delete nodes. |
618 | - node = factory.make_node(set_hostname=True, owner=self.logged_in_user) |
619 | + node = factory.make_node(owner=self.logged_in_user) |
620 | response = self.client.delete(self.get_node_uri(node)) |
621 | |
622 | self.assertEqual(httplib.FORBIDDEN, response.status_code) |
623 | |
624 | def test_DELETE_forbidden_without_edit_permission(self): |
625 | # A user without the edit permission cannot delete a Node. |
626 | - node = factory.make_node(set_hostname=True) |
627 | + node = factory.make_node() |
628 | response = self.client.delete(self.get_node_uri(node)) |
629 | |
630 | self.assertEqual(httplib.FORBIDDEN, response.status_code) |
631 | @@ -1771,8 +1780,7 @@ |
632 | # The api allows for fetching the list of Nodes. |
633 | node1 = factory.make_node() |
634 | node2 = factory.make_node( |
635 | - set_hostname=True, status=NODE_STATUS.ALLOCATED, |
636 | - owner=self.logged_in_user) |
637 | + status=NODE_STATUS.ALLOCATED, owner=self.logged_in_user) |
638 | response = self.client.get(self.get_uri('nodes/'), {'op': 'list'}) |
639 | parsed_result = json.loads(response.content) |
640 | |
641 | |
642 | === modified file 'src/maasserver/tests/test_dhcplease.py' |
643 | --- src/maasserver/tests/test_dhcplease.py 2012-10-30 15:15:30 +0000 |
644 | +++ src/maasserver/tests/test_dhcplease.py 2012-11-16 13:58:29 +0000 |
645 | @@ -168,7 +168,7 @@ |
646 | expected_mapping = {} |
647 | for i in range(3): |
648 | node = factory.make_node( |
649 | - nodegroup=nodegroup, set_hostname=True) |
650 | + nodegroup=nodegroup) |
651 | mac = factory.make_mac_address(node=node) |
652 | factory.make_mac_address(node=node) |
653 | lease = factory.make_dhcp_lease( |
654 | @@ -193,7 +193,7 @@ |
655 | def test_get_hostname_ip_mapping_considers_only_first_mac(self): |
656 | nodegroup = factory.make_node_group() |
657 | node = factory.make_node( |
658 | - nodegroup=nodegroup, set_hostname=True) |
659 | + nodegroup=nodegroup) |
660 | factory.make_mac_address(node=node) |
661 | second_mac = factory.make_mac_address(node=node) |
662 | # Create a lease for the second MAC Address. |
663 | @@ -205,7 +205,7 @@ |
664 | def test_get_hostname_ip_mapping_considers_given_nodegroup(self): |
665 | nodegroup = factory.make_node_group() |
666 | node = factory.make_node( |
667 | - nodegroup=nodegroup, set_hostname=True) |
668 | + nodegroup=nodegroup) |
669 | mac = factory.make_mac_address(node=node) |
670 | factory.make_dhcp_lease( |
671 | nodegroup=nodegroup, mac=mac.mac_address) |
672 | |
673 | === modified file 'src/maasserver/tests/test_dns.py' |
674 | --- src/maasserver/tests/test_dns.py 2012-10-11 01:17:24 +0000 |
675 | +++ src/maasserver/tests/test_dns.py 2012-11-16 13:58:29 +0000 |
676 | @@ -157,7 +157,7 @@ |
677 | management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS) |
678 | interface = nodegroup.get_managed_interface() |
679 | node = factory.make_node( |
680 | - nodegroup=nodegroup, set_hostname=True) |
681 | + nodegroup=nodegroup) |
682 | mac = factory.make_mac_address(node=node) |
683 | ips = IPRange(interface.ip_range_low, interface.ip_range_high) |
684 | lease_ip = str(islice(ips, lease_number, lease_number + 1).next()) |
685 | |
686 | === modified file 'src/maasserver/tests/test_forms.py' |
687 | --- src/maasserver/tests/test_forms.py 2012-11-09 18:12:36 +0000 |
688 | +++ src/maasserver/tests/test_forms.py 2012-11-16 13:58:29 +0000 |
689 | @@ -381,18 +381,6 @@ |
690 | form.save() |
691 | self.assertEqual(old_name, reload_object(node).hostname) |
692 | |
693 | - def test_AdminNodeForm_accepts_omitted_hostname_on_allocated_node(self): |
694 | - node = factory.make_node(status=NODE_STATUS.ALLOCATED) |
695 | - old_name = node.hostname |
696 | - form = AdminNodeForm( |
697 | - data={ |
698 | - 'architecture': node.architecture, |
699 | - }, |
700 | - instance=node) |
701 | - self.assertTrue(form.is_valid()) |
702 | - form.save() |
703 | - self.assertEqual(old_name, reload_object(node).hostname) |
704 | - |
705 | def test_remove_None_values_removes_None_values_in_dict(self): |
706 | random_input = factory.getRandomString() |
707 | self.assertEqual( |
708 | |
709 | === modified file 'src/maasserver/tests/test_node.py' |
710 | --- src/maasserver/tests/test_node.py 2012-11-16 07:43:42 +0000 |
711 | +++ src/maasserver/tests/test_node.py 2012-11-16 13:58:29 +0000 |
712 | @@ -313,7 +313,7 @@ |
713 | node: node.get_effective_power_type() |
714 | for node in nodes} |
715 | started_nodes = Node.objects.start_nodes( |
716 | - list(node_power_types.keys()), user) |
717 | + [node.system_id for node in list(node_power_types.keys())], user) |
718 | successful_types = [node_power_types[node] for node in started_nodes] |
719 | self.assertItemsEqual(configless_power_types, successful_types) |
720 | |
721 | @@ -636,8 +636,7 @@ |
722 | status = NODE_STATUS.READY |
723 | else: |
724 | status = NODE_STATUS.ALLOCATED |
725 | - return factory.make_node( |
726 | - set_hostname=True, status=status, owner=user, **kwargs) |
727 | + return factory.make_node(status=status, owner=user, **kwargs) |
728 | |
729 | def make_node_with_mac(self, user=None, **kwargs): |
730 | node = self.make_node(user, **kwargs) |
731 | |
732 | === modified file 'src/maasserver/tests/test_node_constraint_filter.py' |
733 | --- src/maasserver/tests/test_node_constraint_filter.py 2012-10-11 06:59:33 +0000 |
734 | +++ src/maasserver/tests/test_node_constraint_filter.py 2012-11-16 13:58:29 +0000 |
735 | @@ -60,8 +60,8 @@ |
736 | self.assertConstrainedNodes([node1, node2], {}) |
737 | |
738 | def test_hostname(self): |
739 | - node1 = factory.make_node(set_hostname=True) |
740 | - node2 = factory.make_node(set_hostname=True) |
741 | + node1 = factory.make_node() |
742 | + node2 = factory.make_node() |
743 | self.assertConstrainedNodes([node1], {'hostname': node1.hostname}) |
744 | self.assertConstrainedNodes([node2], {'hostname': node2.hostname}) |
745 | self.assertConstrainedNodes([], {'hostname': 'unknown-name'}) |
746 | |
747 | === modified file 'src/maasserver/tests/test_views_nodes.py' |
748 | --- src/maasserver/tests/test_views_nodes.py 2012-10-17 05:20:13 +0000 |
749 | +++ src/maasserver/tests/test_views_nodes.py 2012-11-16 13:58:29 +0000 |
750 | @@ -25,6 +25,8 @@ |
751 | ARCHITECTURE_CHOICES, |
752 | NODE_AFTER_COMMISSIONING_ACTION, |
753 | NODE_STATUS, |
754 | + NODEGROUP_STATUS, |
755 | + NODEGROUPINTERFACE_MANAGEMENT, |
756 | ) |
757 | from maasserver.exceptions import ( |
758 | InvalidConstraint, |
759 | @@ -79,6 +81,21 @@ |
760 | enlist_preseed_link = reverse('enlist-preseed-view') |
761 | self.assertIn(enlist_preseed_link, get_content_links(response)) |
762 | |
763 | + def test_node_list_displays_fqdn_dns_not_managed(self): |
764 | + nodes = [factory.make_node() for i in range(3)] |
765 | + response = self.client.get(reverse('node-list')) |
766 | + node_fqdns = [node.fqdn for node in nodes] |
767 | + self.assertThat(response.content, ContainsAll(node_fqdns)) |
768 | + |
769 | + def test_node_list_displays_fqdn_dns_managed(self): |
770 | + nodegroup = factory.make_node_group( |
771 | + status=NODEGROUP_STATUS.ACCEPTED, |
772 | + management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS) |
773 | + nodes = [factory.make_node(nodegroup=nodegroup) for i in range(3)] |
774 | + response = self.client.get(reverse('node-list')) |
775 | + node_fqdns = [node.fqdn for node in nodes] |
776 | + self.assertThat(response.content, ContainsAll(node_fqdns)) |
777 | + |
778 | def test_node_list_displays_sorted_list_of_nodes(self): |
779 | # Nodes are sorted on the node list page, newest first. |
780 | nodes = [factory.make_node() for i in range(3)] |
781 | @@ -493,7 +510,7 @@ |
782 | def test_preseedview_node_displays_message_if_commissioning(self): |
783 | node = factory.make_node( |
784 | owner=self.logged_in_user, status=NODE_STATUS.COMMISSIONING, |
785 | - set_hostname=True) |
786 | + ) |
787 | node_preseed_link = reverse('node-preseed-view', args=[node.system_id]) |
788 | response = self.client.get(node_preseed_link) |
789 | self.assertThat( |
790 | |
791 | === modified file 'src/maasserver/tests/test_views_tags.py' |
792 | --- src/maasserver/tests/test_views_tags.py 2012-11-07 11:32:16 +0000 |
793 | +++ src/maasserver/tests/test_views_tags.py 2012-11-16 13:58:29 +0000 |
794 | @@ -15,15 +15,11 @@ |
795 | from django.core.urlresolvers import reverse |
796 | from lxml.etree import XPath |
797 | from lxml.html import fromstring |
798 | -from maastesting.matchers import ContainsAll |
799 | -from maasserver.testing import ( |
800 | - get_content_links, |
801 | - ) |
802 | +from maasserver.testing import get_content_links |
803 | from maasserver.testing.factory import factory |
804 | -from maasserver.testing.testcase import ( |
805 | - LoggedInTestCase, |
806 | - ) |
807 | +from maasserver.testing.testcase import LoggedInTestCase |
808 | from maasserver.views import tags as tags_views |
809 | +from maastesting.matchers import ContainsAll |
810 | |
811 | |
812 | class TagViewsTest(LoggedInTestCase): |
813 | @@ -37,12 +33,12 @@ |
814 | response = self.client.get(tag_link) |
815 | doc = fromstring(response.content) |
816 | content_text = doc.cssselect('#content')[0].text_content() |
817 | - self.assertThat(content_text, |
818 | - ContainsAll([tag.comment, tag.definition])) |
819 | + self.assertThat( |
820 | + content_text, ContainsAll([tag.comment, tag.definition])) |
821 | |
822 | def test_view_tag_includes_node_links(self): |
823 | tag = factory.make_tag() |
824 | - node = factory.make_node(set_hostname=True) |
825 | + node = factory.make_node() |
826 | node.tags.add(tag) |
827 | mac = factory.make_mac_address(node=node).mac_address |
828 | tag_link = reverse('tag-view', args=[tag.name]) |
829 | @@ -50,8 +46,8 @@ |
830 | response = self.client.get(tag_link) |
831 | doc = fromstring(response.content) |
832 | content_text = doc.cssselect('#content')[0].text_content() |
833 | - self.assertThat(content_text, |
834 | - ContainsAll([mac, '(%s)' % node.hostname])) |
835 | + self.assertThat( |
836 | + content_text, ContainsAll([mac, '%s' % node.hostname])) |
837 | self.assertNotIn(node.system_id, content_text) |
838 | self.assertIn(node_link, get_content_links(response)) |
839 | |
840 | @@ -82,8 +78,8 @@ |
841 | |
842 | def test_view_tag_hides_private_nodes(self): |
843 | tag = factory.make_tag() |
844 | - node = factory.make_node(set_hostname=True) |
845 | - node2 = factory.make_node(owner=factory.make_user(), set_hostname=True) |
846 | + node = factory.make_node() |
847 | + node2 = factory.make_node(owner=factory.make_user()) |
848 | node.tags.add(tag) |
849 | node2.tags.add(tag) |
850 | tag_link = reverse('tag-view', args=[tag.name]) |
851 | |
852 | === modified file 'src/maasserver/views/nodes.py' |
853 | --- src/maasserver/views/nodes.py 2012-10-17 05:20:13 +0000 |
854 | +++ src/maasserver/views/nodes.py 2012-11-16 13:58:29 +0000 |
855 | @@ -123,6 +123,8 @@ |
856 | except InvalidConstraint as e: |
857 | self.query_error = e |
858 | return Node.objects.none() |
859 | + nodes = nodes.prefetch_related('nodegroup') |
860 | + nodes = nodes.prefetch_related('nodegroup__nodegroupinterface_set') |
861 | return nodes |
862 | |
863 | def get_context_data(self, **kwargs): |
864 | |
865 | === modified file 'src/maasserver/views/tags.py' |
866 | --- src/maasserver/views/tags.py 2012-11-07 11:32:16 +0000 |
867 | +++ src/maasserver/views/tags.py 2012-11-16 13:58:29 +0000 |
868 | @@ -33,9 +33,12 @@ |
869 | return super(TagView, self).get(request, *args, **kwargs) |
870 | |
871 | def get_queryset(self): |
872 | - return Tag.objects.get_nodes( |
873 | + nodes = Tag.objects.get_nodes( |
874 | self.tag, user=self.request.user, prefetch_mac=True, |
875 | ).order_by('-created') |
876 | + nodes = nodes.prefetch_related('nodegroup') |
877 | + nodes = nodes.prefetch_related('nodegroup__nodegroupinterface_set') |
878 | + return nodes |
879 | |
880 | def get_context_data(self, **kwargs): |
881 | context = super(TagView, self).get_context_data(**kwargs) |
This is a backport of 1335 (and the required db migration merged in revision 1330). Self-approving.