def add_to_ring(self, builder_type, body, lasthash, start_response, env): """ Handle a add device post """ with lock_file(self.bf_path[builder_type], timeout=1, unlink=False): self.verify_current_hash(self.bf_path[builder_type], lasthash) builder = RingBuilder.load(self.bf_path[builder_type]) ring_modified = False try: for device in body['devices']: sleep() # so we don't starve/block if not self._is_existing_dev(builder, device['ip'], int(device['port']), device['device']): self._add_device(builder, int(device['zone']), device['ip'], int(device['port']), device['device'], float(device['weight']), device['meta']) ring_modified = True except (AttributeError, KeyError, ValueError, TypeError) as err: return self.return_response(False, lasthash, "Malformed request.", start_response, env) if ring_modified: newmd5 = self.write_builder(builder, self.bf_path[builder_type]) return self.return_response(True, newmd5, None, start_response, env) else: return self.return_response(False, lasthash, 'Ring remains unchanged.', start_response, env)
def change_weight(self, builder_type, dev_weights, lasthash, start_response, env): """ Change weight of devices :param builder_type: the builder_type to use when loading the builder :param dev_weights: a dict of device id and weight :param lasthash: the hash to use when verifying state """ with lock_file(self.bf_path[builder_type], timeout=1, unlink=False): self.verify_current_hash(self.bf_path[builder_type], lasthash) builder = RingBuilder.load(self.bf_path[builder_type]) for dev_id in dev_weights: sleep() # so we don't starve/block try: builder.set_dev_weight(int(dev_id), float(dev_weights[dev_id])) except (IndexError, TypeError): return self.return_response(False, lasthash, 'Invalid dev id %s.' % dev_id, start_response, env) except ValueError as err: return self.return_response(False, lasthash, str(err), start_response, env) newmd5 = self.write_builder(builder, self.bf_path[builder_type]) return self.return_response(True, newmd5, None, start_response, env)
def change_meta(self, builder_type, dev_meta, lasthash, start_response, env): """ Change meta info for devices :param builder_type: the builder_type to use when loading the builder :param dev_meta: a dict of device id and meta info :param lasthash: the hash to use when verifying state """ with lock_file(self.bf_path[builder_type], timeout=1, unlink=False): self.verify_current_hash(self.bf_path[builder_type], lasthash) builder = RingBuilder.load(self.bf_path[builder_type]) try: modified = False for dev_id in dev_meta: sleep() # so we don't starve/block for device in builder.devs: if not device: continue if device['id'] == int(dev_id): modified = True device['meta'] = '%s' % dev_meta[dev_id] if modified: newmd5 = self.write_builder(builder, self.bf_path[builder_type]) return self.return_response(True, newmd5, None, start_response, env) else: return self.return_response(False, lasthash, 'Invalid dev id %s.' % dev_id, start_response, env) except ValueError as err: return self.return_response(False, lasthash, str(err), start_response, env)
def remove_devs(self, builder_type, devices, lasthash, start_response, env): """ remove devices from the builder :params builder_type: the builder_type to use when loading the builder :params devices: list of device ids to be removed. :params lasthash: the hash to use when verifying state """ with lock_file(self.bf_path[builder_type], timeout=1, unlink=False): self.verify_current_hash(self.bf_path[builder_type], lasthash) builder = RingBuilder.load(self.bf_path[builder_type]) if not isinstance(devices, list): return self.return_response(False, lasthash, 'Malformed request.', start_response, env) for dev_id in devices: sleep() # so we don't starve/block try: builder.remove_dev(int(dev_id)) except (IndexError, TypeError): return self.return_response(False, lasthash, 'Invalid dev id %s.' % dev_id, start_response, env) except RingBuilderError as err: return self.return_response(False, lasthash, 'Error removing %s - %s.' % (dev_id, err), start_response, env) except ValueError as err: return self.return_response(False, lasthash, str(err), start_response, env) newmd5 = self.write_builder(builder, self.bf_path[builder_type]) return self.return_response(True, newmd5, None, start_response, env)
def rebalance(self, builder_type, lasthash, start_response, env): """ rebalance a ring note: rebalance doesn't yield. """ with lock_file(self.bf_path[builder_type], timeout=1, unlink=False): self.verify_current_hash(self.bf_path[builder_type], lasthash) builder = RingBuilder.load(self.bf_path[builder_type]) devs_changed = builder.devs_changed try: last_balance = builder.get_balance() parts, balance = builder.rebalance() except RingBuilderError, err: self.logger.exception(_("Error during ring validation.")) return self.return_response(False, None, err.message, start_response, env) if not parts: msg = 'Either none need to be assigned or none can be due ' \ 'to min_part_hours [%s].' % builder.min_part_hours self.logger.error(_(msg)) return self.return_response(False, None, msg, start_response, env) if not devs_changed and abs(last_balance - balance) < 1: msg = 'Refusing to save rebalance. Did not change at least 1%.' self.logger.error(_(msg)) return self.return_response(False, None, msg, start_response, env) try: builder.validate() except RingValidationError, err: self.logger.exception(_("Error during ring validation.")) return self.return_response(False, None, err.message, start_response, env)
def handle_head(self, target_file, start_response, env): """handle a head request. at this point it simply obtains the targets md5sum. :params target_file: File whos md5sum to obtain. :returns: list of boolean status, md5sum of the current ring, and all builder.devs """ with lock_file(target_file, unlink=False): current_hash = self._get_md5sum(target_file) return self.return_response(True, current_hash, None, start_response, env)
def list_devices(self, builder_type, start_response, env): """ list ALL devices in the ring :params builder_type: the builder_type to use when loading the builder :returns: list of boolean status, md5sum of the current ring, and all builder.devs """ with lock_file(self.bf_path[builder_type], timeout=1, unlink=False): builder = RingBuilder.load(self.bf_path[builder_type]) current_md5sum = self._get_md5sum(self.bf_path[builder_type]) return self.return_response(True, current_md5sum, builder.devs, start_response, env)
def return_static_file(self, filename, start_response, env): """ lock and serve a static file to the client from disk :params filename: the file to serve and who's md5sum to use for the X-Current-Hash header. :params start_response: start_response object :returns: iterator for reading the file from disk. """ with lock_file(filename, timeout=1, unlink=False): filehash = self._get_md5sum(filename) self._log_request(env, 200) start_response('200 OK', [('Content-Length', getsize(filename)), ('X-Current-Hash', filehash), ('Content-Type', 'application/octet-stream')]) return FileIterable(filename)
def flush(self): while self.buffers: filename_list = self.buffers.keys() for filename in filename_list: out = '\n'.join(self.buffers[filename]) + '\n' mid_dirs = os.path.dirname(filename) try: os.makedirs(mid_dirs) except OSError, err: if err.errno == errno.EEXIST: pass else: raise try: with lock_file(filename, append=True, unlink=False) as f: f.write(out) except LockTimeout: # couldn't write, we'll try again later self.logger.debug(_('Timeout writing to %s' % filename)) else: del self.buffers[filename]
def search(self, builder_type, search_pattern, start_response, env): """ search the builder for devices matching search pattern :params builder_type: the builder_type to use when loading the builder :params search_values: the value to search for :returns: list of boolean status, md5sum of current builder file on disk, and error message or dict of matched devices. """ with lock_file(self.bf_path[builder_type], timeout=1, unlink=False): builder = RingBuilder.load(self.bf_path[builder_type]) try: search_result = builder.search_devs(str(search_pattern)) return self.return_response(True, self._get_md5sum( self.bf_path[builder_type]), search_result, start_response, env) except ValueError: return self.return_response(False, self._get_md5sum( self.bf_path[builder_type]), 'Invalid search term', start_response, env)