Merge lp:~julian-edwards/maas/node_group_model into lp:~maas-committers/maas/trunk
- node_group_model
- Merge into trunk
Proposed by
Julian Edwards
Status: | Merged |
---|---|
Approved by: | Julian Edwards |
Approved revision: | no longer in the source branch. |
Merged at revision: | 607 |
Proposed branch: | lp:~julian-edwards/maas/node_group_model |
Merge into: | lp:~maas-committers/maas/trunk |
Diff against target: |
338 lines (+282/-0) 5 files modified
src/maasserver/migrations/0009_add_nodegroup.py (+166/-0) src/maasserver/models/__init__.py (+3/-0) src/maasserver/models/nodegroup.py (+63/-0) src/maasserver/testing/factory.py (+11/-0) src/maasserver/tests/test_nodegroup.py (+39/-0) |
To merge this branch: | bzr merge lp:~julian-edwards/maas/node_group_model |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jeroen T. Vermeulen (community) | Approve | ||
Review via email: mp+108678@code.launchpad.net |
Commit message
Add NodeGroup model, which stores DHCP parameters and the worker API key.
Description of the change
This is a basic implementation of a NodeGroup schema. It is required now because we need to store DHCP parameters on a per-nodegroup basis, so it makes sense to define the schema now.
To post a comment you must log in.
Revision history for this message
Raphaël Badin (rvb) wrote : | # |
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote : | # |
As discussed: better not export the manager class from the model module — it's nice to have a single canonical way to address the manager, as <model>.objects.
review:
Approve
Revision history for this message
Julian Edwards (julian-edwards) wrote : | # |
Raphers, I left the Manager in because it is defined in the template. I expect we'll need one at some stage so it seems strange to delete it and re-add it later.
Thank you for reviewing to both.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added file 'src/maasserver/migrations/0009_add_nodegroup.py' |
2 | --- src/maasserver/migrations/0009_add_nodegroup.py 1970-01-01 00:00:00 +0000 |
3 | +++ src/maasserver/migrations/0009_add_nodegroup.py 2012-06-05 07:13:22 +0000 |
4 | @@ -0,0 +1,166 @@ |
5 | +# flake8: noqa |
6 | +# SKIP this file when reformatting. |
7 | +# The rest of this file was generated by South. |
8 | + |
9 | +# encoding: utf-8 |
10 | +import datetime |
11 | +from south.db import db |
12 | +from south.v2 import SchemaMigration |
13 | +from django.db import models |
14 | + |
15 | +class Migration(SchemaMigration): |
16 | + |
17 | + def forwards(self, orm): |
18 | + |
19 | + # Adding model 'NodeGroup' |
20 | + db.create_table(u'maasserver_nodegroup', ( |
21 | + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), |
22 | + ('created', self.gf('django.db.models.fields.DateTimeField')()), |
23 | + ('updated', self.gf('django.db.models.fields.DateTimeField')()), |
24 | + ('name', self.gf('django.db.models.fields.CharField')(default=u'', unique=True, max_length=80)), |
25 | + ('api_token', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['piston.Token'], unique=True)), |
26 | + ('api_key', self.gf('django.db.models.fields.CharField')(unique=True, max_length=18)), |
27 | + ('worker_ip', self.gf('django.db.models.fields.IPAddressField')(unique=True, max_length=15)), |
28 | + ('subnet_mask', self.gf('django.db.models.fields.IPAddressField')(max_length=15)), |
29 | + ('broadcast_ip', self.gf('django.db.models.fields.IPAddressField')(max_length=15)), |
30 | + ('router_ip', self.gf('django.db.models.fields.IPAddressField')(max_length=15)), |
31 | + ('ip_range_low', self.gf('django.db.models.fields.IPAddressField')(unique=True, max_length=15)), |
32 | + ('ip_range_high', self.gf('django.db.models.fields.IPAddressField')(unique=True, max_length=15)), |
33 | + )) |
34 | + db.send_create_signal(u'maasserver', ['NodeGroup']) |
35 | + |
36 | + |
37 | + def backwards(self, orm): |
38 | + |
39 | + # Deleting model 'NodeGroup' |
40 | + db.delete_table(u'maasserver_nodegroup') |
41 | + |
42 | + |
43 | + models = { |
44 | + 'auth.group': { |
45 | + 'Meta': {'object_name': 'Group'}, |
46 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
47 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), |
48 | + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) |
49 | + }, |
50 | + 'auth.permission': { |
51 | + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, |
52 | + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
53 | + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), |
54 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
55 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) |
56 | + }, |
57 | + 'auth.user': { |
58 | + 'Meta': {'object_name': 'User'}, |
59 | + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
60 | + 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}), |
61 | + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
62 | + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), |
63 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
64 | + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
65 | + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
66 | + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
67 | + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
68 | + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
69 | + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), |
70 | + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), |
71 | + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) |
72 | + }, |
73 | + 'contenttypes.contenttype': { |
74 | + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, |
75 | + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
76 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
77 | + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
78 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) |
79 | + }, |
80 | + u'maasserver.config': { |
81 | + 'Meta': {'object_name': 'Config'}, |
82 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
83 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
84 | + 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'}) |
85 | + }, |
86 | + u'maasserver.filestorage': { |
87 | + 'Meta': {'object_name': 'FileStorage'}, |
88 | + 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '255'}), |
89 | + 'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'}), |
90 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) |
91 | + }, |
92 | + u'maasserver.macaddress': { |
93 | + 'Meta': {'object_name': 'MACAddress'}, |
94 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
95 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
96 | + 'mac_address': ('maasserver.fields.MACAddressField', [], {'unique': 'True'}), |
97 | + 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}), |
98 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
99 | + }, |
100 | + u'maasserver.node': { |
101 | + 'Meta': {'object_name': 'Node'}, |
102 | + 'after_commissioning_action': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
103 | + 'architecture': ('django.db.models.fields.CharField', [], {'default': "u'i386'", 'max_length': '10'}), |
104 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
105 | + 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
106 | + 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
107 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
108 | + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), |
109 | + 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}), |
110 | + 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}), |
111 | + 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}), |
112 | + 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-e3dbb694-aed1-11e1-84fa-002215205ce8'", 'unique': 'True', 'max_length': '41'}), |
113 | + 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Token']", 'null': 'True'}), |
114 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
115 | + }, |
116 | + u'maasserver.nodegroup': { |
117 | + 'Meta': {'object_name': 'NodeGroup'}, |
118 | + 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}), |
119 | + 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Token']", 'unique': 'True'}), |
120 | + 'broadcast_ip': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}), |
121 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
122 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
123 | + 'ip_range_high': ('django.db.models.fields.IPAddressField', [], {'unique': 'True', 'max_length': '15'}), |
124 | + 'ip_range_low': ('django.db.models.fields.IPAddressField', [], {'unique': 'True', 'max_length': '15'}), |
125 | + 'name': ('django.db.models.fields.CharField', [], {'default': "u''", 'unique': 'True', 'max_length': '80'}), |
126 | + 'router_ip': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}), |
127 | + 'subnet_mask': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}), |
128 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}), |
129 | + 'worker_ip': ('django.db.models.fields.IPAddressField', [], {'unique': 'True', 'max_length': '15'}) |
130 | + }, |
131 | + u'maasserver.sshkey': { |
132 | + 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'}, |
133 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
134 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
135 | + 'key': ('django.db.models.fields.TextField', [], {}), |
136 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}), |
137 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) |
138 | + }, |
139 | + u'maasserver.userprofile': { |
140 | + 'Meta': {'object_name': 'UserProfile'}, |
141 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
142 | + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) |
143 | + }, |
144 | + 'piston.consumer': { |
145 | + 'Meta': {'object_name': 'Consumer'}, |
146 | + 'description': ('django.db.models.fields.TextField', [], {}), |
147 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
148 | + 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), |
149 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
150 | + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), |
151 | + 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}), |
152 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': "orm['auth.User']"}) |
153 | + }, |
154 | + 'piston.token': { |
155 | + 'Meta': {'object_name': 'Token'}, |
156 | + 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), |
157 | + 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
158 | + 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Consumer']"}), |
159 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
160 | + 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
161 | + 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), |
162 | + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), |
163 | + 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1338875229L'}), |
164 | + 'token_type': ('django.db.models.fields.IntegerField', [], {}), |
165 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': "orm['auth.User']"}), |
166 | + 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'}) |
167 | + } |
168 | + } |
169 | + |
170 | + complete_apps = ['maasserver'] |
171 | |
172 | === modified file 'src/maasserver/models/__init__.py' |
173 | --- src/maasserver/models/__init__.py 2012-05-31 20:51:10 +0000 |
174 | +++ src/maasserver/models/__init__.py 2012-06-05 07:13:22 +0000 |
175 | @@ -24,6 +24,7 @@ |
176 | "FileStorage", |
177 | "NODE_TRANSITIONS", |
178 | "Node", |
179 | + "NodeGroup", |
180 | "MACAddress", |
181 | "SSHKey", |
182 | "UserProfile", |
183 | @@ -72,6 +73,8 @@ |
184 | from maasserver.models.cleansave import CleanSave |
185 | from maasserver.models.config import Config |
186 | from maasserver.models.filestorage import FileStorage |
187 | +from maasserver.models.nodegroup import NodeGroup |
188 | +NodeGroup |
189 | from maasserver.models.sshkey import SSHKey |
190 | from maasserver.models.timestampedmodel import TimestampedModel |
191 | from maasserver.models.userprofile import UserProfile |
192 | |
193 | === added file 'src/maasserver/models/nodegroup.py' |
194 | --- src/maasserver/models/nodegroup.py 1970-01-01 00:00:00 +0000 |
195 | +++ src/maasserver/models/nodegroup.py 2012-06-05 07:13:22 +0000 |
196 | @@ -0,0 +1,63 @@ |
197 | +# Copyright 2012 Canonical Ltd. This software is licensed under the |
198 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
199 | + |
200 | +"""Model definition for NodeGroup which models a collection of Nodes.""" |
201 | + |
202 | +from __future__ import ( |
203 | + absolute_import, |
204 | + print_function, |
205 | + unicode_literals, |
206 | + ) |
207 | + |
208 | +__metaclass__ = type |
209 | +__all__ = [ |
210 | + 'NodeGroup', |
211 | + ] |
212 | + |
213 | + |
214 | +from django.db.models import ( |
215 | + CharField, |
216 | + ForeignKey, |
217 | + IPAddressField, |
218 | + Manager, |
219 | + ) |
220 | +from maasserver import DefaultMeta |
221 | +from maasserver.models.timestampedmodel import TimestampedModel |
222 | +from piston.models import ( |
223 | + KEY_SIZE, |
224 | + Token, |
225 | + ) |
226 | + |
227 | + |
228 | +class NodeGroupManager(Manager): |
229 | + """Manager for the NodeGroup class. |
230 | + |
231 | + Don't import or instantiate this directly; access as `<Class>.objects` on |
232 | + the model class it manages. |
233 | + """ |
234 | + |
235 | + |
236 | +class NodeGroup(TimestampedModel): |
237 | + |
238 | + class Meta(DefaultMeta): |
239 | + """Needed for South to recognize this model.""" |
240 | + |
241 | + objects = NodeGroupManager() |
242 | + |
243 | + name = CharField( |
244 | + max_length=80, unique=True, editable=True, default="") |
245 | + |
246 | + api_token = ForeignKey(Token, null=False, editable=False, unique=True) |
247 | + api_key = CharField( |
248 | + max_length=KEY_SIZE, null=False, editable=False, unique=True) |
249 | + |
250 | + worker_ip = IPAddressField(null=False, editable=True, unique=True) |
251 | + |
252 | + subnet_mask = IPAddressField(null=False, editable=True, unique=False) |
253 | + |
254 | + broadcast_ip = IPAddressField(null=False, editable=True, unique=False) |
255 | + |
256 | + router_ip = IPAddressField(null=False, editable=True, unique=False) |
257 | + |
258 | + ip_range_low = IPAddressField(null=False, editable=True, unique=True) |
259 | + ip_range_high = IPAddressField(null=False, editable=True, unique=True) |
260 | |
261 | === modified file 'src/maasserver/testing/factory.py' |
262 | --- src/maasserver/testing/factory.py 2012-05-23 16:32:15 +0000 |
263 | +++ src/maasserver/testing/factory.py 2012-06-05 07:13:22 +0000 |
264 | @@ -24,12 +24,15 @@ |
265 | NODE_STATUS, |
266 | ) |
267 | from maasserver.models import ( |
268 | + create_auth_token, |
269 | FileStorage, |
270 | MACAddress, |
271 | Node, |
272 | + NodeGroup, |
273 | NODE_TRANSITIONS, |
274 | SSHKey, |
275 | ) |
276 | +from maasserver.models.nodegroup import NodeGroup |
277 | from maasserver.testing import ( |
278 | get_data, |
279 | reload_object, |
280 | @@ -105,6 +108,14 @@ |
281 | Node.objects.filter(id=node.id).update(created=created) |
282 | return reload_object(node) |
283 | |
284 | + def make_node_group(self, api_token=None, **kwargs): |
285 | + if api_token is None: |
286 | + user = self.make_user() |
287 | + api_token = create_auth_token(user) |
288 | + ng = NodeGroup(api_token=api_token, api_key=api_token.key, **kwargs) |
289 | + ng.save() |
290 | + return ng |
291 | + |
292 | def make_node_commission_result(self, node=None, name=None, data=None): |
293 | if node is None: |
294 | node = self.make_node() |
295 | |
296 | === added file 'src/maasserver/tests/test_nodegroup.py' |
297 | --- src/maasserver/tests/test_nodegroup.py 1970-01-01 00:00:00 +0000 |
298 | +++ src/maasserver/tests/test_nodegroup.py 2012-06-05 07:13:22 +0000 |
299 | @@ -0,0 +1,39 @@ |
300 | +# Copyright 2012 Canonical Ltd. This software is licensed under the |
301 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
302 | + |
303 | +"""Tests for the NodeGroup model.""" |
304 | + |
305 | +from __future__ import ( |
306 | + absolute_import, |
307 | + print_function, |
308 | + unicode_literals, |
309 | + ) |
310 | + |
311 | +__metaclass__ = type |
312 | +__all__ = [] |
313 | + |
314 | +from testtools.matchers import MatchesStructure |
315 | + |
316 | +from maasserver.testing.factory import factory |
317 | +from maastesting.testcase import TestCase |
318 | + |
319 | + |
320 | +class TestNodeGroup(TestCase): |
321 | + |
322 | + def test_model_stores_to_database(self): |
323 | + ng = factory.make_node_group( |
324 | + name=factory.getRandomString(80), |
325 | + worker_ip="10.1.1.1", |
326 | + subnet_mask="255.0.0.0", |
327 | + broadcast_ip="10.255.255.255", |
328 | + router_ip="10.1.1.254", |
329 | + ip_range_low="10.1.1.2", |
330 | + ip_range_high="10.254.254.253") |
331 | + self.assertThat( |
332 | + ng, MatchesStructure.byEquality( |
333 | + worker_ip="10.1.1.1", |
334 | + subnet_mask="255.0.0.0", |
335 | + broadcast_ip="10.255.255.255", |
336 | + router_ip="10.1.1.254", |
337 | + ip_range_low="10.1.1.2", |
338 | + ip_range_high="10.254.254.253")) |
Not a proper review, just a drive-by comment:
229 +class NodeGroupManage r(Manager) :
230 + """Manager for the NodeGroup class.
231 +
232 + Don't import or instantiate this directly; access as `<Class>.objects` on
233 + the model class it manages.
234 + """
235 +
236 +
[...]
241 +
242 + objects = NodeGroupManager()
Unless you've got a followup branch that introduces methods in NodeGroupManager, I suggest you get rid of this custom (and empty) manager for now. Django will create one for us.