Merge lp:~blake-rouse/maas/global-licensekey-model into lp:~maas-committers/maas/trunk
- global-licensekey-model
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Blake Rouse | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 2481 | ||||
Proposed branch: | lp:~blake-rouse/maas/global-licensekey-model | ||||
Merge into: | lp:~maas-committers/maas/trunk | ||||
Diff against target: |
530 lines (+469/-3) 5 files modified
src/maasserver/migrations/0087_add_licensekey_model.py (+319/-0) src/maasserver/models/__init__.py (+5/-3) src/maasserver/models/licensekey.py (+83/-0) src/maasserver/models/tests/test_licensekey.py (+48/-0) src/maasserver/testing/factory.py (+14/-0) |
||||
To merge this branch: | bzr merge lp:~blake-rouse/maas/global-licensekey-model | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Raphaël Badin (community) | Approve | ||
Review via email: mp+224284@code.launchpad.net |
Commit message
Add LicenseKey model to support global license keys.
Description of the change
This is the first part of a series of changes to allow a global registry of license key's in MAAS. The end result will allow a user to set a license key for each operating system and series globally. When a booting operating system and series requires a license key, first the node will be check to see if a specific key has been set in the license_key field. If not then this LicenseKey model will be used to see if one is set globally for the selected operating system and series.
This branch includes only the new model and migration. Later branches will contain logic, api calls, and UI.
Blake Rouse (blake-rouse) : | # |
MAAS Lander (maas-lander) wrote : | # |
The attempt to merge lp:~blake-rouse/maas/global-licensekey-model into lp:maas failed. Below is the output from the failed tests.
Ign http://
Get:1 http://
Ign http://
Get:2 http://
Ign http://
Hit http://
Get:3 http://
Hit http://
Get:4 http://
Get:5 http://
Hit http://
Get:6 http://
Get:7 http://
Hit http://
Hit http://
Hit http://
Hit http://
Hit http://
Get:8 http://
Hit http://
Hit http://
Ign http://
Ign http://
Ign http://
Ign http://
Get:9 http://
Get:10 http://
Get:11 http://
Get:12 http://
Get:13 http://
Hit http://
Ign http://
Ign http://
Fetched 848 kB in 0s (1,944 kB/s)
Reading package lists...
sudo DEBIAN_
--
Preview Diff
1 | === added file 'src/maasserver/migrations/0087_add_licensekey_model.py' |
2 | --- src/maasserver/migrations/0087_add_licensekey_model.py 1970-01-01 00:00:00 +0000 |
3 | +++ src/maasserver/migrations/0087_add_licensekey_model.py 2014-06-26 14:09:35 +0000 |
4 | @@ -0,0 +1,319 @@ |
5 | +from django.db import models |
6 | +from south.db import db |
7 | +# -*- coding: utf-8 -*- |
8 | +from south.utils import datetime_utils as datetime |
9 | +from south.v2 import SchemaMigration |
10 | + |
11 | + |
12 | +class Migration(SchemaMigration): |
13 | + |
14 | + def forwards(self, orm): |
15 | + # Adding model 'LicenseKey' |
16 | + db.create_table(u'maasserver_licensekey', ( |
17 | + (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), |
18 | + ('created', self.gf('django.db.models.fields.DateTimeField')()), |
19 | + ('updated', self.gf('django.db.models.fields.DateTimeField')()), |
20 | + ('osystem', self.gf('django.db.models.fields.CharField')(max_length=255)), |
21 | + ('distro_series', self.gf('django.db.models.fields.CharField')(max_length=255)), |
22 | + ('license_key', self.gf('django.db.models.fields.CharField')(max_length=255)), |
23 | + )) |
24 | + db.send_create_signal(u'maasserver', ['LicenseKey']) |
25 | + |
26 | + # Adding unique constraint on 'LicenseKey', fields ['osystem', 'distro_series'] |
27 | + db.create_unique(u'maasserver_licensekey', ['osystem', 'distro_series']) |
28 | + |
29 | + |
30 | + def backwards(self, orm): |
31 | + # Removing unique constraint on 'LicenseKey', fields ['osystem', 'distro_series'] |
32 | + db.delete_unique(u'maasserver_licensekey', ['osystem', 'distro_series']) |
33 | + |
34 | + # Deleting model 'LicenseKey' |
35 | + db.delete_table(u'maasserver_licensekey') |
36 | + |
37 | + |
38 | + models = { |
39 | + u'auth.group': { |
40 | + 'Meta': {'object_name': 'Group'}, |
41 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
42 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), |
43 | + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) |
44 | + }, |
45 | + u'auth.permission': { |
46 | + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, |
47 | + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
48 | + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), |
49 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
50 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) |
51 | + }, |
52 | + u'auth.user': { |
53 | + 'Meta': {'object_name': 'User'}, |
54 | + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
55 | + 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}), |
56 | + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
57 | + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), |
58 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
59 | + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
60 | + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
61 | + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
62 | + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
63 | + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
64 | + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), |
65 | + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), |
66 | + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) |
67 | + }, |
68 | + u'contenttypes.contenttype': { |
69 | + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, |
70 | + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
71 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
72 | + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
73 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) |
74 | + }, |
75 | + u'maasserver.bootimage': { |
76 | + 'Meta': {'unique_together': "((u'nodegroup', u'osystem', u'architecture', u'subarchitecture', u'release', u'purpose', u'label'),)", 'object_name': 'BootImage'}, |
77 | + 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
78 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
79 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
80 | + 'label': ('django.db.models.fields.CharField', [], {'default': "u'release'", 'max_length': '255'}), |
81 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}), |
82 | + 'osystem': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
83 | + 'purpose': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
84 | + 'release': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
85 | + 'subarchitecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
86 | + 'supported_subarches': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
87 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}), |
88 | + 'xinstall_path': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'null': 'True', 'blank': 'True'}), |
89 | + 'xinstall_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '30', 'null': 'True', 'blank': 'True'}) |
90 | + }, |
91 | + u'maasserver.bootsource': { |
92 | + 'Meta': {'object_name': 'BootSource'}, |
93 | + 'cluster': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}), |
94 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
95 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
96 | + 'keyring_data': ('maasserver.fields.EditableBinaryField', [], {'blank': 'True'}), |
97 | + 'keyring_filename': ('django.db.models.fields.FilePathField', [], {'max_length': '100', 'blank': 'True'}), |
98 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}), |
99 | + 'url': ('django.db.models.fields.URLField', [], {'max_length': '200'}) |
100 | + }, |
101 | + u'maasserver.bootsourceselection': { |
102 | + 'Meta': {'object_name': 'BootSourceSelection'}, |
103 | + 'arches': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}), |
104 | + 'boot_source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.BootSource']"}), |
105 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
106 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
107 | + 'labels': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}), |
108 | + 'release': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}), |
109 | + 'subarches': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'text'", 'null': 'True', 'blank': 'True'}), |
110 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
111 | + }, |
112 | + u'maasserver.componenterror': { |
113 | + 'Meta': {'object_name': 'ComponentError'}, |
114 | + 'component': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}), |
115 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
116 | + 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000'}), |
117 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
118 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
119 | + }, |
120 | + u'maasserver.config': { |
121 | + 'Meta': {'object_name': 'Config'}, |
122 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
123 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
124 | + 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'}) |
125 | + }, |
126 | + u'maasserver.dhcplease': { |
127 | + 'Meta': {'object_name': 'DHCPLease'}, |
128 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
129 | + 'ip': ('django.db.models.fields.IPAddressField', [], {'unique': 'True', 'max_length': '15'}), |
130 | + 'mac': ('maasserver.fields.MACAddressField', [], {}), |
131 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}) |
132 | + }, |
133 | + u'maasserver.downloadprogress': { |
134 | + 'Meta': {'object_name': 'DownloadProgress'}, |
135 | + 'bytes_downloaded': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), |
136 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
137 | + 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000', 'blank': 'True'}), |
138 | + 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
139 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
140 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}), |
141 | + 'size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), |
142 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
143 | + }, |
144 | + u'maasserver.filestorage': { |
145 | + 'Meta': {'unique_together': "((u'filename', u'owner'),)", 'object_name': 'FileStorage'}, |
146 | + 'content': ('metadataserver.fields.BinaryField', [], {'blank': 'True'}), |
147 | + 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
148 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
149 | + 'key': ('django.db.models.fields.CharField', [], {'default': "u'6ff1517c-fd34-11e3-98c5-bcee7b78dc5b'", 'unique': 'True', 'max_length': '36'}), |
150 | + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}) |
151 | + }, |
152 | + u'maasserver.licensekey': { |
153 | + 'Meta': {'unique_together': "((u'osystem', u'distro_series'),)", 'object_name': 'LicenseKey'}, |
154 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
155 | + 'distro_series': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
156 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
157 | + 'license_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
158 | + 'osystem': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
159 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
160 | + }, |
161 | + u'maasserver.macaddress': { |
162 | + 'Meta': {'object_name': 'MACAddress'}, |
163 | + 'cluster_interface': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['maasserver.NodeGroupInterface']", 'null': 'True', 'blank': 'True'}), |
164 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
165 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
166 | + 'ip_addresses': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.StaticIPAddress']", 'symmetrical': 'False', 'through': u"orm['maasserver.MACStaticIPAddressLink']", 'blank': 'True'}), |
167 | + 'mac_address': ('maasserver.fields.MACAddressField', [], {'unique': 'True'}), |
168 | + 'networks': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Network']", 'symmetrical': 'False', 'blank': 'True'}), |
169 | + 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}), |
170 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
171 | + }, |
172 | + u'maasserver.macstaticipaddresslink': { |
173 | + 'Meta': {'unique_together': "((u'ip_address', u'mac_address'),)", 'object_name': 'MACStaticIPAddressLink'}, |
174 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
175 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
176 | + 'ip_address': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.StaticIPAddress']", 'unique': 'True'}), |
177 | + 'mac_address': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.MACAddress']"}), |
178 | + 'nic_alias': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), |
179 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
180 | + }, |
181 | + u'maasserver.network': { |
182 | + 'Meta': {'object_name': 'Network'}, |
183 | + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), |
184 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
185 | + 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'unique': 'True', 'max_length': '39'}), |
186 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), |
187 | + 'netmask': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), |
188 | + 'vlan_tag': ('django.db.models.fields.PositiveSmallIntegerField', [], {'unique': 'True', 'null': 'True', 'blank': 'True'}) |
189 | + }, |
190 | + u'maasserver.node': { |
191 | + 'Meta': {'object_name': 'Node'}, |
192 | + 'agent_name': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'null': 'True', 'blank': 'True'}), |
193 | + 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '31'}), |
194 | + 'cpu_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
195 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
196 | + 'distro_series': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}), |
197 | + 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
198 | + 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'unique': 'True', 'max_length': '255', 'blank': 'True'}), |
199 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
200 | + 'license_key': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}), |
201 | + 'memory': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
202 | + 'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
203 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}), |
204 | + 'osystem': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '20', 'blank': 'True'}), |
205 | + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}), |
206 | + 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}), |
207 | + 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}), |
208 | + 'routers': ('djorm_pgarray.fields.ArrayField', [], {'default': 'None', 'dbtype': "u'macaddr'", 'null': 'True', 'blank': 'True'}), |
209 | + 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}), |
210 | + 'storage': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
211 | + 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-6ff0a98e-fd34-11e3-98c5-bcee7b78dc5b'", 'unique': 'True', 'max_length': '41'}), |
212 | + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}), |
213 | + 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'null': 'True'}), |
214 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}), |
215 | + 'zone': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Zone']", 'on_delete': 'models.SET_DEFAULT'}) |
216 | + }, |
217 | + u'maasserver.nodegroup': { |
218 | + 'Meta': {'object_name': 'NodeGroup'}, |
219 | + 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}), |
220 | + 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Token']", 'unique': 'True'}), |
221 | + 'cluster_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100', 'blank': 'True'}), |
222 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
223 | + 'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
224 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
225 | + 'maas_url': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
226 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}), |
227 | + 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
228 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}), |
229 | + 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'}) |
230 | + }, |
231 | + u'maasserver.nodegroupinterface': { |
232 | + 'Meta': {'unique_together': "((u'nodegroup', u'interface'),)", 'object_name': 'NodeGroupInterface'}, |
233 | + 'broadcast_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
234 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
235 | + 'foreign_dhcp_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
236 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
237 | + 'interface': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
238 | + 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), |
239 | + 'ip_range_high': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
240 | + 'ip_range_low': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
241 | + 'management': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
242 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}), |
243 | + 'router_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
244 | + 'static_ip_range_high': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
245 | + 'static_ip_range_low': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
246 | + 'subnet_mask': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
247 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
248 | + }, |
249 | + u'maasserver.sshkey': { |
250 | + 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'}, |
251 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
252 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
253 | + 'key': ('django.db.models.fields.TextField', [], {}), |
254 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}), |
255 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}) |
256 | + }, |
257 | + u'maasserver.sslkey': { |
258 | + 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSLKey'}, |
259 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
260 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
261 | + 'key': ('django.db.models.fields.TextField', [], {}), |
262 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}), |
263 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}) |
264 | + }, |
265 | + u'maasserver.staticipaddress': { |
266 | + 'Meta': {'object_name': 'StaticIPAddress'}, |
267 | + 'alloc_type': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
268 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
269 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
270 | + 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'unique': 'True', 'max_length': '39'}), |
271 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}), |
272 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': u"orm['auth.User']", 'null': 'True', 'blank': 'True'}) |
273 | + }, |
274 | + u'maasserver.tag': { |
275 | + 'Meta': {'object_name': 'Tag'}, |
276 | + 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}), |
277 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
278 | + 'definition': ('django.db.models.fields.TextField', [], {'blank': 'True'}), |
279 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
280 | + 'kernel_opts': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), |
281 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}), |
282 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
283 | + }, |
284 | + u'maasserver.userprofile': { |
285 | + 'Meta': {'object_name': 'UserProfile'}, |
286 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
287 | + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'}) |
288 | + }, |
289 | + u'maasserver.zone': { |
290 | + 'Meta': {'ordering': "[u'name']", 'object_name': 'Zone'}, |
291 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
292 | + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), |
293 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
294 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}), |
295 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
296 | + }, |
297 | + u'piston.consumer': { |
298 | + 'Meta': {'object_name': 'Consumer'}, |
299 | + 'description': ('django.db.models.fields.TextField', [], {}), |
300 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
301 | + 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), |
302 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
303 | + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), |
304 | + 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}), |
305 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': u"orm['auth.User']"}) |
306 | + }, |
307 | + u'piston.token': { |
308 | + 'Meta': {'object_name': 'Token'}, |
309 | + 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), |
310 | + 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
311 | + 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['piston.Consumer']"}), |
312 | + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
313 | + 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
314 | + 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), |
315 | + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), |
316 | + 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1403788740L'}), |
317 | + 'token_type': ('django.db.models.fields.IntegerField', [], {}), |
318 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': u"orm['auth.User']"}), |
319 | + 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'}) |
320 | + } |
321 | + } |
322 | + |
323 | + complete_apps = ['maasserver'] |
324 | \ No newline at end of file |
325 | |
326 | === modified file 'src/maasserver/models/__init__.py' |
327 | --- src/maasserver/models/__init__.py 2014-06-20 13:38:39 +0000 |
328 | +++ src/maasserver/models/__init__.py 2014-06-26 14:09:35 +0000 |
329 | @@ -21,6 +21,7 @@ |
330 | 'DHCPLease', |
331 | 'DownloadProgress', |
332 | 'FileStorage', |
333 | + 'LicenseKey', |
334 | 'logger', |
335 | 'MACAddress', |
336 | 'Network', |
337 | @@ -52,6 +53,7 @@ |
338 | from maasserver.models.dhcplease import DHCPLease |
339 | from maasserver.models.downloadprogress import DownloadProgress |
340 | from maasserver.models.filestorage import FileStorage |
341 | +from maasserver.models.licensekey import LicenseKey |
342 | from maasserver.models.macaddress import MACAddress |
343 | from maasserver.models.macipaddresslink import MACStaticIPAddressLink |
344 | from maasserver.models.network import Network |
345 | @@ -72,9 +74,9 @@ |
346 | # export in __all__. |
347 | ignore_unused( |
348 | BootImage, ComponentError, Config, DHCPLease, DownloadProgress, |
349 | - FileStorage, StaticIPAddress, MACAddress, MACStaticIPAddressLink, |
350 | - Network, NodeGroup, SSHKey, Tag, UserProfile, NodeGroupInterface, |
351 | - Zone, logger) |
352 | + FileStorage, LicenseKey, StaticIPAddress, MACAddress, |
353 | + MACStaticIPAddressLink, Network, NodeGroup, SSHKey, Tag, UserProfile, |
354 | + NodeGroupInterface, Zone, logger) |
355 | |
356 | |
357 | # Connect the 'create_user' method to the post save signal of User. |
358 | |
359 | === added file 'src/maasserver/models/licensekey.py' |
360 | --- src/maasserver/models/licensekey.py 1970-01-01 00:00:00 +0000 |
361 | +++ src/maasserver/models/licensekey.py 2014-06-26 14:09:35 +0000 |
362 | @@ -0,0 +1,83 @@ |
363 | +# Copyright 2012-2014 Canonical Ltd. This software is licensed under the |
364 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
365 | + |
366 | +"""Global license keys.""" |
367 | + |
368 | +from __future__ import ( |
369 | + absolute_import, |
370 | + print_function, |
371 | + unicode_literals, |
372 | + ) |
373 | + |
374 | +str = None |
375 | + |
376 | +__metaclass__ = type |
377 | +__all__ = [ |
378 | + 'LicenseKey', |
379 | + ] |
380 | + |
381 | + |
382 | +from django.db.models import ( |
383 | + CharField, |
384 | + Manager, |
385 | + ) |
386 | +from maasserver import DefaultMeta |
387 | +from maasserver.models.timestampedmodel import TimestampedModel |
388 | + |
389 | + |
390 | +class LicenseKeyManager(Manager): |
391 | + """Manager for model class. |
392 | + |
393 | + Don't import or instantiate this directly; access as `LicenseKey.objects`. |
394 | + """ |
395 | + |
396 | + def get_by_osystem_series(self, osystem, distro_series): |
397 | + """Returns :class:`LicenseKey`; for osystem and distro_series.""" |
398 | + return self.get(osystem=osystem, distro_series=distro_series) |
399 | + |
400 | + def get_license_key(self, osystem, distro_series): |
401 | + """Returns the value of license_key in the :class:`LicenseKey`; model. |
402 | + |
403 | + :param osystem: operating system |
404 | + :param distro_series: os distro series |
405 | + :returns: license key |
406 | + :rtype: unicode |
407 | + """ |
408 | + key = self.get_by_osystem_series(osystem, distro_series) |
409 | + return key.license_key |
410 | + |
411 | + def has_license_key(self, osystem, distro_series): |
412 | + """Checks that a license key exists for the osystem and |
413 | + distro_series.""" |
414 | + return self.filter( |
415 | + osystem=osystem, distro_series=distro_series).exists() |
416 | + |
417 | + |
418 | +class LicenseKey(TimestampedModel): |
419 | + """Available license key for osystem and distro_series combo. |
420 | + |
421 | + Each `LicenseKey` matches to a operating system and release. Only one |
422 | + license key can exists per osystem/distro_series combination. |
423 | + """ |
424 | + |
425 | + class Meta(DefaultMeta): |
426 | + unique_together = ( |
427 | + ('osystem', 'distro_series'), |
428 | + ) |
429 | + |
430 | + objects = LicenseKeyManager() |
431 | + |
432 | + # Operating system (e.g. "ubuntu") that uses the license key. |
433 | + osystem = CharField(max_length=255, blank=False) |
434 | + |
435 | + # OS series (e.g. "precise") that uses the license key. |
436 | + distro_series = CharField(max_length=255, blank=False) |
437 | + |
438 | + # License key for the osystem/distro_series combo. |
439 | + license_key = CharField(max_length=255, blank=False) |
440 | + |
441 | + def __repr__(self): |
442 | + return "<LicenseKey %s/%s>" % ( |
443 | + self.osystem, |
444 | + self.distro_series, |
445 | + ) |
446 | |
447 | === added file 'src/maasserver/models/tests/test_licensekey.py' |
448 | --- src/maasserver/models/tests/test_licensekey.py 1970-01-01 00:00:00 +0000 |
449 | +++ src/maasserver/models/tests/test_licensekey.py 2014-06-26 14:09:35 +0000 |
450 | @@ -0,0 +1,48 @@ |
451 | +# Copyright 2012-2014 Canonical Ltd. This software is licensed under the |
452 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
453 | + |
454 | +"""Tests for :class:`LicenseKey`.""" |
455 | + |
456 | +from __future__ import ( |
457 | + absolute_import, |
458 | + print_function, |
459 | + unicode_literals, |
460 | + ) |
461 | + |
462 | +str = None |
463 | + |
464 | +__metaclass__ = type |
465 | +__all__ = [] |
466 | + |
467 | +from maasserver.models import LicenseKey |
468 | +from maasserver.testing.factory import factory |
469 | +from maasserver.testing.testcase import MAASServerTestCase |
470 | + |
471 | + |
472 | +class TestLicenseKeyManager(MAASServerTestCase): |
473 | + |
474 | + def test_get_by_osystem_series(self): |
475 | + key = factory.make_license_key() |
476 | + expected = LicenseKey.objects.get_by_osystem_series( |
477 | + key.osystem, key.distro_series) |
478 | + self.assertEqual(key, expected) |
479 | + |
480 | + def test_get_license_key(self): |
481 | + key = factory.make_license_key() |
482 | + license_key = LicenseKey.objects.get_license_key( |
483 | + key.osystem, key.distro_series) |
484 | + self.assertEqual(key.license_key, license_key) |
485 | + |
486 | + def test_has_license_key_True(self): |
487 | + key = factory.make_license_key() |
488 | + self.assertTrue( |
489 | + LicenseKey.objects.has_license_key( |
490 | + key.osystem, key.distro_series)) |
491 | + |
492 | + def test_has_license_key_False(self): |
493 | + factory.make_license_key() |
494 | + osystem = factory.make_name('osystem') |
495 | + series = factory.make_name('distro_series') |
496 | + self.assertFalse( |
497 | + LicenseKey.objects.has_license_key( |
498 | + osystem, series)) |
499 | |
500 | === modified file 'src/maasserver/testing/factory.py' |
501 | --- src/maasserver/testing/factory.py 2014-06-25 16:29:11 +0000 |
502 | +++ src/maasserver/testing/factory.py 2014-06-26 14:09:35 +0000 |
503 | @@ -38,6 +38,7 @@ |
504 | DHCPLease, |
505 | DownloadProgress, |
506 | FileStorage, |
507 | + LicenseKey, |
508 | MACAddress, |
509 | MACStaticIPAddressLink, |
510 | Network, |
511 | @@ -890,6 +891,19 @@ |
512 | boot_source_selection.save() |
513 | return boot_source_selection |
514 | |
515 | + def make_license_key(self, osystem=None, distro_series=None, |
516 | + license_key=None): |
517 | + if osystem is None: |
518 | + osystem = factory.make_name('osystem') |
519 | + if distro_series is None: |
520 | + distro_series = factory.make_name('distro_series') |
521 | + if license_key is None: |
522 | + license_key = factory.make_name('key') |
523 | + return LicenseKey.objects.create( |
524 | + osystem=osystem, |
525 | + distro_series=distro_series, |
526 | + license_key=license_key) |
527 | + |
528 | |
529 | # Create factory singleton. |
530 | factory = Factory() |
Looks good but I've got a couple of remarks (see inline).