1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
3
# Copyright [2010] [Anso Labs, LLC]
5
# Licensed under the Apache License, Version 2.0 (the "License");
6
# you may not use this file except in compliance with the License.
7
# You may obtain a copy of the License at
9
# http://www.apache.org/licenses/LICENSE-2.0
11
# Unless required by applicable law or agreed to in writing, software
12
# distributed under the License is distributed on an "AS IS" BASIS,
13
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
# See the License for the specific language governing permissions and
15
# limitations under the License.
20
Providers the Keeper class, a simple pseudo-dictionary that
23
MAKE Sure that ReDIS is running, and your flags are set properly,
24
before trying to run this.
32
from nova import vendor
35
from nova import flags
36
from nova import utils
40
flags.DEFINE_string('datastore_path', utils.abspath('../keeper'),
41
'where keys are stored on disk')
42
flags.DEFINE_string('redis_host', '127.0.0.1',
43
'Host that redis is running on.')
44
flags.DEFINE_integer('redis_port', 6379,
45
'Port that redis is running on.')
46
flags.DEFINE_integer('redis_db', 0, 'Multiple DB keeps tests away')
47
flags.DEFINE_string('keeper_backend', 'redis',
48
'which backend to use for keeper')
53
if hasattr(self.__class__, '_instance'):
54
raise Exception('Attempted to instantiate singleton')
58
if not hasattr(cls, '_instance'):
59
inst = redis.Redis(host=FLAGS.redis_host, port=FLAGS.redis_port, db=FLAGS.redis_db)
64
class RedisModel(object):
65
""" Wrapper around redis-backed properties """
66
object_type = 'generic'
67
def __init__(self, object_id):
68
""" loads an object from the datastore if exists """
69
self.object_id = object_id
70
self.initial_state = {}
71
self.state = Redis.instance().hgetall(self.__redis_key)
73
self.initial_state = self.state
75
self.set_default_state()
77
def set_default_state(self):
78
self.state = {'state' : 'pending'}
79
self.state[self.object_type+"_id"] = self.object_id
82
def __redis_key(self):
83
""" Magic string for instance keys """
84
return '%s:%s' % (self.object_type, self.object_id)
87
return "<%s:%s>" % (self.object_type, self.object_id)
90
return str(self.state)
93
return self.state.keys()
97
for item in self.keys():
98
copyDict[item] = self[item]
101
def get(self, item, default):
102
return self.state.get(item, default)
104
def __getitem__(self, item):
105
return self.state[item]
107
def __setitem__(self, item, val):
108
self.state[item] = val
109
return self.state[item]
111
def __delitem__(self, item):
112
""" We don't support this """
113
raise Exception("Silly monkey, we NEED all our properties.")
116
""" update the directory with the state from this instance """
117
# TODO(ja): implement hmset in redis-py and use it
118
# instead of multiple calls to hset
119
for key, val in self.state.iteritems():
120
# if (not self.initial_state.has_key(key)
121
# or self.initial_state[key] != val):
122
Redis.instance().hset(self.__redis_key, key, val)
123
if self.initial_state == {}:
125
self.initial_state = self.state
128
def first_save(self):
132
""" deletes all related records from datastore.
133
does NOT do anything to running state.
135
Redis.instance().delete(self.__redis_key)
139
def slugify(key, prefix=None):
141
Key has to be a valid filename. Slugify solves that.
143
return "%s%s" % (prefix, key)
146
class SqliteKeeper(object):
147
""" Keeper implementation in SQLite, mostly for in-memory testing """
148
_conn = {} # class variable
150
def __init__(self, prefix):
155
if self.prefix not in self.__class__._conn:
156
logging.debug('no sqlite connection (%s), making new', self.prefix)
157
if FLAGS.datastore_path != ':memory:':
159
os.mkdir(FLAGS.datastore_path)
162
conn = sqlite3.connect(os.path.join(
163
FLAGS.datastore_path, '%s.sqlite' % self.prefix))
165
conn = sqlite3.connect(':memory:')
169
c.execute('''CREATE TABLE data (item text, value text)''')
172
logging.exception('create table failed')
176
self.__class__._conn[self.prefix] = conn
178
return self.__class__._conn[self.prefix]
180
def __delitem__(self, item):
181
#logging.debug('sqlite deleting %s', item)
182
c = self.conn.cursor()
184
c.execute('DELETE FROM data WHERE item = ?', (item, ))
187
logging.exception('delete failed: %s', item)
191
def __getitem__(self, item):
192
#logging.debug('sqlite getting %s', item)
194
c = self.conn.cursor()
196
c.execute('SELECT value FROM data WHERE item = ?', (item, ))
199
result = json.loads(row[0])
203
logging.exception('select failed: %s', item)
206
#logging.debug('sqlite got %s: %s', item, result)
209
def __setitem__(self, item, value):
210
serialized_value = json.dumps(value)
212
if self[item] is not None:
214
#logging.debug('sqlite insert %s: %s', item, value)
215
c = self.conn.cursor()
218
c.execute('INSERT INTO data VALUES (?, ?)',
219
(item, serialized_value))
221
c.execute('UPDATE data SET item=?, value=? WHERE item = ?',
222
(item, serialized_value, item))
226
logging.exception('select failed: %s', item)
231
if self.prefix not in self.__class__._conn:
234
if FLAGS.datastore_path != ':memory:':
235
os.unlink(os.path.join(FLAGS.datastore_path, '%s.sqlite' % self.prefix))
236
del self.__class__._conn[self.prefix]
239
for k, conn in self.__class__._conn.iteritems():
241
if FLAGS.datastore_path != ':memory:':
242
os.unlink(os.path.join(FLAGS.datastore_path,
243
'%s.sqlite' % self.prefix))
244
self.__class__._conn = {}
247
def set_add(self, item, value):
254
def set_is_member(self, item, value):
258
return value in group
260
def set_remove(self, item, value):
267
def set_fetch(self, item):
268
# TODO(termie): I don't really know what set_fetch is supposed to do
274
class JsonKeeper(object):
276
Simple dictionary class that persists using
277
JSON in files saved to disk.
279
def __init__(self, prefix):
282
def __delitem__(self, item):
284
Removing a key means deleting a file from disk.
286
item = slugify(item, self.prefix)
287
path = "%s/%s" % (FLAGS.datastore_path, item)
288
if os.path.isfile(path):
291
def __getitem__(self, item):
293
Fetch file contents and dejsonify them.
295
item = slugify(item, self.prefix)
296
path = "%s/%s" % (FLAGS.datastore_path, item)
297
if os.path.isfile(path):
298
return json.load(open(path, 'r'))
301
def __setitem__(self, item, value):
303
JSON encode value and save to file.
305
item = slugify(item, self.prefix)
306
path = "%s/%s" % (FLAGS.datastore_path, item)
307
with open(path, "w") as blobfile:
308
blobfile.write(json.dumps(value))
312
class RedisKeeper(object):
314
Simple dictionary class that persists using
317
def __init__(self, prefix="redis-"):
319
Redis.instance().ping()
321
def __setitem__(self, item, value):
323
JSON encode value and save to file.
325
item = slugify(item, self.prefix)
326
Redis.instance().set(item, json.dumps(value))
329
def __getitem__(self, item):
330
item = slugify(item, self.prefix)
331
value = Redis.instance().get(item)
333
return json.loads(value)
335
def __delitem__(self, item):
336
item = slugify(item, self.prefix)
337
return Redis.instance().delete(item)
340
raise NotImplementedError()
343
raise NotImplementedError()
345
def set_add(self, item, value):
346
item = slugify(item, self.prefix)
347
return Redis.instance().sadd(item, json.dumps(value))
349
def set_is_member(self, item, value):
350
item = slugify(item, self.prefix)
351
return Redis.instance().sismember(item, json.dumps(value))
353
def set_remove(self, item, value):
354
item = slugify(item, self.prefix)
355
return Redis.instance().srem(item, json.dumps(value))
357
def set_fetch(self, item):
358
item = slugify(item, self.prefix)
359
for obj in Redis.instance().sinter([item]):
360
yield json.loads(obj)
363
def Keeper(prefix=''):
364
KEEPERS = {'redis': RedisKeeper,
365
'sqlite': SqliteKeeper}
366
return KEEPERS[FLAGS.keeper_backend](prefix)