def ack_unit_attention(self, d): if not os.path.exists(d): return 0 i = self.preempt_timeout self.set_read_only(0) while i > 0: i -= 1 cmd = ['sg_persist', '-n', '-r', d] p = Popen(cmd, stdout=PIPE, stderr=PIPE, close_fds=True) out, err = p.communicate() out = bdecode(out) err = bdecode(err) ret = p.returncode if "unsupported service action" in err: raise ex.excScsiPrNotsupported( "disk %s does not support persistent reservation" % d) if "error opening file" in err: return 0 if "Unit Attention" in out or ret != 0: self.log.debug("disk %s reports 'Unit Attention' ... waiting" % d) time.sleep(1) continue break if i == 0: self.log.error( "timed out waiting for 'Unit Attention' to go away on disk %s" % d) return 1 return 0
def _fullpem(self): required = set(["private_key", "certificate_chain"]) if required & set(self.data_keys()) != required: self.gen_cert() buff = bdecode(self.decode_key("private_key")) buff += bdecode(self.decode_key("certificate_chain")) return buff
def encrypt(self, data, cluster_name=None, secret=None, encode=True): """ Encrypt and return data in a wrapping structure. """ if cluster_name is None: cluster_name = self.cluster_name if secret is None: cluster_key = self.cluster_key else: cluster_key = secret if cluster_key is None: return iv = self.gen_iv() try: data = json.dumps(data).encode() except (UnicodeDecodeError, TypeError): # already binary data pass message = { "clustername": cluster_name, "nodename": rcEnv.nodename, "iv": bdecode(base64.urlsafe_b64encode(iv)), "data": bdecode( base64.urlsafe_b64encode(self._encrypt(data, cluster_key, iv))), } if encode: return (json.dumps(message) + '\0').encode() return json.dumps(message)
def decrypt(self, message, cluster_name=None, secret=None, sender_id=None): """ Validate the message meta, decrypt and return the data. """ if cluster_name is None: cluster_names = self.cluster_names else: cluster_names = [cluster_name] message = bdecode(message).rstrip("\0\x00") try: message = json.loads(message) except ValueError: message_len = len(message) if message_len > 40: self.log.error("misformatted encrypted message from %s: %s", sender_id, message[:30] + "..." + message[-10:]) elif message_len > 0: self.log.error("misformatted encrypted message from %s", sender_id) return None, None, None msg_clustername = message.get("clustername") msg_nodename = message.get("nodename") if secret is None: if msg_nodename in self.cluster_drpnodes: cluster_key = self.get_secret(Storage(server=msg_nodename), None) else: cluster_key = self.cluster_key else: cluster_key = secret if cluster_name != "join" and \ msg_clustername not in set(["join"]) | self.cluster_names: self.log.warning("discard message from cluster %s, sender %s", msg_clustername, sender_id) return None, None, None if cluster_key is None: return None, None, None if msg_nodename is None: return None, None, None iv = message.get("iv") if iv is None: return None, None, None if self.blacklisted(sender_id): return None, None, None iv = base64.urlsafe_b64decode(to_bytes(iv)) data = base64.urlsafe_b64decode(to_bytes(message["data"])) try: data = self._decrypt(data, cluster_key, iv) except Exception as exc: self.log.error("decrypt message from %s: %s", msg_nodename, str(exc)) self.blacklist(sender_id) return None, None, None if sender_id: self.blacklist_clear(sender_id) try: return msg_clustername, msg_nodename, json.loads(bdecode(data)) except ValueError as exc: return msg_clustername, msg_nodename, data
def provisioner(self): if which("mdadm") is None: raise ex.excError("mdadm is not installed") level = self.r.oget("level") devs = self.r.oget("devs") spares = self.r.oget('spares') chunk = self.r.oget("chunk") layout = self.r.oget("layout") if len(devs) == 0: raise ex.excError( "at least 2 devices must be set in the 'devs' provisioning parameter" ) # long md names cause a buffer overflow in mdadm name = self.r.devname() cmd = [ self.r.mdadm, '--create', name, '--force', '--quiet', '--metadata=default' ] cmd += ['-n', str(len(devs) - spares)] if level: cmd += ["-l", level] if spares: cmd += ["-x", str(spares)] if chunk: cmd += ["-c", str(convert_size(chunk, _to="k", _round=4))] if layout: cmd += ["-p", layout] cmd += devs self.r.log.info(" ".join(cmd)) from subprocess import Popen, PIPE proc = Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE) out, err = proc.communicate(input=b'no\n') out, err = bdecode(out).strip(), bdecode(err).strip() self.r.log.info(out) if proc.returncode != 0: raise ex.excError(err) self.r.can_rollback = True if len(out) > 0: self.r.log.info(out) if len(err) > 0: self.r.log.error(err) self.r.uuid = os.path.basename(name) uuid = self.get_real_uuid(name) self.r.uuid = uuid if self.r.shared: self.r.log.info("set %s.uuid = %s", self.r.rid, uuid) self.r.svc._set(self.r.rid, "uuid", uuid) else: self.r.log.info("set %s.uuid@%s = %s", self.r.rid, rcEnv.nodename, uuid) self.r.svc._set(self.r.rid, "uuid@" + rcEnv.nodename, uuid) self.r.svc.node.unset_lazy("devtree")
def get_secret(self, sp, secret): if want_context(): return if sp.context and not sp.context.get("secret"): return elif secret: return bdecode(secret) elif sp.server in self.cluster_drpnodes: node = self.get_node() return bdecode( self.prepare_key( node.oget("cluster", "secret", impersonate=sp.server))) else: return bdecode(self.cluster_key)
def action(self, nodename, thr=None, **kwargs): options = self.parse_options(kwargs) if not options.data and not options.template: return {"status": 0, "info": "no data"} if options.template is not None: if options.path: paths = [options.path] cmd = [ "create", "-s", options.path, "--template=%s" % options.template, "--env=-" ] else: paths = [p for p in options.data] cmd = ["create", "--template=%s" % options.template, "--env=-"] else: paths = [p for p in options.data] cmd = ["create", "--config=-"] validate_paths(paths) if options.namespace: cmd.append("--namespace=" + options.namespace) if options.restore: cmd.append("--restore") thr.log_request("create/update %s" % ",".join(paths), nodename, **kwargs) proc = thr.service_command(None, cmd, stdout=PIPE, stderr=PIPE, stdin=json.dumps(options.data)) if options.sync: out, err = proc.communicate() result = { "status": proc.returncode, "data": { "out": bdecode(out), "err": bdecode(err), "ret": proc.returncode, }, } else: thr.push_proc(proc) result = { "status": 0, "info": "started %s action %s" % (options.path, " ".join(cmd)), } if options.provision: for path in paths: thr.set_smon(path, global_expect="provisioned") return result
def _add_key(self, key, data): if not key: raise ex.excError("configuration key name can not be empty") if not data: raise ex.excError("configuration value can not be empty") if not is_string(data): data = "base64:"+bdecode(base64.urlsafe_b64encode(data)) elif "\n" in data: data = "base64:"+bdecode(base64.urlsafe_b64encode(bencode(data))) else: data = "literal:"+data self.set_multi(["data.%s=%s" % (key, data)]) self.log.info("configuration key '%s' added (%s)", key, print_size(len(data), compact=True, unit="b")) # refresh if in use self.postinstall(key)
def read_slot(self, slot, fo=None): offset = self.slot_offset(slot) fo.seek(offset, os.SEEK_SET) fo.readinto(self.slot_buff) data = bdecode(self.slot_buff[:]) end = data.index("\0") return data[:end]
def post(self, uri, data=None): api = self.api+uri+"/" headers = {'Content-Type': 'application/json'} if data: data = json.dumps(data) r = requests.post(api, data=data, auth=self.auth, timeout=self.timeout, verify=VERIFY, headers=headers) return bdecode(r.content)
def meta_read_slot(self, slot, fo=None): offset = self.meta_slot_offset(slot) fo.seek(offset, os.SEEK_SET) fo.readinto(self.meta_slot_buff) try: return bdecode(self.meta_slot_buff[:mmap.PAGESIZE]) except Exception as exc: return None
def is_container(): p = '/proc/1/environ' if not os.path.exists(p): return False with open(p, 'r') as f: buff = f.read() if "container=lxc" in bdecode(buff): return True return False
def _pkcs12(self, password): required = set(["private_key", "certificate_chain"]) if required & set(self.data_keys()) != required: self.gen_cert() from subprocess import Popen, PIPE import tempfile _tmpcert = tempfile.NamedTemporaryFile() _tmpkey = tempfile.NamedTemporaryFile() tmpcert = _tmpcert.name tmpkey = _tmpkey.name _tmpcert.close() _tmpkey.close() if password is None: from getpass import getpass pwd = getpass("Password: "******"\n" elif password in ["/dev/stdin", "-"]: pwd = sys.stdin.readline() else: pwd = password + "\n" if six.PY3: pwd = bencode(pwd) try: with open(tmpkey, "w") as _tmpkey: os.chmod(tmpkey, 0o600) _tmpkey.write(bdecode(self.decode_key("private_key"))) with open(tmpcert, "w") as _tmpcert: os.chmod(tmpcert, 0o600) _tmpcert.write(bdecode(self.decode_key("certificate_chain"))) cmd = [ "openssl", "pkcs12", "-export", "-in", tmpcert, "-inkey", tmpkey, "-passout", "stdin" ] proc = Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE) out, err = proc.communicate(input=pwd) if err: print(err, file=sys.stderr) return out finally: if os.path.exists(tmpcert): os.unlink(tmpcert) if os.path.exists(tmpkey): os.unlink(tmpkey)
def h2_daemon_stream_fetch(self, stream_id, conn): resps = [] for push in conn.get_pushes(stream_id): resps.append(push.get_response()) for resp in resps: # resp.read() can modify push.promises_headers, which get_pushes iterates # causing a RuntimeError => keep in a separate loop evt = resp.read() evt = json.loads(bdecode(evt)) yield evt
def post2(self, uri, data=None): api = self.api.replace("api/v1.0", "")+uri s = requests.Session() r = s.get(api) csrf_token = r.cookies['csrftoken'] data["csrfmiddlewaretoken"] = csrf_token if data: data = json.dumps(data) r = requests.post(api, data=data, auth=self.auth, timeout=self.timeout, verify=VERIFY) return bdecode(r.content)
def parse_rsync(self, buff): """ Extract normalized speed and transfered data size from the dd output """ data = {"bytes": 0, "speed": 0} for line in bdecode(buff).splitlines(): if line.startswith("Total bytes sent"): data["bytes"] = int(line.split()[-1].replace(",","")) elif line.endswith("/sec"): data["speed"] = int(convert_speed(line.split()[-2].replace(",","")+"/s")) return data
def do_it(self, send_cmd, receive_cmd, node): self.log.info(' '.join(send_cmd + ["|"] + receive_cmd)) p1 = Popen(send_cmd, stdout=PIPE) pi = Popen(["dd", "bs=4096"], stdin=p1.stdout, stdout=PIPE, stderr=PIPE) p2 = Popen(receive_cmd, stdin=pi.stdout, stdout=PIPE, stderr=PIPE) buff = p2.communicate() if p2.returncode == 0: stats_buff = pi.communicate()[1] stats = self.parse_dd(stats_buff) self.update_stats(stats, target=node) out = bdecode(buff[0]) err = bdecode(buff[1]) if p2.returncode != 0: if err is not None and len(err) > 0: self.log.error(err) raise ex.excError("sync failed") if out is not None and len(out) > 0: self.log.info(out)
def parse_dd(self, buff): """ Extract normalized speed and transfered data size from the dd output """ data = {} if not buff: return data words = bdecode(buff).split() if "bytes" in words: data["bytes"] = int(words[words.index("bytes") - 1]) if words[-1].endswith("/s"): data["speed"] = int(convert_speed("".join(words[-2:]))) return data
def h2_daemon_request(self, data, server=None, node=None, with_result=True, silent=False, cluster_name=None, secret=None, timeout=0, sp=None, method="GET"): secret = self.get_secret(sp, secret) path = self.h2_path_from_data(data) headers = self.h2_headers(node=node, secret=secret, multiplexed=data.get("multiplexed"), af=sp.af) body = self.h2_body_from_data(data) headers.update({"Content-Length": str(len(body))}) conn = self.h2c(sp=sp) elapsed = 0 while True: try: conn.request(method, path, headers=headers, body=body) break except AssertionError as exc: raise ex.excError(str(exc)) except ConnectionResetError: return { "status": 1, "error": "%s %s connection reset" % (method, path) } except (ConnectionRefusedError, ssl.SSLError, socket.error) as exc: try: errno = exc.errno except AttributeError: errno = None if errno in RETRYABLE and \ (timeout == 0 or elapsed < timeout): # Resource temporarily unavailable (busy, overflow) # Retry after a delay, if the daemon is still # running and timeout is not exhausted time.sleep(PAUSE) elapsed += PAUSE continue return {"status": 1, "error": "%s" % exc} resp = conn.get_response() data = resp.read() data = json.loads(bdecode(data)) return data
def get_src_dir_dev(self, dev): """Given a directory path, return its hosting device """ if dev in self.src_dir_devs_cache: return self.src_dir_devs_cache[dev] p = Popen(self.df_one_cmd + [dev], stdout=PIPE, stderr=STDOUT, close_fds=True) out, err = p.communicate() if p.returncode != 0: return out = bdecode(out).lstrip() lines = out.splitlines() if len(lines) == 2: out = lines[1] self.src_dir_devs_cache[dev] = out.split()[0] return self.src_dir_devs_cache[dev]
def action(self, nodename, thr=None, stream_id=None, **kwargs): logfile = os.path.join(rcEnv.paths.pathlog, "node.log") ofile = thr._action_logs_open(logfile, 0, "node") request_headers = HTTPHeaderMap( thr.streams[stream_id]["request"].headers) try: content_type = bdecode(request_headers.get("accept").pop()) except: content_type = "application/json" thr.streams[stream_id]["content_type"] = content_type thr.streams[stream_id]["pushers"].append({ "o": self, "fn": "h2_push_logs", "args": [ofile, True], })
def action(self, nodename, thr=None, **kwargs): options = self.parse_options(kwargs) try: return { "status": 0, "data": bdecode(shared.SERVICES[options.path].decode_key(options.key)) } except ex.excError as exc: return {"status": 1, "error": str(exc)} except Exception as exc: return { "status": 1, "error": str(exc), "traceback": traceback.format_exc() }
def action(self, nodename, thr=None, stream_id=None, **kwargs): options = self.parse_options(kwargs) thr.selector = options.selector if not thr.event_queue: thr.event_queue = queue.Queue() if options.full: data = thr.daemon_status() namespaces = thr.get_namespaces() fevent = { "nodename": rcEnv.nodename, "ts": time.time(), "kind": "full", "data": thr.filter_daemon_status(data, namespaces=namespaces, selector=options.selector), } if thr.h2conn: _msg = fevent elif thr.encrypted: _msg = thr.encrypt(fevent) else: _msg = thr.msg_encode(fevent) thr.event_queue.put(_msg) if not thr in thr.parent.events_clients: thr.parent.events_clients.append(thr) if not stream_id in thr.events_stream_ids: thr.events_stream_ids.append(stream_id) if thr.h2conn: request_headers = HTTPHeaderMap( thr.streams[stream_id]["request"].headers) try: content_type = bdecode(request_headers.get("accept").pop()) except: content_type = "application/json" thr.streams[stream_id]["content_type"] = content_type thr.streams[stream_id]["pushers"].append({ "fn": "h2_push_action_events", }) else: thr.raw_push_action_events()
def action(self, nodename, thr=None, stream_id=None, **kwargs): options = self.parse_options(kwargs) svc = thr.get_service(options.path) if svc is None: raise HTTP(404, "%s not found" % options.path) request_headers = HTTPHeaderMap( thr.streams[stream_id]["request"].headers) try: content_type = bdecode(request_headers.get("accept").pop()) except: content_type = "application/json" thr.streams[stream_id]["content_type"] = content_type logfile = os.path.join(svc.log_d, svc.name + ".log") ofile = thr._action_logs_open(logfile, 0, svc.path) thr.streams[stream_id]["pushers"].append({ "o": self, "fn": "h2_push_logs", "args": [ofile, True], })
def _handle_client(self, conn, cr, cw): chunks = [] buff_size = 4096 while True: try: data = cr.readline() except socket.timeout as exc: break except socket.error as exc: self.log.info("%s", exc) break if len(data) == 0: #self.log.info("no more data") break self.log.debug("received %s", data) try: data = bdecode(data) data = json.loads(data) except Exception as exc: self.log.error(exc) data = None if self.stopped(): self.log.info("stop event received (handler thread)") break if data is None or not isinstance(data, dict): continue result = self.router(data) if result is not None: message = json.dumps(result) + "\n" cw.write(message) cw.flush() self.log.debug("replied %s", message) message_len = len(message) self.stats.sessions.tx += message_len
def gen_cert(self): data = {} for key in ("cn", "c", "st", "l", "o", "ou", "email", "alt_names", "bits", "validity", "ca"): val = self.oget("DEFAULT", key) if val is not None: data[key] = val ca = data.get("ca") casec = None if ca is not None: casecname, canamespace, _ = split_path(ca) casec = factory("sec")(casecname, namespace=canamespace, log=self.log, volatile=True) if not casec.exists(): raise ex.excError("ca secret %s does not exist" % ca) for key in ("crt", "key", "csr"): data[key] = self.tempfilename() if "alt_names" in data: data["cnf"] = self.tempfilename() try: if casec: for key, kw in (("cacrt", "certificate"), ("cakey", "private_key")): if kw not in casec.data_keys(): continue data[key] = self.tempfilename() buff = bdecode(casec.decode_key(kw)) with open(data[key], "w") as ofile: ofile.write(buff) gen_cert(log=self.log, **data) self._add("private_key", value_from=data["key"]) if data.get("crt") is not None: self._add("certificate", value_from=data["crt"]) if data.get("csr") is not None: self._add("certificate_signing_request", value_from=data["csr"]) if data.get("cakey") is None: self._add("certificate_chain", value_from=data["crt"]) else: # merge cacrt and crt chain = self.tempfilename() try: with open(data["crt"], "r") as ofile: buff = ofile.read() with open(data["cacrt"], "r") as ofile: buff += ofile.read() with open(chain, "w") as ofile: ofile.write(buff) self._add("certificate_chain", value_from=chain) finally: try: os.unlink(chain) except Exception: pass self.add_key("fullpem", self._fullpem()) finally: for key in ("crt", "key", "cacrt", "cakey", "csr", "cnf"): if key not in data: continue try: os.unlink(data[key]) except Exception: pass
def provisioner(self): if not which('vgdisplay'): self.r.log.error("vgdisplay command not found") raise ex.excError if not which('lvcreate'): self.r.log.error("lvcreate command not found") raise ex.excError if not which('lvdisplay'): self.r.log.error("lvdisplay command not found") raise ex.excError dev = self.get_dev() try: self.size = self.r.conf_get("size") self.size = str(self.size).upper() if "%" not in self.size: size_parm = ["-L", str(convert_size(self.size, _to="m")) + 'M'] else: size_parm = ["-l", self.size] vg = self.r.conf_get("vg") except Exception as e: self.r.log.info("skip lv provisioning: %s" % str(e)) return create_options = self.r.oget("create_options") cmd = ['vgdisplay', vg] out, err, ret = justcall(cmd) if ret != 0: self.r.log.error("volume group %s does not exist" % vg) raise ex.excError lvname = os.path.basename(dev) # create the logical volume cmd = ['lvcreate', '-n', lvname] + size_parm + create_options + [vg] _cmd = "yes | " + " ".join(cmd) self.r.log.info(_cmd) p1 = Popen(["yes"], stdout=PIPE, preexec_fn=restore_signals) p2 = Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=p1.stdout, close_fds=True) out, err = p2.communicate() out = bdecode(out) err = bdecode(err) if p2.returncode != 0: raise ex.excError(err) if hasattr(self.r, "fullname"): self.r.can_rollback = True if len(out) > 0: for line in out.splitlines(): self.r.log.info(line) if len(err) > 0: for line in err.splitlines(): if line.startswith("WARNING:"): self.r.log.warning(line.replace("WARNING: ", "")) else: self.r.log.error(err) # /dev/mapper/$vg-$lv and /dev/$vg/$lv creation is delayed ... refresh try: cmd = [rcEnv.syspaths.dmsetup, 'mknodes'] p = Popen(cmd, stdout=PIPE, stderr=PIPE) p.communicate() except: # best effort pass mapname = "%s-%s" % (vg.replace('-', '--'), lvname.replace('-', '--')) dev = '/dev/mapper/' + mapname for i in range(3, 0, -1): if os.path.exists(dev): break if i != 0: time.sleep(1) if i == 0: self.r.log.error("timed out waiting for %s to appear" % dev) raise ex.excError self.r.svc.node.unset_lazy("devtree")
def action(self, nodename, thr=None, **kwargs): options = self.parse_options(kwargs) name, namespace, kind = split_path(options.path) if thr.get_service(options.path) is None and options.action not in ( "create", "deploy"): thr.log_request("service action (%s not installed)" % options.path, nodename, lvl="warning", **kwargs) raise HTTP(404, "%s not found" % options.path) if not options.action and not options.cmd: thr.log_request("service action (no action set)", nodename, lvl="error", **kwargs) raise HTTP(400, "action not set") for opt in ("node", "daemon", "svcs", "service", "s", "parm_svcs", "local", "id"): if opt in options.options: del options.options[opt] for opt, ropt in (("jsonpath_filter", "filter"), ): if opt in options.options: options.options[ropt] = options.options[opt] del options.options[opt] options.options["local"] = True pmod = __import__(kind + "mgr_parser") popt = pmod.OPT def find_opt(opt): for k, o in popt.items(): if o.dest == opt: return o if o.dest == "parm_" + opt: return o if options.cmd: cmd = [options.cmd] else: cmd = [options.action] for opt, val in options.options.items(): po = find_opt(opt) if po is None: continue if val == po.default: continue if val is None: continue opt = po._long_opts[0] if po._long_opts else po._short_opts[0] if po.action == "append": cmd += [opt + "=" + str(v) for v in val] elif po.action == "store_true" and val: cmd.append(opt) elif po.action == "store_false" and not val: cmd.append(opt) elif po.type == "string": opt += "=" + val cmd.append(opt) elif po.type == "integer": opt += "=" + str(val) cmd.append(opt) fullcmd = rcEnv.python_cmd + [ os.path.join(rcEnv.paths.pathlib, kind + "mgr.py"), "-s", options.path ] + cmd thr.log_request("run '%s'" % " ".join(fullcmd), nodename, **kwargs) if options.sync: proc = Popen(fullcmd, stdout=PIPE, stderr=PIPE, stdin=None, close_fds=True) out, err = proc.communicate() try: result = json.loads(out) except Exception: result = { "status": 0, "data": { "out": bdecode(out), "err": bdecode(err), "ret": proc.returncode, }, } else: proc = Popen(fullcmd, stdin=None, close_fds=True) thr.push_proc(proc) result = { "status": 0, "info": "started %s action %s" % (options.path, " ".join(cmd)), } return result
def msg_decode(self, message): message = bdecode(message).rstrip("\0\x00") if len(message) == 0: return return json.loads(message)
def get(self, uri, params=None): r = requests.get(self.api+uri+"/?format=json", params=params, auth=self.auth, timeout=self.timeout, verify=VERIFY) return bdecode(r.content)