def items_to_dict(items): SUCCESS_STATUS = 1 FAILURE_STATUS = -1 KEY_ERROR = 1 RUNTIME_ERROR = 4 DATABASE_ERROR = 6 class Database(object):
    STATUS = enum('MOUNTED', 'UNMOUNTED')

    def __init__(self, name, path, options,
                 status=STATUS.UNMOUNTED, init_connector=False):
        self.name = name
        self.path = path
        self.status = status
        self.last_access = 0.0
        self._connector = None
        self.options = options

        if init_connector:
            self.set_connector(self.path, **self.options)

    def __del__(self):
        del self._connector

    @property
    def connector(self):
        return self._connector

    @connector.setter
    def connector(self, value):
        if isinstance(value, plyvel.DB) or value is None:
            self._connector = value
        else:
            raise TypeError("Connector whether should be"
                            "a plyvel object or None")

    @connector.deleter
    def connector(self):
        del self._connector

    def set_connector(self, path, *args, **kwargs):
        kwargs.update({'create_if_missing': True})

        try:
            self._connector = plyvel.DB(path, *args, **kwargs)
        except CorruptionError as e:
            errors_logger.exception(e.message)

    def mount(self):
        if self.status is self.STATUS.UNMOUNTED:
            self.set_connector(self.path)

            if self.connector is None:
                return failure(DATABASE_ERROR, "Database %s could not be mounted" % self.path)

            self.status = self.STATUS.MOUNTED
        else:
            return failure(DATABASE_ERROR, "Database %r already mounted" % self.path)

        return success()

    def umount(self):
        if self.status is self.STATUS.MOUNTED:
            self.status = self.STATUS.UNMOUNTED
            del self._connector
            self._connector = None
        else:
            return failure(DATABASE_ERROR, "Database %r already unmounted" % self.name)

        return success() class DatabaseStore(dict):
    STATUSES = enum('MOUNTED', 'UNMOUNTED')

    def __init__(self, config, *args, **kwargs):
        self.index = dict().fromkeys('name_to_uid')

        self.index['name_to_uid'] = {}
        self['reverse_name_index'] = {}
        self['paths_index'] = {}
        self.store_file = config['database_store']
        self.storage_path = config['databases_storage_path']

        self._global_cache_size = None

        self.load()
        self.mount('default')  # Always mount default

    def __del__(self):
        """
        Explictly shutsdown the internal leveldb connectors
        """
        for uid in self.keys():
            del self[uid]

    @property
    def last_access(self):
        result = {}

        for uid, data in self.iteritems():
            if hasattr(data, 'last_access'):
                result[uid] = data.last_access

        return result

    def extract_store_datas(self):
        """Retrieves database store from file

        If file doesn't exist, or is invalid json,
        and empty store is returned.

        Return
        ------
        store_datas, dict
        """
        try:
            store_datas = json.load(open(self.store_file, 'r'))
        except (IOError, ValueError):
            store_datas = {}

        return store_datas

    def load(self):
        """Loads databases from store file"""
        store_datas = self.extract_store_datas()

        for db_name, db_desc in store_datas.iteritems():
            self.index['name_to_uid'].update({db_name: db_desc['uid']})
            self.update({
                db_desc['uid']: Database(db_name, db_desc['path'], db_desc['options'])
            })

        # Always bootstrap 'default'
        if 'default' not in self.index['name_to_uid']:
            self.add('default')

    def store_update(self, db_name, db_desc):
        """Updates the database store file db_name
        key, with db_desc value"""
        store_datas = self.extract_store_datas()

        store_datas.update({db_name: db_desc})
        json.dump(store_datas, open(self.store_file, 'w'))

    def store_remove(self, db_name):
        """Removes a database from store file"""
        store_datas = self.extract_store_datas()
        store_datas.pop(db_name)
        json.dump(store_datas, open(self.store_file, 'w'))

    def status(self, db_name):
        """Returns the mounted/unmounted database status"""

        db_uid = self.index['name_to_uid'][db_name] if db_name in self.index['name_to_uid'] else None
        return self[db_uid].status

    def mount(self, db_name):
        db_uid = self.index['name_to_uid'][db_name] if db_name in self.index['name_to_uid'] else None
        return self[db_uid].mount()

    def umount(self, db_name):
        db_uid = self.index['name_to_uid'][db_name] if db_name in self.index['name_to_uid'] else None
        return self[db_uid].umount()

    def add(self, db_name, db_options=None):
        """Adds a db to the DatabasesStore object, and sync it
        to the store file"""
        db_options = db_options or DatabaseOptions()
        db_name_is_path = db_name.startswith('.') or ('/' in db_name)
        is_abspath = lambda: not db_name.startswith('.') and ('/' in db_name)

        # Handle case when a db is a path
        if db_name_is_path:
            if not is_abspath():
                return failure(DATABASE_ERROR, "Canno't create database from relative path")
            try:
                new_db_path = db_name
                if not os.path.exists(new_db_path):
                    os.mkdir(new_db_path)
            except OSError as e:
                return failure(OS_ERROR, e.strerror)
        else:
            new_db_path = os.path.join(self.storage_path, db_name)

        path = new_db_path
        database = Database(db_name,
                            path,
                            db_options,
                            status=Database.STATUS.MOUNTED,
                            init_connector=True)

        # Adding db to store, and updating handler
        uid = str(uuid.uuid4())
        self.index['name_to_uid'].update({db_name: uid})
        self.store_update(db_name, {
            'path': path,
            'uid': uid,
            'options': db_options,
        })
        self.update({uid: database})

        return success()

    def drop(self, db_name):
        """Drops a db from the DatabasesHandler, and sync it
        to store file"""
        db_uid = self.index['name_to_uid'].pop(db_name)
        db_path = self[db_uid].path

        self.pop(db_uid)
        self.store_remove(db_name)

        try:
            rmtree(db_path)
        except OSError:
            return failure(DATABASE_ERROR, "Cannot drop db : %s, files not found")

        return success()

    def exists(self, db_name):
        """Checks if a database exists on disk"""
        db_uid = self.index['name_to_uid'][db_name] if db_name in self.index['name_to_uid'] else None

        if db_uid:
            if os.path.exists(self[db_uid].path):
                return True
            else:
                self.drop(db_name)

        return False

    def list(self):
        """Lists all the DatabasesHandler known databases"""
        return [db_name for db_name
                in [key for key
                    in self.index['name_to_uid'].iterkeys()]
                if self.exists(db_name)] class DatabaseOptions(dict):
    def __init__(self, *args, **kwargs):
        self['create_if_missing'] = True
        self['error_if_exists'] = False
        self['bloom_filter_bits'] = 10  # Value recommended by leveldb doc
        self['paranoid_checks'] = False
        self['lru_cache_size'] = 512 * (1 << 20)  # 512 Mo
        self['write_buffer_size'] = 4 * (1 << 20)  # 4 Mo
        self['block_size'] = 4096
        self['max_open_files'] = 1000

        for key, value in kwargs.iteritems():
            if key in self:
                self[key] = value def gen_test_config():
    tmp = mkdtemp('/tmp')
    return Config(**{
        'daemonize': 'no',
        'pidfile': os.path.join(tmp, 'elevator_test.pid'),
        'databases_storage_path': os.path.join(tmp, 'elevator_test'),
        'database_store': os.path.join(tmp, 'elevator_test/store.json'),
        'default_db': 'default',
        'port': 4141,
        'bind': '127.0.0.1',
        'activity_log': os.path.join(tmp, 'elevator_test.log'),
        'errors_log': os.path.join(tmp, 'elevator_errors.log'),
        'max_cache_size': 1024,
    }) def rm_from_pattern(dir, pattern):
    """Removes directory files matching with a provided
    pattern"""
    for f in os.listdir(dir):
        if re.search(pattern, f):
                os.remove(os.path.join(dir, f))
import unittest2
import os
import json
import shutil
import tempfile
import plyvel
from elevator.utils.snippets import from_mo_to_bytes
from elevator.constants import SUCCESS_STATUS, FAILURE_STATUS,\
                               KEY_ERROR, RUNTIME_ERROR, DATABASE_ERROR
from elevator.db import Database, DatabaseStore, DatabaseOptions
from .fakers import gen_test_config
from .utils import rm_from_pattern
from __future__ import absolute_import





class DatabaseOptionsTest(unittest2.TestCase):
    def setUp(self):
        pass

    def tearDown(self):
        pass


class DatabasesTest(unittest2.TestCase):
    def setUp(self):
        self.store = '/tmp/store.json'
        self.dest = '/tmp/dbs'
        self.config = gen_test_config()
        if not os.path.exists(self.dest):
            os.mkdir(self.dest)
        self.handler = DatabaseStore(self.config)

    def tearDown(self):
        self.handler.__del__()
        os.remove('/tmp/store.json')
        shutil.rmtree('/tmp/dbs')

    def test_init(self):
        self.assertIn('default', self.handler.index['name_to_uid'])
        default_db_uid = self.handler.index['name_to_uid']['default']

        self.assertEqual(self.handler[default_db_uid].name, 'default')
        self.assertEqual(self.handler[default_db_uid].path, '/tmp/dbs/default')

    def test_load(self):
        db_name = 'testdb'
        self.handler.add(db_name)

        self.assertIn(db_name, self.handler.index['name_to_uid'])
        db_uid = self.handler.index['name_to_uid'][db_name]

        self.assertIn(db_uid, self.handler)
        self.assertEqual(self.handler[db_uid].name, db_name)
        self.assertIn('default', self.handler.index['name_to_uid'])

    def test_store_update(self):
        db_name = 'test_db'
        db_desc = {
            'path': '/tmp/test_path',
            'uid': 'testuid',
            'options': {},
        }
        self.handler.store_update(db_name, db_desc)

        store_datas = json.load(open(self.handler.store_file, 'r'))
        self.assertIn(db_name, store_datas)
        self.assertEqual(store_datas[db_name], db_desc)

    def test_store_remove(self):
        db_name = 'test_db'
        db_desc = {
            'path': '/tmp/test_path',
            'uid': 'testuid',
            'options': {},
        }
        self.handler.store_update(db_name, db_desc)
        self.handler.store_remove(db_name)
        store_datas = json.load(open(self.handler.store_file, 'r'))

        self.assertNotIn(db_name, store_datas)

    def test_drop_existing_db(self):
        db_name = 'default'  # Automatically created on startup
        status, content = self.handler.drop(db_name)

        self.assertEqual(status, SUCCESS_STATUS)
        self.assertEqual(content, None)

        store_datas = json.load(open(self.handler.store_file, 'r'))
        self.assertNotIn(db_name, store_datas)

    def test_remove_existing_db_which_files_were_erased(self):
        db_name = 'testdb'  # Automatically created on startup
        db_path = '/tmp/dbs/testdb'
        status, content = self.handler.add(db_name)
        shutil.rmtree(db_path)
        status, content = self.handler.drop(db_name)

        self.assertEqual(status, FAILURE_STATUS)
        self.assertIsInstance(content, list)
        self.assertEqual(len(content), 2)
        self.assertEqual(content[0], DATABASE_ERROR)

        store_datas = json.load(open(self.handler.store_file, 'r'))
        self.assertNotIn(db_name, store_datas)

    def test_add_from_db_name_without_options_passed(self):
        db_name = 'testdb'

        default_db_options = DatabaseOptions()