Merge lp:~blake-rouse/maas/global-licensekey-model into lp:~maas-committers/maas/trunk

Proposed by Blake Rouse
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
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.

To post a comment you must log in.
Revision history for this message
Raphaël Badin (rvb) wrote :

Looks good but I've got a couple of remarks (see inline).

review: Approve
Revision history for this message
Blake Rouse (blake-rouse) :
Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (20.5 KiB)

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://security.ubuntu.com trusty-security InRelease
Get:1 http://security.ubuntu.com trusty-security Release.gpg [933 B]
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Get:2 http://security.ubuntu.com trusty-security Release [58.5 kB]
Ign http://nova.clouds.archive.ubuntu.com trusty-updates InRelease
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Get:3 http://nova.clouds.archive.ubuntu.com trusty-updates Release.gpg [933 B]
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Get:4 http://nova.clouds.archive.ubuntu.com trusty-updates Release [58.5 kB]
Get:5 http://security.ubuntu.com trusty-security/main Sources [28.6 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Get:6 http://security.ubuntu.com trusty-security/universe Sources [5,763 B]
Get:7 http://security.ubuntu.com trusty-security/main amd64 Packages [99.1 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Get:8 http://security.ubuntu.com trusty-security/universe amd64 Packages [33.4 kB]
Hit http://security.ubuntu.com trusty-security/main Translation-en
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Ign http://security.ubuntu.com trusty-security/main Translation-en_US
Ign http://security.ubuntu.com trusty-security/universe Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Get:9 http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources [76.5 kB]
Get:10 http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources [52.6 kB]
Get:11 http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages [204 kB]
Get:12 http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages [137 kB]
Get:13 http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en [91.5 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en
Ign http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en_US
Fetched 848 kB in 0s (1,944 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 bind9 bind9utils build-essential bzr-builddeb curl daemontools debhelper dh-apport distro-info dnsutils firefox freeipmi-tools ipython isc-dhcp-common libjs-raphael libjs-yui3-full libjs-yui3-min libpq-dev make postgresql python-amqplib python-bzrlib python-celery python-convoy python-crochet python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-formencode python-hivex pyt...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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()