~ubuntu-branches/ubuntu/raring/maas/raring-proposed

« back to all changes in this revision

Viewing changes to src/maasserver/models/filestorage.py

  • Committer: Package Import Robot
  • Author(s): Andres Rodriguez, Raphaël Badin, Julian Edwards, Andres Rodriguez, Gavin Panella, Jeroen Vermeulen
  • Date: 2012-11-13 14:58:21 UTC
  • mfrom: (1.2.2)
  • Revision ID: package-import@ubuntu.com-20121113145821-6jq53jtljo3qou80
Tags: 1.2+bzr1349+dfsg-0ubuntu1
* New upstream bugfix release. Fixes:
  - The DNS configuration is not created if maas-dns is installed after
    the DNS config has been set up (LP: #1085865).
  - IPMI detection ends up with power_address of 0.0.0.0 (LP: #1064224)
  - Main page slow to load with many nodes (LP: #1066775)
  - maas-cluster-controller doesn't have images for
    provisioning (LP: #1068843)
  - Filestorage is unique to each appserver instance (LP: #1069734)
  - import_pxe_files does not include quantal (LP: #1069850)
  - maas-cli nodes new incomplete documentation (LP: #1070522)
  - DNS forward zone ends up with nonsensical entries (LP: #1070765)
  - The hostname of a node can still be changed once the node is in
    use. (LP: #1070774)
  - The zone name (attached to a cluster controller) can still be changed
    when it contains in-use nodes and DNS is managed. (LP: #1070775)
  - Duplicated prefix in the url used by the CLI (LP: #1075597)
  - Not importing Quantal boot images (LP: #1077180)
  - Nodes are deployed with wrong domain name. (LP: #1078744)
  - src/maasserver/api.py calls request.data.getlist with a 'default'
    parameter. That parameter is not supported by Django 1.3. (LP: #1080673)
  - API calls that return a node leak private data (LP: #1034318)
  - MAAS hostnames should be 5 easily disambiguated characters (LP: #1058998)
  - URI in API description wrong when accessing machine via alternative
    interface. (LP: #1059645)
  - Oops when renaming nodegroup w/o interface (LP: #1077075)
  - Error in log when using 'Start node' button: MAASAPINotFound: No user
    data available for this node. (LP: #1069603)

[ Raphaël Badin ]
* debian/maas-dns.postinst: Call write_dns_config (LP: #1085865).
* debian/maas-dns.postinst: fix permissions and group ownership of
  file /etc/bind/maas/named.conf.rndc.maas. (LP: #1066935)

[ Julian Edwards ]
* debian/maas-region-controller.install: Remove installation of maas-gc; it
  is no longer required as upstream no longer stores files in the filesystem.
  (LP: #1069734)
* debian/maas-cluster-controller.postinst: Ensure that /etc/maas/pserv.yaml
  is updated when reconfiguring. (LP: #1081212)

[ Andres Rodriguez ]
* debian/control:
  - maas-cluster-controller Conflicts with tftpd-hpa (LP: #1076028)
  - maas-dns: Conflicts with dnsmasq
  - Drop Dependency on rabbitmq-server for maas-cluster-controller.
    (LP: #1072744)
  - Add conflicts/replaces for maas-region-controller to
    maas-cluster-controller.
* debian/maas-cluster-controller.config: If URL has been detected, add
  /MAAS if it doesn't contain it. This helps upgrades from versions where
  DEFAULT_MAAS_URL didn't use /MAAS.
* Install maas-import-pxe-files and related files with
  maas-cluster-controller, as well as configure tgtd, as
  maas-region-controller no longer stores images. Thanks to Jeroen
  Vermuelen.

[ Gavin Panella ]
* debian/extras/99-maas: squashfs image download is no longer needed.
* debian/maas-cluster-controller.install: maas-import-squashfs and its
  configuration file are no longer part of upstream.

[ Jeroen Vermeulen ]
* debian/maas-cluster-controller.maas-pserv.upstart: Source maas_cluster.conf
  before starting pserv (tftpd) process.
* debian/maas-cluster-controller.postinst: Duplicate CLUSTER_UUID setting
  to maas_cluster.conf.
* Bumped revision number to current 1.2 revision 1342 (requested by rvba).

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
    ]
16
16
 
17
17
 
18
 
from errno import ENOENT
19
 
import os
20
 
import time
21
 
 
22
 
from django.conf import settings
23
 
from django.core.files.base import ContentFile
24
 
from django.core.files.storage import FileSystemStorage
25
18
from django.db.models import (
26
19
    CharField,
27
 
    FileField,
28
20
    Manager,
29
21
    Model,
30
22
    )
31
23
from maasserver import DefaultMeta
32
24
from maasserver.models.cleansave import CleanSave
33
 
from maasserver.utils.orm import get_one
 
25
from metadataserver.fields import (
 
26
    Bin,
 
27
    BinaryField,
 
28
    )
34
29
 
35
30
 
36
31
class FileStorageManager(Manager):
47
42
    original file will not be affected.  Also, any ongoing reads from the
48
43
    old file will continue without iterruption.
49
44
    """
50
 
    # The time, in seconds, that an unreferenced file is allowed to
51
 
    # persist in order to satisfy ongoing requests.
52
 
    grace_time = 12 * 60 * 60
53
 
 
54
 
    def get_existing_storage(self, filename):
55
 
        """Return an existing `FileStorage` of this name, or None."""
56
 
        return get_one(self.filter(filename=filename))
57
45
 
58
46
    def save_file(self, filename, file_object):
59
 
        """Save the file to the filesystem and persist to the database.
60
 
 
61
 
        The file will end up in MEDIA_ROOT/storage/
 
47
        """Save the file to the database.
62
48
 
63
49
        If a file of that name already existed, it will be replaced by the
64
50
        new contents.
65
51
        """
66
52
        # This probably ought to read in chunks but large files are
67
 
        # not expected.  Also note that uploading a file with the same
68
 
        # name as an existing one will cause that file to be written
69
 
        # with a new generated name, and the old one remains where it
70
 
        # is.  See https://code.djangoproject.com/ticket/6157 - the
71
 
        # Django devs consider deleting things dangerous ... ha.
72
 
        # HOWEVER - this operation would need to be atomic anyway so
73
 
        # it's safest left how it is for now (reads can overlap with
74
 
        # writes from Juju).
75
 
        content = ContentFile(file_object.read())
76
 
 
77
 
        storage = self.get_existing_storage(filename)
78
 
        if storage is None:
79
 
            storage = FileStorage(filename=filename)
80
 
        storage.data.save(filename, content)
 
53
        # not expected.
 
54
        content = Bin(file_object.read())
 
55
        storage, created = self.get_or_create(
 
56
            filename=filename, defaults={'content': content})
 
57
        if not created:
 
58
            storage.content = content
 
59
            storage.save()
81
60
        return storage
82
61
 
83
 
    def list_stored_files(self):
84
 
        """Find the files stored in the filesystem."""
85
 
        dirs, files = FileStorage.storage.listdir(FileStorage.upload_dir)
86
 
        return [
87
 
            os.path.join(FileStorage.upload_dir, filename)
88
 
            for filename in files]
89
 
 
90
 
    def list_referenced_files(self):
91
 
        """Find the names of files that are referenced from `FileStorage`.
92
 
 
93
 
        :return: All file paths within MEDIA ROOT (relative to MEDIA_ROOT)
94
 
            that have `FileStorage` entries referencing them.
95
 
        :rtype: frozenset
96
 
        """
97
 
        return frozenset(
98
 
            file_storage.data.name
99
 
            for file_storage in self.all())
100
 
 
101
 
    def is_old(self, storage_filename):
102
 
        """Is the named file in the filesystem storage old enough to be dead?
103
 
 
104
 
        :param storage_filename: The name under which the file is stored in
105
 
            the filesystem, relative to MEDIA_ROOT.  This need not be the
106
 
            same name as its filename as stored in the `FileStorage` object.
107
 
            It includes the name of the upload directory.
108
 
        """
109
 
        file_path = os.path.join(settings.MEDIA_ROOT, storage_filename)
110
 
        mtime = os.stat(file_path).st_mtime
111
 
        expiry = mtime + self.grace_time
112
 
        return expiry <= time.time()
113
 
 
114
 
    def collect_garbage(self):
115
 
        """Clean up stored files that are no longer accessible."""
116
 
        # Avoid circular imports.
117
 
        from maasserver.models import logger
118
 
 
119
 
        try:
120
 
            stored_files = self.list_stored_files()
121
 
        except OSError as e:
122
 
            if e.errno != ENOENT:
123
 
                raise
124
 
            logger.info(
125
 
                "Upload directory does not exist yet.  "
126
 
                "Skipping garbage collection.")
127
 
            return
128
 
        referenced_files = self.list_referenced_files()
129
 
        for path in stored_files:
130
 
            if path not in referenced_files and self.is_old(path):
131
 
                FileStorage.storage.delete(path)
132
 
 
133
62
 
134
63
class FileStorage(CleanSave, Model):
135
64
    """A simple file storage keyed on file name.
136
65
 
137
66
    :ivar filename: A unique file name to use for the data being stored.
138
 
    :ivar data: The file's actual data.
 
67
    :ivar content: The file's actual data.
139
68
    """
140
69
 
141
70
    class Meta(DefaultMeta):
142
71
        """Needed for South to recognize this model."""
143
72
 
144
 
    storage = FileSystemStorage()
145
 
 
146
 
    upload_dir = "storage"
147
 
 
148
 
    # Unix filenames can be longer than this (e.g. 255 bytes), but leave
149
 
    # some extra room for the full path, as well as a versioning suffix.
150
 
    filename = CharField(max_length=200, unique=True, editable=False)
151
 
    data = FileField(upload_to=upload_dir, storage=storage, max_length=255)
 
73
    filename = CharField(max_length=255, unique=True, editable=False)
 
74
    content = BinaryField(null=False)
152
75
 
153
76
    objects = FileStorageManager()
154
77