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