2.0 refactoring:
* Add type for most of functions
* Remove old fio run code, move to RPC/pluggable
* Remove most of sensors code, will move then to RPC
* Other refactoring
diff --git a/wally/node.py b/wally/node.py
new file mode 100644
index 0000000..b146876
--- /dev/null
+++ b/wally/node.py
@@ -0,0 +1,108 @@
+import re
+import getpass
+from typing import Tuple
+from .inode import INode, NodeInfo
+
+from .ssh_utils import parse_ssh_uri, run_over_ssh, connect
+
+
+class Node(INode):
+ """Node object"""
+
+ def __init__(self, node_info: NodeInfo):
+ self.info = node_info
+ self.roles = node_info.roles
+ self.bind_ip = node_info.bind_ip
+
+ assert self.ssh_conn_url.startswith("ssh://")
+ self.ssh_conn_url = node_info.ssh_conn_url
+
+ self.ssh_conn = None
+ self.rpc_conn_url = None
+ self.rpc_conn = None
+ self.os_vm_id = None
+ self.hw_info = None
+
+ if self.ssh_conn_url is not None:
+ self.ssh_cred = parse_ssh_uri(self.ssh_conn_url)
+ self.node_id = "{0.host}:{0.port}".format(self.ssh_cred)
+ else:
+ self.ssh_cred = None
+ self.node_id = None
+
+ def __str__(self):
+ template = "<Node: url={conn_url!r} roles={roles}" + \
+ " connected={is_connected}>"
+ return template.format(conn_url=self.ssh_conn_url,
+ roles=", ".join(self.roles),
+ is_connected=self.ssh_conn is not None)
+
+ def __repr__(self):
+ return str(self)
+
+ def connect_ssh(self) -> None:
+ self.ssh_conn = connect(self.ssh_conn_url)
+
+ def connect_rpc(self) -> None:
+ raise NotImplementedError()
+
+ def prepare_rpc(self) -> None:
+ raise NotImplementedError()
+
+ def get_ip(self) -> str:
+ """get node connection ip address"""
+
+ if self.ssh_conn_url == 'local':
+ return '127.0.0.1'
+ return self.ssh_cred.host
+
+ def get_user(self) -> str:
+ """"get ssh connection username"""
+ if self.ssh_conn_url == 'local':
+ return getpass.getuser()
+ return self.ssh_cred.user
+
+ def run(self, cmd: str, stdin_data: str=None, timeout: int=60, nolog: bool=False) -> Tuple[int, str]:
+ """Run command on node. Will use rpc connection, if available"""
+
+ if self.rpc_conn is None:
+ return run_over_ssh(self.ssh_conn, cmd,
+ stdin_data=stdin_data, timeout=timeout,
+ nolog=nolog, node=self)
+ assert not stdin_data
+ proc_id = self.rpc_conn.cli.spawn(cmd)
+ exit_code = None
+ output = ""
+
+ while exit_code is None:
+ exit_code, stdout_data, stderr_data = self.rpc_conn.cli.get_updates(proc_id)
+ output += stdout_data + stderr_data
+
+ return exit_code, output
+
+ def discover_hardware_info(self) -> None:
+ raise NotImplementedError()
+
+ def get_file_content(self, path: str) -> str:
+ raise NotImplementedError()
+
+ def forward_port(self, ip: str, remote_port: int, local_port: int = None) -> int:
+ raise NotImplementedError()
+
+ def get_interface(self, ip: str) -> str:
+ """Get node external interface for given IP"""
+ data = self.run("ip a", nolog=True)
+ curr_iface = None
+
+ for line in data.split("\n"):
+ match1 = re.match(r"\d+:\s+(?P<name>.*?):\s\<", line)
+ if match1 is not None:
+ curr_iface = match1.group('name')
+
+ match2 = re.match(r"\s+inet\s+(?P<ip>[0-9.]+)/", line)
+ if match2 is not None:
+ if match2.group('ip') == ip:
+ assert curr_iface is not None
+ return curr_iface
+
+ raise KeyError("Can't found interface for ip {0}".format(ip))