Merge lp:~jtv/maas/bug-1069734 into lp:~maas-committers/maas/trunk
- bug-1069734
- Merge into trunk
Proposed by
Jeroen T. Vermeulen
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Jeroen T. Vermeulen | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 1310 | ||||
Proposed branch: | lp:~jtv/maas/bug-1069734 | ||||
Merge into: | lp:~maas-committers/maas/trunk | ||||
Diff against target: |
1191 lines (+690/-305) 12 files modified
etc/cron.d/maas-gc (+2/-6) setup.py (+0/-2) src/maasserver/api.py (+1/-1) src/maasserver/management/commands/gc.py (+0/-24) src/maasserver/migrations/0039_add_filestorage_content.py (+233/-0) src/maasserver/migrations/0040_make_filestorage_data_not_null.py (+203/-0) src/maasserver/migrations/0041_remove_filestorage_data.py (+213/-0) src/maasserver/models/filestorage.py (+15/-92) src/maasserver/testing/factory.py (+4/-4) src/maasserver/tests/test_api.py (+4/-2) src/maasserver/tests/test_commands.py (+0/-11) src/maasserver/tests/test_filestorage.py (+15/-163) |
||||
To merge this branch: | bzr merge lp:~jtv/maas/bug-1069734 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
John A Meinel (community) | Approve | ||
Review via email: mp+131551@code.launchpad.net |
Commit message
Forward-port 1.2 r1272 to trunk: move region file storage into the database.
Description of the change
This fixes a problem that was breaking region-controller setups that had multiple instances of the app server. The fix was originally written against, and landed on, the 1.2 branch (for Quantal). It was forward-ported to trunk without changes. Even the sequence numbers on the database migrations still fit.
Jeroen
To post a comment you must log in.
Revision history for this message
John A Meinel (jameinel) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'etc/cron.d/maas-gc' |
2 | --- etc/cron.d/maas-gc 2012-08-03 16:33:05 +0000 |
3 | +++ etc/cron.d/maas-gc 2012-10-26 08:37:31 +0000 |
4 | @@ -1,8 +1,4 @@ |
5 | # Perform daily background cleanups in MAAS. |
6 | # |
7 | -# The "maas gc" command is for garbage-collection, such as deleting uploaded |
8 | -# files from Juju's file storage API, and in the future commissioning logs, |
9 | -# that have been superseded by newer ones. (This isn't done immediately |
10 | -# when the files are overwritten because (1) the transaction that overwrites |
11 | -# them may fail, and (2) a file may still be in use when it's overwritten.) |
12 | -0 0 * * * root /usr/sbin/maas gc &> /dev/null |
13 | +# This currently does nothing: maas-gc is no longer needed. |
14 | +#0 0 * * * root /usr/sbin/maas gc &> /dev/null |
15 | |
16 | === modified file 'setup.py' |
17 | --- setup.py 2012-10-01 22:56:46 +0000 |
18 | +++ setup.py 2012-10-26 08:37:31 +0000 |
19 | @@ -65,8 +65,6 @@ |
20 | 'etc/maas/commissioning-user-data', |
21 | 'contrib/maas-http.conf', |
22 | 'contrib/maas_local_settings.py']), |
23 | - ('/etc/cron.d', |
24 | - ['etc/cron.d/maas-gc']), |
25 | ('/usr/share/maas', |
26 | ['contrib/wsgi.py', |
27 | 'etc/celeryconfig.py', |
28 | |
29 | === modified file 'src/maasserver/api.py' |
30 | --- src/maasserver/api.py 2012-10-26 07:59:12 +0000 |
31 | +++ src/maasserver/api.py 2012-10-26 08:37:31 +0000 |
32 | @@ -985,7 +985,7 @@ |
33 | db_file = FileStorage.objects.get(filename=filename) |
34 | except FileStorage.DoesNotExist: |
35 | raise MAASAPINotFound("File not found") |
36 | - return HttpResponse(db_file.data.read(), status=httplib.OK) |
37 | + return HttpResponse(db_file.content, status=httplib.OK) |
38 | |
39 | |
40 | class AnonFilesHandler(AnonymousOperationsHandler): |
41 | |
42 | === removed file 'src/maasserver/management/commands/gc.py' |
43 | --- src/maasserver/management/commands/gc.py 2012-04-16 10:00:51 +0000 |
44 | +++ src/maasserver/management/commands/gc.py 1970-01-01 00:00:00 +0000 |
45 | @@ -1,24 +0,0 @@ |
46 | -# Copyright 2012 Canonical Ltd. This software is licensed under the |
47 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
48 | - |
49 | -"""Custom django command: garabge-collect.""" |
50 | - |
51 | -from __future__ import ( |
52 | - absolute_import, |
53 | - print_function, |
54 | - unicode_literals, |
55 | - ) |
56 | - |
57 | -__metaclass__ = type |
58 | -__all__ = [ |
59 | - 'Command', |
60 | - ] |
61 | - |
62 | - |
63 | -from django.core.management.base import BaseCommand |
64 | -from maasserver.models import FileStorage |
65 | - |
66 | - |
67 | -class Command(BaseCommand): |
68 | - def handle(self, *args, **options): |
69 | - FileStorage.objects.collect_garbage() |
70 | |
71 | === added file 'src/maasserver/migrations/0039_add_filestorage_content.py' |
72 | --- src/maasserver/migrations/0039_add_filestorage_content.py 1970-01-01 00:00:00 +0000 |
73 | +++ src/maasserver/migrations/0039_add_filestorage_content.py 2012-10-26 08:37:31 +0000 |
74 | @@ -0,0 +1,233 @@ |
75 | +# -*- coding: utf-8 -*- |
76 | +from base64 import b64encode |
77 | +import datetime |
78 | +import os.path |
79 | + |
80 | +from django.conf import settings |
81 | +from django.db import models |
82 | +from south.db import db |
83 | +from south.v2 import SchemaMigration |
84 | + |
85 | + |
86 | +def get_unmigrated_filestorages(orm): |
87 | + """Find FileStorage objects whose data needs migrating.""" |
88 | + return orm['maasserver.FileStorage'].objects.filter(content=None) |
89 | + |
90 | + |
91 | +def read_file(storage): |
92 | + """Read file contents from a FileStorage.""" |
93 | + return storage.data.read() |
94 | + |
95 | + |
96 | +def copy_files_into_database(orm): |
97 | + """Copy file contents into the "content" field.""" |
98 | + for storage in get_unmigrated_filestorages(orm): |
99 | + raw_content = read_file(storage) |
100 | + storage.content = b64encode(raw_content).decode('ascii') |
101 | + storage.save() |
102 | + |
103 | + |
104 | +class Migration(SchemaMigration): |
105 | + |
106 | + def forwards(self, orm): |
107 | + # Adding field 'FileStorage.content' |
108 | + db.add_column(u'maasserver_filestorage', 'content', |
109 | + self.gf('metadataserver.fields.BinaryField')(null=True), |
110 | + keep_default=False) |
111 | + |
112 | + # Changing field 'FileStorage.filename' |
113 | + db.alter_column(u'maasserver_filestorage', 'filename', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255)) |
114 | + |
115 | + # Effecting data migration. Not deleting the old files yet; the |
116 | + # database transaction might still abort for whatever reason. |
117 | + copy_files_into_database(orm) |
118 | + |
119 | + def backwards(self, orm): |
120 | + # Deleting field 'FileStorage.content' |
121 | + db.delete_column(u'maasserver_filestorage', 'content') |
122 | + |
123 | + |
124 | + # Changing field 'FileStorage.filename' |
125 | + db.alter_column(u'maasserver_filestorage', 'filename', self.gf('django.db.models.fields.CharField')(max_length=200, unique=True)) |
126 | + |
127 | + models = { |
128 | + 'auth.group': { |
129 | + 'Meta': {'object_name': 'Group'}, |
130 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
131 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), |
132 | + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) |
133 | + }, |
134 | + 'auth.permission': { |
135 | + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, |
136 | + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
137 | + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), |
138 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
139 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) |
140 | + }, |
141 | + 'auth.user': { |
142 | + 'Meta': {'object_name': 'User'}, |
143 | + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
144 | + 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}), |
145 | + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
146 | + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), |
147 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
148 | + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
149 | + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
150 | + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
151 | + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
152 | + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
153 | + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), |
154 | + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), |
155 | + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) |
156 | + }, |
157 | + 'contenttypes.contenttype': { |
158 | + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, |
159 | + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
160 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
161 | + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
162 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) |
163 | + }, |
164 | + u'maasserver.bootimage': { |
165 | + 'Meta': {'unique_together': "((u'architecture', u'subarchitecture', u'release', u'purpose'),)", 'object_name': 'BootImage'}, |
166 | + 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
167 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
168 | + 'purpose': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
169 | + 'release': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
170 | + 'subarchitecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}) |
171 | + }, |
172 | + u'maasserver.componenterror': { |
173 | + 'Meta': {'object_name': 'ComponentError'}, |
174 | + 'component': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}), |
175 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
176 | + 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000'}), |
177 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
178 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
179 | + }, |
180 | + u'maasserver.config': { |
181 | + 'Meta': {'object_name': 'Config'}, |
182 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
183 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
184 | + 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'}) |
185 | + }, |
186 | + u'maasserver.dhcplease': { |
187 | + 'Meta': {'object_name': 'DHCPLease'}, |
188 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
189 | + 'ip': ('django.db.models.fields.IPAddressField', [], {'unique': 'True', 'max_length': '15'}), |
190 | + 'mac': ('maasserver.fields.MACAddressField', [], {}), |
191 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}) |
192 | + }, |
193 | + u'maasserver.filestorage': { |
194 | + 'Meta': {'object_name': 'FileStorage'}, |
195 | + 'content': ('metadataserver.fields.BinaryField', [], {'null': 'True'}), |
196 | + 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '255'}), |
197 | + 'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), |
198 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) |
199 | + }, |
200 | + u'maasserver.macaddress': { |
201 | + 'Meta': {'object_name': 'MACAddress'}, |
202 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
203 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
204 | + 'mac_address': ('maasserver.fields.MACAddressField', [], {'unique': 'True'}), |
205 | + 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}), |
206 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
207 | + }, |
208 | + u'maasserver.node': { |
209 | + 'Meta': {'object_name': 'Node'}, |
210 | + 'after_commissioning_action': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
211 | + 'architecture': ('django.db.models.fields.CharField', [], {'default': "u'i386/generic'", 'max_length': '31'}), |
212 | + 'cpu_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
213 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
214 | + 'distro_series': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '10', 'null': 'True', 'blank': 'True'}), |
215 | + 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
216 | + 'hardware_details': ('maasserver.fields.XMLField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), |
217 | + 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
218 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
219 | + 'memory': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
220 | + 'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
221 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}), |
222 | + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), |
223 | + 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}), |
224 | + 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}), |
225 | + 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}), |
226 | + 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-b588ce50-1ea0-11e2-946f-002608dc6120'", 'unique': 'True', 'max_length': '41'}), |
227 | + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}), |
228 | + 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Token']", 'null': 'True'}), |
229 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
230 | + }, |
231 | + u'maasserver.nodegroup': { |
232 | + 'Meta': {'object_name': 'NodeGroup'}, |
233 | + 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}), |
234 | + 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Token']", 'unique': 'True'}), |
235 | + 'cluster_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100', 'blank': 'True'}), |
236 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
237 | + 'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
238 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
239 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}), |
240 | + 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
241 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}), |
242 | + 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'}) |
243 | + }, |
244 | + u'maasserver.nodegroupinterface': { |
245 | + 'Meta': {'unique_together': "((u'nodegroup', u'interface'),)", 'object_name': 'NodeGroupInterface'}, |
246 | + 'broadcast_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
247 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
248 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
249 | + 'interface': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
250 | + 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), |
251 | + 'ip_range_high': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
252 | + 'ip_range_low': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
253 | + 'management': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
254 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}), |
255 | + 'router_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
256 | + 'subnet_mask': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
257 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
258 | + }, |
259 | + u'maasserver.sshkey': { |
260 | + 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'}, |
261 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
262 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
263 | + 'key': ('django.db.models.fields.TextField', [], {}), |
264 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}), |
265 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) |
266 | + }, |
267 | + u'maasserver.tag': { |
268 | + 'Meta': {'object_name': 'Tag'}, |
269 | + 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}), |
270 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
271 | + 'definition': ('django.db.models.fields.TextField', [], {}), |
272 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
273 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}), |
274 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
275 | + }, |
276 | + u'maasserver.userprofile': { |
277 | + 'Meta': {'object_name': 'UserProfile'}, |
278 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
279 | + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) |
280 | + }, |
281 | + 'piston.consumer': { |
282 | + 'Meta': {'object_name': 'Consumer'}, |
283 | + 'description': ('django.db.models.fields.TextField', [], {}), |
284 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
285 | + 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), |
286 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
287 | + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), |
288 | + 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}), |
289 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': "orm['auth.User']"}) |
290 | + }, |
291 | + 'piston.token': { |
292 | + 'Meta': {'object_name': 'Token'}, |
293 | + 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), |
294 | + 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
295 | + 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Consumer']"}), |
296 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
297 | + 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
298 | + 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), |
299 | + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), |
300 | + 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1351168636L'}), |
301 | + 'token_type': ('django.db.models.fields.IntegerField', [], {}), |
302 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': "orm['auth.User']"}), |
303 | + 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'}) |
304 | + } |
305 | + } |
306 | + |
307 | + complete_apps = ['maasserver'] |
308 | |
309 | === added file 'src/maasserver/migrations/0040_make_filestorage_data_not_null.py' |
310 | --- src/maasserver/migrations/0040_make_filestorage_data_not_null.py 1970-01-01 00:00:00 +0000 |
311 | +++ src/maasserver/migrations/0040_make_filestorage_data_not_null.py 2012-10-26 08:37:31 +0000 |
312 | @@ -0,0 +1,203 @@ |
313 | +# -*- coding: utf-8 -*- |
314 | +import datetime |
315 | + |
316 | +from django.db import models |
317 | +from south.db import db |
318 | +from south.v2 import SchemaMigration |
319 | + |
320 | + |
321 | +class Migration(SchemaMigration): |
322 | + |
323 | + def forwards(self, orm): |
324 | + |
325 | + # Changing field 'FileStorage.content' |
326 | + # Disallow NULLs. The previous migration should have |
327 | + # initialized the column for all existing rows. |
328 | + db.alter_column(u'maasserver_filestorage', 'content', self.gf('metadataserver.fields.BinaryField')(null=False)) |
329 | + |
330 | + def backwards(self, orm): |
331 | + |
332 | + # Changing field 'FileStorage.content' |
333 | + db.alter_column(u'maasserver_filestorage', 'content', self.gf('metadataserver.fields.BinaryField')(null=True)) |
334 | + |
335 | + models = { |
336 | + 'auth.group': { |
337 | + 'Meta': {'object_name': 'Group'}, |
338 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
339 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), |
340 | + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) |
341 | + }, |
342 | + 'auth.permission': { |
343 | + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, |
344 | + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
345 | + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), |
346 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
347 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) |
348 | + }, |
349 | + 'auth.user': { |
350 | + 'Meta': {'object_name': 'User'}, |
351 | + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
352 | + 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}), |
353 | + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
354 | + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), |
355 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
356 | + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
357 | + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
358 | + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
359 | + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
360 | + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
361 | + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), |
362 | + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), |
363 | + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) |
364 | + }, |
365 | + 'contenttypes.contenttype': { |
366 | + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, |
367 | + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
368 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
369 | + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
370 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) |
371 | + }, |
372 | + u'maasserver.bootimage': { |
373 | + 'Meta': {'unique_together': "((u'architecture', u'subarchitecture', u'release', u'purpose'),)", 'object_name': 'BootImage'}, |
374 | + 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
375 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
376 | + 'purpose': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
377 | + 'release': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
378 | + 'subarchitecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}) |
379 | + }, |
380 | + u'maasserver.componenterror': { |
381 | + 'Meta': {'object_name': 'ComponentError'}, |
382 | + 'component': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}), |
383 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
384 | + 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000'}), |
385 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
386 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
387 | + }, |
388 | + u'maasserver.config': { |
389 | + 'Meta': {'object_name': 'Config'}, |
390 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
391 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
392 | + 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'}) |
393 | + }, |
394 | + u'maasserver.dhcplease': { |
395 | + 'Meta': {'object_name': 'DHCPLease'}, |
396 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
397 | + 'ip': ('django.db.models.fields.IPAddressField', [], {'unique': 'True', 'max_length': '15'}), |
398 | + 'mac': ('maasserver.fields.MACAddressField', [], {}), |
399 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}) |
400 | + }, |
401 | + u'maasserver.filestorage': { |
402 | + 'Meta': {'object_name': 'FileStorage'}, |
403 | + 'content': ('metadataserver.fields.BinaryField', [], {}), |
404 | + 'data': ('django.db.models.fields.files.FileField', [], {'max_length': '255'}), |
405 | + 'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), |
406 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) |
407 | + }, |
408 | + u'maasserver.macaddress': { |
409 | + 'Meta': {'object_name': 'MACAddress'}, |
410 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
411 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
412 | + 'mac_address': ('maasserver.fields.MACAddressField', [], {'unique': 'True'}), |
413 | + 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}), |
414 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
415 | + }, |
416 | + u'maasserver.node': { |
417 | + 'Meta': {'object_name': 'Node'}, |
418 | + 'after_commissioning_action': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
419 | + 'architecture': ('django.db.models.fields.CharField', [], {'default': "u'i386/generic'", 'max_length': '31'}), |
420 | + 'cpu_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
421 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
422 | + 'distro_series': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '10', 'null': 'True', 'blank': 'True'}), |
423 | + 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
424 | + 'hardware_details': ('maasserver.fields.XMLField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), |
425 | + 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
426 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
427 | + 'memory': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
428 | + 'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
429 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}), |
430 | + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), |
431 | + 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}), |
432 | + 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}), |
433 | + 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}), |
434 | + 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-766d0f92-1ea5-11e2-9dee-002608dc6120'", 'unique': 'True', 'max_length': '41'}), |
435 | + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}), |
436 | + 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Token']", 'null': 'True'}), |
437 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
438 | + }, |
439 | + u'maasserver.nodegroup': { |
440 | + 'Meta': {'object_name': 'NodeGroup'}, |
441 | + 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}), |
442 | + 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Token']", 'unique': 'True'}), |
443 | + 'cluster_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100', 'blank': 'True'}), |
444 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
445 | + 'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
446 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
447 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}), |
448 | + 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
449 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}), |
450 | + 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'}) |
451 | + }, |
452 | + u'maasserver.nodegroupinterface': { |
453 | + 'Meta': {'unique_together': "((u'nodegroup', u'interface'),)", 'object_name': 'NodeGroupInterface'}, |
454 | + 'broadcast_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
455 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
456 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
457 | + 'interface': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
458 | + 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), |
459 | + 'ip_range_high': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
460 | + 'ip_range_low': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
461 | + 'management': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
462 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}), |
463 | + 'router_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
464 | + 'subnet_mask': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
465 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
466 | + }, |
467 | + u'maasserver.sshkey': { |
468 | + 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'}, |
469 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
470 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
471 | + 'key': ('django.db.models.fields.TextField', [], {}), |
472 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}), |
473 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) |
474 | + }, |
475 | + u'maasserver.tag': { |
476 | + 'Meta': {'object_name': 'Tag'}, |
477 | + 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}), |
478 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
479 | + 'definition': ('django.db.models.fields.TextField', [], {}), |
480 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
481 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}), |
482 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
483 | + }, |
484 | + u'maasserver.userprofile': { |
485 | + 'Meta': {'object_name': 'UserProfile'}, |
486 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
487 | + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) |
488 | + }, |
489 | + 'piston.consumer': { |
490 | + 'Meta': {'object_name': 'Consumer'}, |
491 | + 'description': ('django.db.models.fields.TextField', [], {}), |
492 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
493 | + 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), |
494 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
495 | + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), |
496 | + 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}), |
497 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': "orm['auth.User']"}) |
498 | + }, |
499 | + 'piston.token': { |
500 | + 'Meta': {'object_name': 'Token'}, |
501 | + 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), |
502 | + 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
503 | + 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Consumer']"}), |
504 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
505 | + 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
506 | + 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), |
507 | + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), |
508 | + 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1351170650L'}), |
509 | + 'token_type': ('django.db.models.fields.IntegerField', [], {}), |
510 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': "orm['auth.User']"}), |
511 | + 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'}) |
512 | + } |
513 | + } |
514 | + |
515 | + complete_apps = ['maasserver'] |
516 | |
517 | === added file 'src/maasserver/migrations/0041_remove_filestorage_data.py' |
518 | --- src/maasserver/migrations/0041_remove_filestorage_data.py 1970-01-01 00:00:00 +0000 |
519 | +++ src/maasserver/migrations/0041_remove_filestorage_data.py 2012-10-26 08:37:31 +0000 |
520 | @@ -0,0 +1,213 @@ |
521 | +# -*- coding: utf-8 -*- |
522 | +import datetime |
523 | +import os.path |
524 | +from shutil import rmtree |
525 | + |
526 | +from django.conf import settings |
527 | +from django.db import models |
528 | +from south.db import db |
529 | +from south.v2 import SchemaMigration |
530 | + |
531 | +# The sub-directory of MEDIA_ROOT where FileStorage used to store its |
532 | +# files. This duplicates the value of the now-removed |
533 | +# FileStorage.upload_dir. |
534 | +upload_dir = 'storage' |
535 | + |
536 | + |
537 | +class Migration(SchemaMigration): |
538 | + |
539 | + def forwards(self, orm): |
540 | + # Deleting field 'FileStorage.data' |
541 | + db.delete_column(u'maasserver_filestorage', 'data') |
542 | + |
543 | + # Cleaning up any obsolete FileStorage files. |
544 | + if settings.MEDIA_ROOT and os.path.isdir(settings.MEDIA_ROOT): |
545 | + rmtree( |
546 | + os.path.join(settings.MEDIA_ROOT, upload_dir), |
547 | + ignore_errors=True) |
548 | + |
549 | + def backwards(self, orm): |
550 | + |
551 | + # User chose to not deal with backwards NULL issues for 'FileStorage.data' |
552 | + raise RuntimeError("Cannot reverse this migration. 'FileStorage.data' and its values cannot be restored.") |
553 | + |
554 | + models = { |
555 | + 'auth.group': { |
556 | + 'Meta': {'object_name': 'Group'}, |
557 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
558 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), |
559 | + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) |
560 | + }, |
561 | + 'auth.permission': { |
562 | + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, |
563 | + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
564 | + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), |
565 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
566 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) |
567 | + }, |
568 | + 'auth.user': { |
569 | + 'Meta': {'object_name': 'User'}, |
570 | + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
571 | + 'email': ('django.db.models.fields.EmailField', [], {'unique': 'True', 'max_length': '75', 'blank': 'True'}), |
572 | + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
573 | + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), |
574 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
575 | + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
576 | + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
577 | + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
578 | + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
579 | + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
580 | + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), |
581 | + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), |
582 | + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) |
583 | + }, |
584 | + 'contenttypes.contenttype': { |
585 | + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, |
586 | + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
587 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
588 | + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
589 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) |
590 | + }, |
591 | + u'maasserver.bootimage': { |
592 | + 'Meta': {'unique_together': "((u'architecture', u'subarchitecture', u'release', u'purpose'),)", 'object_name': 'BootImage'}, |
593 | + 'architecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
594 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
595 | + 'purpose': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
596 | + 'release': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
597 | + 'subarchitecture': ('django.db.models.fields.CharField', [], {'max_length': '255'}) |
598 | + }, |
599 | + u'maasserver.componenterror': { |
600 | + 'Meta': {'object_name': 'ComponentError'}, |
601 | + 'component': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}), |
602 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
603 | + 'error': ('django.db.models.fields.CharField', [], {'max_length': '1000'}), |
604 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
605 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
606 | + }, |
607 | + u'maasserver.config': { |
608 | + 'Meta': {'object_name': 'Config'}, |
609 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
610 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
611 | + 'value': ('maasserver.fields.JSONObjectField', [], {'null': 'True'}) |
612 | + }, |
613 | + u'maasserver.dhcplease': { |
614 | + 'Meta': {'object_name': 'DHCPLease'}, |
615 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
616 | + 'ip': ('django.db.models.fields.IPAddressField', [], {'unique': 'True', 'max_length': '15'}), |
617 | + 'mac': ('maasserver.fields.MACAddressField', [], {}), |
618 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}) |
619 | + }, |
620 | + u'maasserver.filestorage': { |
621 | + 'Meta': {'object_name': 'FileStorage'}, |
622 | + 'content': ('metadataserver.fields.BinaryField', [], {}), |
623 | + 'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), |
624 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) |
625 | + }, |
626 | + u'maasserver.macaddress': { |
627 | + 'Meta': {'object_name': 'MACAddress'}, |
628 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
629 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
630 | + 'mac_address': ('maasserver.fields.MACAddressField', [], {'unique': 'True'}), |
631 | + 'node': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.Node']"}), |
632 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
633 | + }, |
634 | + u'maasserver.node': { |
635 | + 'Meta': {'object_name': 'Node'}, |
636 | + 'after_commissioning_action': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
637 | + 'architecture': ('django.db.models.fields.CharField', [], {'default': "u'i386/generic'", 'max_length': '31'}), |
638 | + 'cpu_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
639 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
640 | + 'distro_series': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '10', 'null': 'True', 'blank': 'True'}), |
641 | + 'error': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
642 | + 'hardware_details': ('maasserver.fields.XMLField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), |
643 | + 'hostname': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
644 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
645 | + 'memory': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
646 | + 'netboot': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
647 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']", 'null': 'True'}), |
648 | + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), |
649 | + 'power_parameters': ('maasserver.fields.JSONObjectField', [], {'default': "u''", 'blank': 'True'}), |
650 | + 'power_type': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '10', 'blank': 'True'}), |
651 | + 'status': ('django.db.models.fields.IntegerField', [], {'default': '0', 'max_length': '10'}), |
652 | + 'system_id': ('django.db.models.fields.CharField', [], {'default': "u'node-da71dfcc-1ea5-11e2-8763-002608dc6120'", 'unique': 'True', 'max_length': '41'}), |
653 | + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['maasserver.Tag']", 'symmetrical': 'False'}), |
654 | + 'token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Token']", 'null': 'True'}), |
655 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
656 | + }, |
657 | + u'maasserver.nodegroup': { |
658 | + 'Meta': {'object_name': 'NodeGroup'}, |
659 | + 'api_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '18'}), |
660 | + 'api_token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Token']", 'unique': 'True'}), |
661 | + 'cluster_name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100', 'blank': 'True'}), |
662 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
663 | + 'dhcp_key': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
664 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
665 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'blank': 'True'}), |
666 | + 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
667 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}), |
668 | + 'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '36'}) |
669 | + }, |
670 | + u'maasserver.nodegroupinterface': { |
671 | + 'Meta': {'unique_together': "((u'nodegroup', u'interface'),)", 'object_name': 'NodeGroupInterface'}, |
672 | + 'broadcast_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
673 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
674 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
675 | + 'interface': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'blank': 'True'}), |
676 | + 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), |
677 | + 'ip_range_high': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
678 | + 'ip_range_low': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
679 | + 'management': ('django.db.models.fields.IntegerField', [], {'default': '0'}), |
680 | + 'nodegroup': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['maasserver.NodeGroup']"}), |
681 | + 'router_ip': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
682 | + 'subnet_mask': ('django.db.models.fields.GenericIPAddressField', [], {'default': 'None', 'max_length': '39', 'null': 'True', 'blank': 'True'}), |
683 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
684 | + }, |
685 | + u'maasserver.sshkey': { |
686 | + 'Meta': {'unique_together': "((u'user', u'key'),)", 'object_name': 'SSHKey'}, |
687 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
688 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
689 | + 'key': ('django.db.models.fields.TextField', [], {}), |
690 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}), |
691 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) |
692 | + }, |
693 | + u'maasserver.tag': { |
694 | + 'Meta': {'object_name': 'Tag'}, |
695 | + 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}), |
696 | + 'created': ('django.db.models.fields.DateTimeField', [], {}), |
697 | + 'definition': ('django.db.models.fields.TextField', [], {}), |
698 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
699 | + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}), |
700 | + 'updated': ('django.db.models.fields.DateTimeField', [], {}) |
701 | + }, |
702 | + u'maasserver.userprofile': { |
703 | + 'Meta': {'object_name': 'UserProfile'}, |
704 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
705 | + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) |
706 | + }, |
707 | + 'piston.consumer': { |
708 | + 'Meta': {'object_name': 'Consumer'}, |
709 | + 'description': ('django.db.models.fields.TextField', [], {}), |
710 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
711 | + 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), |
712 | + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
713 | + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), |
714 | + 'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '16'}), |
715 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'consumers'", 'null': 'True', 'to': "orm['auth.User']"}) |
716 | + }, |
717 | + 'piston.token': { |
718 | + 'Meta': {'object_name': 'Token'}, |
719 | + 'callback': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), |
720 | + 'callback_confirmed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
721 | + 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['piston.Consumer']"}), |
722 | + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
723 | + 'is_approved': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
724 | + 'key': ('django.db.models.fields.CharField', [], {'max_length': '18'}), |
725 | + 'secret': ('django.db.models.fields.CharField', [], {'max_length': '32'}), |
726 | + 'timestamp': ('django.db.models.fields.IntegerField', [], {'default': '1351170840L'}), |
727 | + 'token_type': ('django.db.models.fields.IntegerField', [], {}), |
728 | + 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'tokens'", 'null': 'True', 'to': "orm['auth.User']"}), |
729 | + 'verifier': ('django.db.models.fields.CharField', [], {'max_length': '10'}) |
730 | + } |
731 | + } |
732 | + |
733 | + complete_apps = ['maasserver'] |
734 | |
735 | === modified file 'src/maasserver/models/filestorage.py' |
736 | --- src/maasserver/models/filestorage.py 2012-08-24 10:28:29 +0000 |
737 | +++ src/maasserver/models/filestorage.py 2012-10-26 08:37:31 +0000 |
738 | @@ -15,22 +15,17 @@ |
739 | ] |
740 | |
741 | |
742 | -from errno import ENOENT |
743 | -import os |
744 | -import time |
745 | - |
746 | -from django.conf import settings |
747 | -from django.core.files.base import ContentFile |
748 | -from django.core.files.storage import FileSystemStorage |
749 | from django.db.models import ( |
750 | CharField, |
751 | - FileField, |
752 | Manager, |
753 | Model, |
754 | ) |
755 | from maasserver import DefaultMeta |
756 | from maasserver.models.cleansave import CleanSave |
757 | -from maasserver.utils.orm import get_one |
758 | +from metadataserver.fields import ( |
759 | + Bin, |
760 | + BinaryField, |
761 | + ) |
762 | |
763 | |
764 | class FileStorageManager(Manager): |
765 | @@ -47,108 +42,36 @@ |
766 | original file will not be affected. Also, any ongoing reads from the |
767 | old file will continue without iterruption. |
768 | """ |
769 | - # The time, in seconds, that an unreferenced file is allowed to |
770 | - # persist in order to satisfy ongoing requests. |
771 | - grace_time = 12 * 60 * 60 |
772 | - |
773 | - def get_existing_storage(self, filename): |
774 | - """Return an existing `FileStorage` of this name, or None.""" |
775 | - return get_one(self.filter(filename=filename)) |
776 | |
777 | def save_file(self, filename, file_object): |
778 | - """Save the file to the filesystem and persist to the database. |
779 | - |
780 | - The file will end up in MEDIA_ROOT/storage/ |
781 | + """Save the file to the database. |
782 | |
783 | If a file of that name already existed, it will be replaced by the |
784 | new contents. |
785 | """ |
786 | # This probably ought to read in chunks but large files are |
787 | - # not expected. Also note that uploading a file with the same |
788 | - # name as an existing one will cause that file to be written |
789 | - # with a new generated name, and the old one remains where it |
790 | - # is. See https://code.djangoproject.com/ticket/6157 - the |
791 | - # Django devs consider deleting things dangerous ... ha. |
792 | - # HOWEVER - this operation would need to be atomic anyway so |
793 | - # it's safest left how it is for now (reads can overlap with |
794 | - # writes from Juju). |
795 | - content = ContentFile(file_object.read()) |
796 | - |
797 | - storage = self.get_existing_storage(filename) |
798 | - if storage is None: |
799 | - storage = FileStorage(filename=filename) |
800 | - storage.data.save(filename, content) |
801 | + # not expected. |
802 | + content = Bin(file_object.read()) |
803 | + storage, created = self.get_or_create( |
804 | + filename=filename, defaults={'content': content}) |
805 | + if not created: |
806 | + storage.content = content |
807 | + storage.save() |
808 | return storage |
809 | |
810 | - def list_stored_files(self): |
811 | - """Find the files stored in the filesystem.""" |
812 | - dirs, files = FileStorage.storage.listdir(FileStorage.upload_dir) |
813 | - return [ |
814 | - os.path.join(FileStorage.upload_dir, filename) |
815 | - for filename in files] |
816 | - |
817 | - def list_referenced_files(self): |
818 | - """Find the names of files that are referenced from `FileStorage`. |
819 | - |
820 | - :return: All file paths within MEDIA ROOT (relative to MEDIA_ROOT) |
821 | - that have `FileStorage` entries referencing them. |
822 | - :rtype: frozenset |
823 | - """ |
824 | - return frozenset( |
825 | - file_storage.data.name |
826 | - for file_storage in self.all()) |
827 | - |
828 | - def is_old(self, storage_filename): |
829 | - """Is the named file in the filesystem storage old enough to be dead? |
830 | - |
831 | - :param storage_filename: The name under which the file is stored in |
832 | - the filesystem, relative to MEDIA_ROOT. This need not be the |
833 | - same name as its filename as stored in the `FileStorage` object. |
834 | - It includes the name of the upload directory. |
835 | - """ |
836 | - file_path = os.path.join(settings.MEDIA_ROOT, storage_filename) |
837 | - mtime = os.stat(file_path).st_mtime |
838 | - expiry = mtime + self.grace_time |
839 | - return expiry <= time.time() |
840 | - |
841 | - def collect_garbage(self): |
842 | - """Clean up stored files that are no longer accessible.""" |
843 | - # Avoid circular imports. |
844 | - from maasserver.models import logger |
845 | - |
846 | - try: |
847 | - stored_files = self.list_stored_files() |
848 | - except OSError as e: |
849 | - if e.errno != ENOENT: |
850 | - raise |
851 | - logger.info( |
852 | - "Upload directory does not exist yet. " |
853 | - "Skipping garbage collection.") |
854 | - return |
855 | - referenced_files = self.list_referenced_files() |
856 | - for path in stored_files: |
857 | - if path not in referenced_files and self.is_old(path): |
858 | - FileStorage.storage.delete(path) |
859 | - |
860 | |
861 | class FileStorage(CleanSave, Model): |
862 | """A simple file storage keyed on file name. |
863 | |
864 | :ivar filename: A unique file name to use for the data being stored. |
865 | - :ivar data: The file's actual data. |
866 | + :ivar content: The file's actual data. |
867 | """ |
868 | |
869 | class Meta(DefaultMeta): |
870 | """Needed for South to recognize this model.""" |
871 | |
872 | - storage = FileSystemStorage() |
873 | - |
874 | - upload_dir = "storage" |
875 | - |
876 | - # Unix filenames can be longer than this (e.g. 255 bytes), but leave |
877 | - # some extra room for the full path, as well as a versioning suffix. |
878 | - filename = CharField(max_length=200, unique=True, editable=False) |
879 | - data = FileField(upload_to=upload_dir, storage=storage, max_length=255) |
880 | + filename = CharField(max_length=255, unique=True, editable=False) |
881 | + content = BinaryField(null=False) |
882 | |
883 | objects = FileStorageManager() |
884 | |
885 | |
886 | === modified file 'src/maasserver/testing/factory.py' |
887 | --- src/maasserver/testing/factory.py 2012-10-24 14:53:02 +0000 |
888 | +++ src/maasserver/testing/factory.py 2012-10-26 08:37:31 +0000 |
889 | @@ -296,13 +296,13 @@ |
890 | admin.save() |
891 | return admin |
892 | |
893 | - def make_file_storage(self, filename=None, data=None): |
894 | + def make_file_storage(self, filename=None, content=None): |
895 | if filename is None: |
896 | filename = self.getRandomString(100) |
897 | - if data is None: |
898 | - data = self.getRandomString(1024).encode('ascii') |
899 | + if content is None: |
900 | + content = self.getRandomString(1024).encode('ascii') |
901 | |
902 | - return FileStorage.objects.save_file(filename, BytesIO(data)) |
903 | + return FileStorage.objects.save_file(filename, BytesIO(content)) |
904 | |
905 | def make_oauth_header(self, **kwargs): |
906 | """Fake an OAuth authorization header. |
907 | |
908 | === modified file 'src/maasserver/tests/test_api.py' |
909 | --- src/maasserver/tests/test_api.py 2012-10-26 07:59:12 +0000 |
910 | +++ src/maasserver/tests/test_api.py 2012-10-26 08:37:31 +0000 |
911 | @@ -2515,7 +2515,8 @@ |
912 | class AnonymousFileStorageAPITest(FileStorageAPITestMixin, AnonAPITestCase): |
913 | |
914 | def test_get_works_anonymously(self): |
915 | - factory.make_file_storage(filename="foofilers", data=b"give me rope") |
916 | + factory.make_file_storage( |
917 | + filename="foofilers", content=b"give me rope") |
918 | response = self.make_API_GET_request("get", "foofilers") |
919 | |
920 | self.assertEqual(httplib.OK, response.status_code) |
921 | @@ -2585,7 +2586,8 @@ |
922 | self.assertEqual("file two", response.content) |
923 | |
924 | def test_get_file_succeeds(self): |
925 | - factory.make_file_storage(filename="foofilers", data=b"give me rope") |
926 | + factory.make_file_storage( |
927 | + filename="foofilers", content=b"give me rope") |
928 | response = self.make_API_GET_request("get", "foofilers") |
929 | |
930 | self.assertEqual(httplib.OK, response.status_code) |
931 | |
932 | === modified file 'src/maasserver/tests/test_commands.py' |
933 | --- src/maasserver/tests/test_commands.py 2012-08-24 10:28:29 +0000 |
934 | +++ src/maasserver/tests/test_commands.py 2012-10-26 08:37:31 +0000 |
935 | @@ -14,13 +14,10 @@ |
936 | |
937 | from codecs import getwriter |
938 | from io import BytesIO |
939 | -import os |
940 | |
941 | -from django.conf import settings |
942 | from django.contrib.auth.models import User |
943 | from django.core.cache import cache |
944 | from django.core.management import call_command |
945 | -from maasserver.models import FileStorage |
946 | from maasserver.testing.factory import factory |
947 | from maasserver.utils.orm import get_one |
948 | from maastesting.djangotestcase import DjangoTestCase |
949 | @@ -33,14 +30,6 @@ |
950 | in a command's code, it should be extracted and unit-tested separately. |
951 | """ |
952 | |
953 | - def test_gc(self): |
954 | - upload_dir = os.path.join(settings.MEDIA_ROOT, FileStorage.upload_dir) |
955 | - os.makedirs(upload_dir) |
956 | - self.addCleanup(os.removedirs, upload_dir) |
957 | - call_command('gc') |
958 | - # The test is that we get here without errors. |
959 | - pass |
960 | - |
961 | def test_generate_api_doc(self): |
962 | out = BytesIO() |
963 | stdout = getwriter("UTF-8")(out) |
964 | |
965 | === modified file 'src/maasserver/tests/test_filestorage.py' |
966 | --- src/maasserver/tests/test_filestorage.py 2012-06-25 09:03:14 +0000 |
967 | +++ src/maasserver/tests/test_filestorage.py 2012-10-26 08:37:31 +0000 |
968 | @@ -14,45 +14,15 @@ |
969 | |
970 | import codecs |
971 | from io import BytesIO |
972 | -import os |
973 | -import shutil |
974 | |
975 | -from django.conf import settings |
976 | from maasserver.models import FileStorage |
977 | from maasserver.testing.factory import factory |
978 | from maasserver.testing.testcase import TestCase |
979 | -from maastesting.utils import age_file |
980 | -from testtools.matchers import ( |
981 | - GreaterThan, |
982 | - LessThan, |
983 | - ) |
984 | |
985 | |
986 | class FileStorageTest(TestCase): |
987 | """Testing of the :class:`FileStorage` model.""" |
988 | |
989 | - def make_upload_dir(self): |
990 | - """Create the upload directory, and arrange for eventual deletion. |
991 | - |
992 | - The directory must not already exist. If it does, this method will |
993 | - fail rather than arrange for deletion of a directory that may |
994 | - contain meaningful data. |
995 | - |
996 | - :return: Absolute path to the `FileStorage` upload directory. This |
997 | - is the directory where the actual files are stored. |
998 | - """ |
999 | - media_root = settings.MEDIA_ROOT |
1000 | - self.assertFalse(os.path.exists(media_root), "See media/README") |
1001 | - self.addCleanup(shutil.rmtree, media_root, ignore_errors=True) |
1002 | - os.mkdir(media_root) |
1003 | - upload_dir = os.path.join(media_root, FileStorage.upload_dir) |
1004 | - os.mkdir(upload_dir) |
1005 | - return upload_dir |
1006 | - |
1007 | - def get_media_path(self, filename): |
1008 | - """Get the path to a given stored file, relative to MEDIA_ROOT.""" |
1009 | - return os.path.join(FileStorage.upload_dir, filename) |
1010 | - |
1011 | def make_data(self, including_text='data'): |
1012 | """Return arbitrary data. |
1013 | |
1014 | @@ -67,40 +37,24 @@ |
1015 | text = "%s %s" % (including_text, factory.getRandomString()) |
1016 | return text.encode('ascii') |
1017 | |
1018 | - def test_get_existing_storage_returns_None_if_none_found(self): |
1019 | - nonexistent_file = factory.getRandomString() |
1020 | - self.assertIsNone( |
1021 | - FileStorage.objects.get_existing_storage(nonexistent_file)) |
1022 | - |
1023 | - def test_get_existing_storage_finds_FileStorage(self): |
1024 | - self.make_upload_dir() |
1025 | - storage = factory.make_file_storage() |
1026 | - self.assertEqual( |
1027 | - storage, |
1028 | - FileStorage.objects.get_existing_storage(storage.filename)) |
1029 | - |
1030 | def test_save_file_creates_storage(self): |
1031 | - self.make_upload_dir() |
1032 | filename = factory.getRandomString() |
1033 | - data = self.make_data() |
1034 | - storage = FileStorage.objects.save_file(filename, BytesIO(data)) |
1035 | + content = self.make_data() |
1036 | + storage = FileStorage.objects.save_file(filename, BytesIO(content)) |
1037 | self.assertEqual( |
1038 | - (filename, data), |
1039 | - (storage.filename, storage.data.read())) |
1040 | + (filename, content), |
1041 | + (storage.filename, storage.content)) |
1042 | |
1043 | def test_storage_can_be_retrieved(self): |
1044 | - self.make_upload_dir() |
1045 | filename = factory.getRandomString() |
1046 | - data = self.make_data() |
1047 | - factory.make_file_storage(filename=filename, data=data) |
1048 | + content = self.make_data() |
1049 | + factory.make_file_storage(filename=filename, content=content) |
1050 | storage = FileStorage.objects.get(filename=filename) |
1051 | self.assertEqual( |
1052 | - (filename, data), |
1053 | - (storage.filename, storage.data.read())) |
1054 | + (filename, content), |
1055 | + (storage.filename, storage.content)) |
1056 | |
1057 | def test_stores_binary_data(self): |
1058 | - self.make_upload_dir() |
1059 | - |
1060 | # This horrible binary data could never, ever, under any |
1061 | # encoding known to man be interpreted as text(1). Switch the |
1062 | # bytes of the byte-order mark around and by design you get an |
1063 | @@ -114,121 +68,19 @@ |
1064 | |
1065 | # And yet, because FileStorage supports binary data, it comes |
1066 | # out intact. |
1067 | - storage = factory.make_file_storage(filename="x", data=binary_data) |
1068 | - self.assertEqual(binary_data, storage.data.read()) |
1069 | + storage = factory.make_file_storage(filename="x", content=binary_data) |
1070 | + self.assertEqual(binary_data, storage.content) |
1071 | |
1072 | def test_overwrites_file(self): |
1073 | # If a file of the same name has already been stored, the |
1074 | # reference to the old data gets overwritten with one to the new |
1075 | - # data. They are actually different files on the filesystem. |
1076 | - self.make_upload_dir() |
1077 | + # data. |
1078 | filename = factory.make_name('filename') |
1079 | old_storage = factory.make_file_storage( |
1080 | - filename=filename, data=self.make_data('old data')) |
1081 | + filename=filename, content=self.make_data('old data')) |
1082 | new_data = self.make_data('new-data') |
1083 | new_storage = factory.make_file_storage( |
1084 | - filename=filename, data=new_data) |
1085 | - self.assertNotEqual(old_storage.data.name, new_storage.data.name) |
1086 | + filename=filename, content=new_data) |
1087 | + self.assertEqual(old_storage.filename, new_storage.filename) |
1088 | self.assertEqual( |
1089 | - new_data, FileStorage.objects.get(filename=filename).data.read()) |
1090 | - |
1091 | - def test_list_stored_files_lists_files(self): |
1092 | - filename = factory.getRandomString() |
1093 | - factory.make_file( |
1094 | - location=self.make_upload_dir(), name=filename, |
1095 | - contents=self.make_data()) |
1096 | - self.assertIn( |
1097 | - self.get_media_path(filename), |
1098 | - FileStorage.objects.list_stored_files()) |
1099 | - |
1100 | - def test_list_stored_files_includes_referenced_files(self): |
1101 | - self.make_upload_dir() |
1102 | - storage = factory.make_file_storage() |
1103 | - self.assertIn( |
1104 | - storage.data.name, FileStorage.objects.list_stored_files()) |
1105 | - |
1106 | - def test_list_referenced_files_lists_FileStorage_files(self): |
1107 | - self.make_upload_dir() |
1108 | - storage = factory.make_file_storage() |
1109 | - self.assertIn( |
1110 | - storage.data.name, FileStorage.objects.list_referenced_files()) |
1111 | - |
1112 | - def test_list_referenced_files_excludes_unreferenced_files(self): |
1113 | - filename = factory.getRandomString() |
1114 | - factory.make_file( |
1115 | - location=self.make_upload_dir(), name=filename, |
1116 | - contents=self.make_data()) |
1117 | - self.assertNotIn( |
1118 | - self.get_media_path(filename), |
1119 | - FileStorage.objects.list_referenced_files()) |
1120 | - |
1121 | - def test_list_referenced_files_uses_file_name_not_FileStorage_name(self): |
1122 | - self.make_upload_dir() |
1123 | - filename = factory.getRandomString() |
1124 | - # The filename we're going to use is already taken. The file |
1125 | - # we'll be looking at will have to have a different name. |
1126 | - factory.make_file_storage(filename=filename) |
1127 | - storage = factory.make_file_storage(filename=filename) |
1128 | - # It's the name of the file, not the FileStorage.filename, that |
1129 | - # is in list_referenced_files. |
1130 | - self.assertIn( |
1131 | - storage.data.name, FileStorage.objects.list_referenced_files()) |
1132 | - |
1133 | - def test_is_old_returns_False_for_recent_file(self): |
1134 | - filename = factory.getRandomString() |
1135 | - path = factory.make_file( |
1136 | - location=self.make_upload_dir(), name=filename, |
1137 | - contents=self.make_data()) |
1138 | - age_file(path, FileStorage.objects.grace_time - 60) |
1139 | - self.assertFalse( |
1140 | - FileStorage.objects.is_old(self.get_media_path(filename))) |
1141 | - |
1142 | - def test_is_old_returns_True_for_old_file(self): |
1143 | - filename = factory.getRandomString() |
1144 | - path = factory.make_file( |
1145 | - location=self.make_upload_dir(), name=filename, |
1146 | - contents=self.make_data()) |
1147 | - age_file(path, FileStorage.objects.grace_time + 1) |
1148 | - self.assertTrue( |
1149 | - FileStorage.objects.is_old(self.get_media_path(filename))) |
1150 | - |
1151 | - def test_collect_garbage_deletes_garbage(self): |
1152 | - filename = factory.getRandomString() |
1153 | - path = factory.make_file( |
1154 | - location=self.make_upload_dir(), name=filename, |
1155 | - contents=self.make_data()) |
1156 | - age_file(path, FileStorage.objects.grace_time + 1) |
1157 | - FileStorage.objects.collect_garbage() |
1158 | - self.assertFalse( |
1159 | - FileStorage.storage.exists(self.get_media_path(filename))) |
1160 | - |
1161 | - def test_grace_time_is_generous_but_not_unlimited(self): |
1162 | - # Grace time for garbage collection is long enough that it won't |
1163 | - # expire while the request that wrote it is still being handled. |
1164 | - # But it won't keep a file around for ages. For instance, it'll |
1165 | - # be more than 20 seconds, but less than a day. |
1166 | - self.assertThat(FileStorage.objects.grace_time, GreaterThan(20)) |
1167 | - self.assertThat(FileStorage.objects.grace_time, LessThan(24 * 60 * 60)) |
1168 | - |
1169 | - def test_collect_garbage_leaves_recent_files_alone(self): |
1170 | - filename = factory.getRandomString() |
1171 | - factory.make_file( |
1172 | - location=self.make_upload_dir(), name=filename, |
1173 | - contents=self.make_data()) |
1174 | - FileStorage.objects.collect_garbage() |
1175 | - self.assertTrue( |
1176 | - FileStorage.storage.exists(self.get_media_path(filename))) |
1177 | - |
1178 | - def test_collect_garbage_leaves_referenced_files_alone(self): |
1179 | - self.make_upload_dir() |
1180 | - storage = factory.make_file_storage() |
1181 | - age_file(storage.data.path, FileStorage.objects.grace_time + 1) |
1182 | - FileStorage.objects.collect_garbage() |
1183 | - self.assertTrue(FileStorage.storage.exists(storage.data.name)) |
1184 | - |
1185 | - def test_collect_garbage_tolerates_missing_upload_dir(self): |
1186 | - # When MAAS is freshly installed, the upload directory is still |
1187 | - # missing. But... |
1188 | - FileStorage.objects.collect_garbage() |
1189 | - # ...we get through garbage collection without breakage. |
1190 | - pass |
1191 | + new_data, FileStorage.objects.get(filename=filename).content) |