-
Notifications
You must be signed in to change notification settings - Fork 23
/
store.py
executable file
·287 lines (231 loc) · 9.01 KB
/
store.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
#! /usr/bin/env python
#
"""
"""
# Copyright (C) 2011-2018 University of Zurich. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# stdlib imports
from abc import ABCMeta, abstractmethod
# GC3Pie imports
import gc3libs
from gc3libs.url import Url
__docformat__ = 'reStructuredText'
class Store(object):
"""
Interface for storing and retrieving objects on permanent storage.
Each `save` operation returns a unique "ID"; each ID is a Python
string value, which is guaranteed to be temporally unique, i.e.,
no two `save` operations in the same persistent store can result
in the same IDs being assigned to different objects. The "ID" is
also stored in the instance attribute `_id`.
Any Python object can stored, provided it meets the following
conditions:
* it can be pickled with Python's standard module `pickle`.
* the instance attribute `persistent_id` is reserved for use by
the `Store` class: it should not be set or altered by other
parts of the code.
"""
__metaclass__ = ABCMeta
def __init__(self, url=None):
if url and not isinstance(url, Url):
url = Url(url)
self.url = url
def pre_fork(self):
"""
Make preparations for `fork()`ing the current process.
This should close open network connections or any other
sockets or file descriptors that cannot be used by both the
parent and child process.
The default implementation of this method does nothing; as of
2018-04-10, the only subclass making use of this functionality
is `SqlStore`:class:, which needs to dispose the SQLAlchemy
engine and re-create it after forking.
"""
pass
def post_fork(self):
"""
Restore functionality that was suspended in `pre_fork`:meth:
This method will be called after forking/daemonizing has been
successfully accomplished.
The default implementation of this method does nothing.
"""
pass
def list(self, **extra_args):
"""
Return list of IDs of saved `Job` objects.
This is an optional method; classes that do not implement it
should raise a `NotImplementedError` exception.
"""
raise NotImplementedError(
"Method `list` not implemented in this class.")
@abstractmethod
def remove(self, id_):
"""
Delete a given object from persistent storage, given its ID.
"""
pass
@abstractmethod
def replace(self, id_, obj):
"""
Replace the object already saved with the given ID with a copy
of `obj`.
"""
pass
@abstractmethod
def load(self, id_):
"""
Load a saved object given its ID, and return it.
"""
pass
def _update_to_latest_schema(self):
"""
Modify an object in-place to reflect changes in the schema.
Called immediately after a successful `load()`, just before returning
the retrieved object to the caller.
"""
# FIXME: remove after 2.5 release cycle
if isinstance(self, gc3libs.Application):
if hasattr(self, '_lrms_vm_id'):
# already updated, skip
pass
elif hasattr(self, 'os_instance_id'):
self.execution._lrms_vm_id = self.os_instance_id
del self.os_instance_id
elif hasattr(self, 'ec2_instance_id'):
self.execution._lrms_vm_id = self.ec2_instance_id
del self.ec2_instance_id
elif hasattr(self.execution, 'os_instance_id'):
self.execution._lrms_vm_id = self.execution.os_instance_id
del self.execution.os_instance_id
elif hasattr(self.execution, 'ec2_instance_id'):
self.execution._lrms_vm_id = self.execution.ec2_instance_id
del self.execution.ec2_instance_id
@abstractmethod
def save(self, obj):
"""
Save an object, and return an ID.
"""
pass
class Persistable(object):
"""
A mix-in class to mark that an object should be persisted by its ID.
Any instance of this class is saved as an 'external reference'
when a container holding a reference to it is saved.
"""
def __init__(self, *args, **kwargs):
# ensure object will be saved next time Store.save() is invoked
self.changed = True
def __str__(self):
try:
return str(self.persistent_id)
except AttributeError:
return super(Persistable, self).__str__()
def __eq__(self, other):
try:
return self.persistent_id == other.persistent_id
except AttributeError:
# fall back to Python object comparison
return super(Persistable, self) == other
def __ne__(self, other):
return not self.__eq__(other)
# registration mechanism
_registered_store_ctors = {}
def register(scheme, constructor):
"""
Register `constructor` as the factory corresponding to an URL scheme.
If a different constructor is already registered for the same
scheme, it is silently overwritten.
The registry mapping schemes to constructors is used in the
`make_store`:func: to create concrete instances of
`gc3libs.persistence.Store`, given a URI that identifies the kind
and location of the storage.
:param str scheme: URL scheme to associate with the given constructor.
:param callable constructor: A callable returning a `Store`:class:
instance. Typically, a class constructor.
"""
global _registered_store_ctors
assert callable(constructor), (
"Registering non-callable constructor for scheme "
"'%s' in `gc3libs.persistence.register`"
% scheme)
gc3libs.log.debug(
"Registering scheme '%s' with the `gc3libs.persistence` registry.",
scheme)
_registered_store_ctors[str(scheme)] = constructor
def make_store(uri, *args, **extra_args):
"""
Factory producing concrete `Store`:class: instances.
Given a URL and (optionally) initialization arguments, return a
fully-constructed `Store`:class: instance.
The only required argument is `uri`; if any other arguments are
present in the function invocation, they are passed verbatim to
the constructor associated with the scheme of the given `uri`.
Example::
>>> fs1 = make_store('file:///tmp')
>>> fs1.__class__.__name__
'FilesystemStore'
Argument `uri` can also consist of a path name, in which case a
URL scheme 'file:///' is assumed::
>>> fs2 = make_store('/tmp')
>>> fs2.__class__.__name__
'FilesystemStore'
"""
if not isinstance(uri, Url):
uri = Url(uri)
# since SQLAlchemy allows URIs of the form `db+driver://...`
# (e.g., `postresql+psycopg://...`) we need to examine the URI
# scheme only up to the first `+`
scheme = uri.scheme.split('+')[0]
try:
# hard-code schemes that are supported by GC3Pie itself
if uri.scheme == 'file':
import gc3libs.persistence.filesystem
return gc3libs.persistence.filesystem.make_filesystemstore(
uri, *args, **extra_args)
elif scheme in [
# DBs supported in SQLAlchemy core as of version 1.1,
# see: http://docs.sqlalchemy.org/en/latest/dialects/index.html
'firebird',
'mssql',
'mysql',
'oracle',
'postgresql',
'sqlite',
'sybase',
]:
import gc3libs.persistence.sql
return gc3libs.persistence.sql.make_sqlstore(
uri, *args, **extra_args)
else:
try:
return _registered_store_ctors[
uri.scheme](uri, *args, **extra_args)
except KeyError:
gc3libs.log.error(
"Unknown URL scheme '%s' in"
" `gc3libs.persistence.make_store`:"
" has never been registered.", uri.scheme)
raise
except Exception as err:
gc3libs.log.error(
"Error constructing store for URL '%s': %s: %s",
uri, err.__class__.__name__, err)
raise
# main: run tests
if "__main__" == __name__:
import doctest
doctest.testmod(name="store",
optionflags=doctest.NORMALIZE_WHITESPACE)