def __init__(self, logger=None, reset_storage=False, storage=None):
     if not logger:
         logger = logging.getLogger('hostpool.rest.backend')
     self.logger = logger.getChild('backend')
     self.logger.setLevel(logging.DEBUG)
     self.storage = Database(storage)
     if reset_storage:
         with FLOCK.acquire(timeout=10):
             self.storage.init_data()
class RestBackend(object):
    '''RESTful service backend class'''
    def __init__(self, logger=None, reset_storage=False, storage=None):
        if not logger:
            logger = logging.getLogger('hostpool.rest.backend')
        self.logger = logger.getChild('backend')
        self.logger.setLevel(logging.DEBUG)
        self.storage = Database(storage)
        if reset_storage:
            with FLOCK.acquire(timeout=10):
                self.storage.init_data()

    def list_hosts(self, filters=None):
        '''Get an iterable of all hosts'''
        self.logger.debug('backend.list_hosts()')
        return [x for x in self.storage.get_hosts()
                if self.check_host_by_filters(x, filters)]

    def add_hosts(self, config):
        '''Adds hosts to the host pool'''
        self.logger.debug('backend.add_hosts({0})'.format(config))
        if not isinstance(config, dict) or \
           not config.get('hosts'):
            raise exceptions.HostPoolHTTPException('Invalid hosts format')
        hosts = HostAlchemist(config).parse()
        return self.storage.add_hosts(hosts)

    def remove_host(self, host_id):
        '''Remove a host from the host pool'''
        self.logger.debug('backend.remove_host({0})'.format(host_id))
        if not host_id or not isinstance(host_id, int):
            raise exceptions.HostNotFoundException(host_id)
        h_id = self.storage.remove_host(host_id)
        if not h_id:
            raise exceptions.HostNotFoundException(host_id)
        return h_id

    def update_host(self, host_id, updates):
        '''Updates a host in the host pool'''
        self.logger.debug('backend.update_host({0})'.format(host_id))
        if not host_id or not isinstance(host_id, int):
            raise exceptions.HostNotFoundException(host_id)
        if not isinstance(updates, dict):
            raise exceptions.HostPoolHTTPException('Invalid data format')
        orig = self.storage.get_host(host_id)
        if not orig:
            raise exceptions.HostNotFoundException(host_id)
        updated = dict_update(orig, updates)
        h_id = self.storage.update_host(host_id, updated)
        if not h_id:
            raise exceptions.HostNotFoundException(host_id)
        return h_id

    def check_host_by_filters(self, host, filters):
        '''Check if a host matches a set of filters'''
        # Basic validation
        if not filters or not isinstance(filters, dict):
            self.logger.warn('No filters specified')
            return True
        if not host:
            self.logger.warn('No host specified')
            return False
        # Check filters using a True fall-through
        # Check OS
        if filters.get('os'):
            if not isinstance(filters.get('os'), basestring):
                self.logger.warn('Invalid, non-string requested OS provided')
                return False
            if filters.get('os').lower() != host.get('os', '').lower():
                self.logger.warn('Host does not match all filters '
                                 '(os={0})'.format(filters.get('os').lower()))
                return False
        # Check tags (AND method)
        if filters.get('tags'):
            if not isinstance(filters.get('tags'), list):
                self.logger.warn('Invalid, non-list requested tags provided')
                return False
            for tag in filters.get('tags'):
                if tag not in host.get('tags', list()):
                    self.logger.warn('Host does not match all filters '
                                     '(tags={0})'.format(filters.get('tags')))
                    return False
        return True

    def acquire_host(self, filters=None):
        '''Acquire a host, mark it taken'''
        # Format the OS request
        self.logger.debug('backend.acquire_host({0})'.format(filters))
        for host_id in self.get_unallocated_host_ids():
            # Allocate the host
            self.logger.debug('Trying host #{0}'.format(host_id))
            self.storage.update_host(host_id, {'allocated': True})
            # Refresh our data
            host = self.storage.get_host(host_id)
            # Enforce any user-defined requests
            if self.check_host_by_filters(host, filters) and \
               self.host_port_scan(host['endpoint']):
                return self.storage.get_host(host_id)
            self.storage.update_host(host_id, {'allocated': False})
        # We didn't manage to acquire any host
        raise exceptions.NoHostAvailableException()

    def release_host(self, host_id):
        '''Release a host, free it'''
        if not host_id or not isinstance(host_id, int):
            raise exceptions.HostNotFoundException(host_id)
        self.storage.update_host(host_id, {'allocated': False})
        return self.storage.get_host(host_id)

    def get_host(self, host_id):
        '''Gets a host + key data'''
        self.logger.debug('backend.get_host({0})'.format(host_id))
        if not host_id or not isinstance(host_id, int):
            raise exceptions.HostNotFoundException(host_id)
        host = self.storage.get_host(host_id)
        if not host:
            raise exceptions.HostNotFoundException(host_id)
        return host

    def get_unallocated_host_ids(self):
        '''Get a generator for free hosts'''
        hosts = self.list_hosts()
        for host in hosts:
            if not host['allocated']:
                yield host[constants.HOST_ID_KEY]

    def host_port_scan(self, endpoint):
        '''Scans a TCP port'''
        # Basic validation
        if not endpoint or not endpoint.get('ip') or not endpoint.get('port'):
            self.logger.error('Invalid endpoint specified')
            return False

        # Creates a TCP socket
        sock = socket.socket()
        sock.settimeout(1)
        self.logger.info('Testing endpoint tcp://{0}:{1}'.format(
            endpoint['ip'], endpoint['port']))
        try:
            sock.connect((endpoint['ip'], endpoint['port']))
            sock.close()
            self.logger.info('Successfully connected to '
                             'endpoint tcp://{0}:{1}'.format(
                                 endpoint['ip'], endpoint['port']))
            return True
        except socket.error as exc:
            self.logger.warn('Error connecting to endpoint tcp://{0}:{1}. '
                             'Exception: {2}'.format(
                                 endpoint['ip'], endpoint['port'], exc))
            return False