~ubuntu-branches/ubuntu/trusty/swift/trusty-updates

« back to all changes in this revision

Viewing changes to swift/obj/base.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short, James Page, Chuck Short
  • Date: 2013-08-13 10:37:13 UTC
  • mfrom: (1.2.21)
  • Revision ID: package-import@ubuntu.com-20130813103713-1ctbx4zifyljs2aq
Tags: 1.9.1-0ubuntu1
[ James Page ]
* d/control: Update VCS fields for new branch locations.

[ Chuck Short ]
* New upstream release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (c) 2010-2013 OpenStack, LLC.
2
 
#
3
 
# Licensed under the Apache License, Version 2.0 (the "License");
4
 
# you may not use this file except in compliance with the License.
5
 
# You may obtain a copy of the License at
6
 
#
7
 
#    http://www.apache.org/licenses/LICENSE-2.0
8
 
#
9
 
# Unless required by applicable law or agreed to in writing, software
10
 
# distributed under the License is distributed on an "AS IS" BASIS,
11
 
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12
 
# implied.
13
 
# See the License for the specific language governing permissions and
14
 
# limitations under the License.
15
 
 
16
 
"""General object server functions."""
17
 
 
18
 
import cPickle as pickle
19
 
import errno
20
 
import hashlib
21
 
import logging
22
 
import os
23
 
import time
24
 
import uuid
25
 
from os.path import basename, dirname, join
26
 
 
27
 
from swift.common.exceptions import PathNotDir
28
 
from swift.common.utils import lock_path, renamer, write_pickle
29
 
 
30
 
 
31
 
PICKLE_PROTOCOL = 2
32
 
ONE_WEEK = 604800
33
 
HASH_FILE = 'hashes.pkl'
34
 
 
35
 
 
36
 
def quarantine_renamer(device_path, corrupted_file_path):
37
 
    """
38
 
    In the case that a file is corrupted, move it to a quarantined
39
 
    area to allow replication to fix it.
40
 
 
41
 
    :params device_path: The path to the device the corrupted file is on.
42
 
    :params corrupted_file_path: The path to the file you want quarantined.
43
 
 
44
 
    :returns: path (str) of directory the file was moved to
45
 
    :raises OSError: re-raises non errno.EEXIST / errno.ENOTEMPTY
46
 
                     exceptions from rename
47
 
    """
48
 
    from_dir = dirname(corrupted_file_path)
49
 
    to_dir = join(device_path, 'quarantined', 'objects', basename(from_dir))
50
 
    invalidate_hash(dirname(from_dir))
51
 
    try:
52
 
        renamer(from_dir, to_dir)
53
 
    except OSError, e:
54
 
        if e.errno not in (errno.EEXIST, errno.ENOTEMPTY):
55
 
            raise
56
 
        to_dir = "%s-%s" % (to_dir, uuid.uuid4().hex)
57
 
        renamer(from_dir, to_dir)
58
 
    return to_dir
59
 
 
60
 
 
61
 
def hash_suffix(path, reclaim_age):
62
 
    """
63
 
    Performs reclamation and returns an md5 of all (remaining) files.
64
 
 
65
 
    :param reclaim_age: age in seconds at which to remove tombstones
66
 
    :raises PathNotDir: if given path is not a valid directory
67
 
    :raises OSError: for non-ENOTDIR errors
68
 
    """
69
 
    md5 = hashlib.md5()
70
 
    try:
71
 
        path_contents = sorted(os.listdir(path))
72
 
    except OSError, err:
73
 
        if err.errno in (errno.ENOTDIR, errno.ENOENT):
74
 
            raise PathNotDir()
75
 
        raise
76
 
    for hsh in path_contents:
77
 
        hsh_path = join(path, hsh)
78
 
        try:
79
 
            files = os.listdir(hsh_path)
80
 
        except OSError, err:
81
 
            if err.errno == errno.ENOTDIR:
82
 
                partition_path = dirname(path)
83
 
                objects_path = dirname(partition_path)
84
 
                device_path = dirname(objects_path)
85
 
                quar_path = quarantine_renamer(device_path, hsh_path)
86
 
                logging.exception(
87
 
                    _('Quarantined %s to %s because it is not a directory') %
88
 
                    (hsh_path, quar_path))
89
 
                continue
90
 
            raise
91
 
        if len(files) == 1:
92
 
            if files[0].endswith('.ts'):
93
 
                # remove tombstones older than reclaim_age
94
 
                ts = files[0].rsplit('.', 1)[0]
95
 
                if (time.time() - float(ts)) > reclaim_age:
96
 
                    os.unlink(join(hsh_path, files[0]))
97
 
                    files.remove(files[0])
98
 
        elif files:
99
 
            files.sort(reverse=True)
100
 
            meta = data = tomb = None
101
 
            for filename in list(files):
102
 
                if not meta and filename.endswith('.meta'):
103
 
                    meta = filename
104
 
                if not data and filename.endswith('.data'):
105
 
                    data = filename
106
 
                if not tomb and filename.endswith('.ts'):
107
 
                    tomb = filename
108
 
                if (filename < tomb or       # any file older than tomb
109
 
                    filename < data or       # any file older than data
110
 
                    (filename.endswith('.meta') and
111
 
                     filename < meta)):      # old meta
112
 
                    os.unlink(join(hsh_path, filename))
113
 
                    files.remove(filename)
114
 
        if not files:
115
 
            os.rmdir(hsh_path)
116
 
        for filename in files:
117
 
            md5.update(filename)
118
 
    try:
119
 
        os.rmdir(path)
120
 
    except OSError:
121
 
        pass
122
 
    return md5.hexdigest()
123
 
 
124
 
 
125
 
def invalidate_hash(suffix_dir):
126
 
    """
127
 
    Invalidates the hash for a suffix_dir in the partition's hashes file.
128
 
 
129
 
    :param suffix_dir: absolute path to suffix dir whose hash needs
130
 
                       invalidating
131
 
    """
132
 
 
133
 
    suffix = os.path.basename(suffix_dir)
134
 
    partition_dir = os.path.dirname(suffix_dir)
135
 
    hashes_file = join(partition_dir, HASH_FILE)
136
 
    with lock_path(partition_dir):
137
 
        try:
138
 
            with open(hashes_file, 'rb') as fp:
139
 
                hashes = pickle.load(fp)
140
 
            if suffix in hashes and not hashes[suffix]:
141
 
                return
142
 
        except Exception:
143
 
            return
144
 
        hashes[suffix] = None
145
 
        write_pickle(hashes, hashes_file, partition_dir, PICKLE_PROTOCOL)
146
 
 
147
 
 
148
 
def get_hashes(partition_dir, recalculate=None, do_listdir=False,
149
 
               reclaim_age=ONE_WEEK):
150
 
    """
151
 
    Get a list of hashes for the suffix dir.  do_listdir causes it to mistrust
152
 
    the hash cache for suffix existence at the (unexpectedly high) cost of a
153
 
    listdir.  reclaim_age is just passed on to hash_suffix.
154
 
 
155
 
    :param partition_dir: absolute path of partition to get hashes for
156
 
    :param recalculate: list of suffixes which should be recalculated when got
157
 
    :param do_listdir: force existence check for all hashes in the partition
158
 
    :param reclaim_age: age at which to remove tombstones
159
 
 
160
 
    :returns: tuple of (number of suffix dirs hashed, dictionary of hashes)
161
 
    """
162
 
 
163
 
    hashed = 0
164
 
    hashes_file = join(partition_dir, HASH_FILE)
165
 
    modified = False
166
 
    force_rewrite = False
167
 
    hashes = {}
168
 
    mtime = -1
169
 
 
170
 
    if recalculate is None:
171
 
        recalculate = []
172
 
 
173
 
    try:
174
 
        with open(hashes_file, 'rb') as fp:
175
 
            hashes = pickle.load(fp)
176
 
        mtime = os.path.getmtime(hashes_file)
177
 
    except Exception:
178
 
        do_listdir = True
179
 
        force_rewrite = True
180
 
    if do_listdir:
181
 
        for suff in os.listdir(partition_dir):
182
 
            if len(suff) == 3:
183
 
                hashes.setdefault(suff, None)
184
 
        modified = True
185
 
    hashes.update((hash_, None) for hash_ in recalculate)
186
 
    for suffix, hash_ in hashes.items():
187
 
        if not hash_:
188
 
            suffix_dir = join(partition_dir, suffix)
189
 
            try:
190
 
                hashes[suffix] = hash_suffix(suffix_dir, reclaim_age)
191
 
                hashed += 1
192
 
            except PathNotDir:
193
 
                del hashes[suffix]
194
 
            except OSError:
195
 
                logging.exception(_('Error hashing suffix'))
196
 
            modified = True
197
 
    if modified:
198
 
        with lock_path(partition_dir):
199
 
            if force_rewrite or not os.path.exists(hashes_file) or \
200
 
                    os.path.getmtime(hashes_file) == mtime:
201
 
                write_pickle(
202
 
                    hashes, hashes_file, partition_dir, PICKLE_PROTOCOL)
203
 
                return hashed, hashes
204
 
        return get_hashes(partition_dir, recalculate, do_listdir,
205
 
                          reclaim_age)
206
 
    else:
207
 
        return hashed, hashes