|  | import os | 
|  | import re | 
|  | import sys | 
|  | import socket | 
|  | import logging | 
|  | from urlparse import urlparse | 
|  |  | 
|  | import yaml | 
|  | from wally.fuel_rest_api import (KeystoneAuth, get_cluster_id, | 
|  | reflect_cluster, FuelInfo) | 
|  | from wally.utils import parse_creds | 
|  | from wally.ssh_utils import run_over_ssh, connect | 
|  |  | 
|  | from .node import Node | 
|  |  | 
|  |  | 
|  | logger = logging.getLogger("wally.discover") | 
|  | BASE_PF_PORT = 44006 | 
|  |  | 
|  |  | 
|  | def discover_fuel_nodes(fuel_data, var_dir, discover_nodes=True): | 
|  | username, tenant_name, password = parse_creds(fuel_data['creds']) | 
|  | creds = {"username": username, | 
|  | "tenant_name": tenant_name, | 
|  | "password": password} | 
|  |  | 
|  | conn = KeystoneAuth(fuel_data['url'], creds, headers=None) | 
|  |  | 
|  | cluster_id = get_cluster_id(conn, fuel_data['openstack_env']) | 
|  | cluster = reflect_cluster(conn, cluster_id) | 
|  |  | 
|  | if not discover_nodes: | 
|  | logger.warning("Skip fuel cluster discovery") | 
|  | return ([], None, cluster.get_openrc()) | 
|  |  | 
|  | version = FuelInfo(conn).get_version() | 
|  |  | 
|  | fuel_nodes = list(cluster.get_nodes()) | 
|  |  | 
|  | logger.debug("Found FUEL {0}".format(".".join(map(str, version)))) | 
|  |  | 
|  | network = 'fuelweb_admin' if version >= [6, 0] else 'admin' | 
|  |  | 
|  | ssh_creds = fuel_data['ssh_creds'] | 
|  |  | 
|  | fuel_host = urlparse(fuel_data['url']).hostname | 
|  | fuel_ip = socket.gethostbyname(fuel_host) | 
|  | ssh_conn = connect("{0}@@{1}".format(ssh_creds, fuel_host)) | 
|  |  | 
|  | fuel_ext_iface = get_external_interface(ssh_conn, fuel_ip) | 
|  |  | 
|  | # TODO: keep ssh key in memory | 
|  | # http://stackoverflow.com/questions/11994139/how-to-include-the-private-key-in-paramiko-after-fetching-from-string | 
|  | fuel_key_file = os.path.join(var_dir, "fuel_master_node_id_rsa") | 
|  | download_master_key(ssh_conn, fuel_key_file) | 
|  |  | 
|  | nodes = [] | 
|  | ports = range(BASE_PF_PORT, BASE_PF_PORT + len(fuel_nodes)) | 
|  | ips_ports = [] | 
|  |  | 
|  | for fuel_node, port in zip(fuel_nodes, ports): | 
|  | ip = fuel_node.get_ip(network) | 
|  | forward_ssh_port(ssh_conn, fuel_ext_iface, port, ip) | 
|  |  | 
|  | conn_url = "ssh://root@{0}:{1}:{2}".format(fuel_host, | 
|  | port, | 
|  | fuel_key_file) | 
|  | node = Node(conn_url, fuel_node['roles']) | 
|  | node.monitor_url = None | 
|  | nodes.append(node) | 
|  | ips_ports.append((ip, port)) | 
|  |  | 
|  | logger.debug("Found %s fuel nodes for env %r" % | 
|  | (len(nodes), fuel_data['openstack_env'])) | 
|  |  | 
|  | # return ([], | 
|  | #         (ssh_conn, fuel_ext_iface, ips_ports), | 
|  | #         cluster.get_openrc()) | 
|  |  | 
|  | return (nodes, | 
|  | (ssh_conn, fuel_ext_iface, ips_ports), | 
|  | cluster.get_openrc()) | 
|  |  | 
|  |  | 
|  | def download_master_key(conn, dest): | 
|  | # download master key | 
|  | sftp = conn.open_sftp() | 
|  | sftp.get('/root/.ssh/id_rsa', dest) | 
|  | os.chmod(dest, 0o400) | 
|  | sftp.close() | 
|  |  | 
|  | logger.debug("Fuel master key stored in {0}".format(dest)) | 
|  |  | 
|  |  | 
|  | def get_external_interface(conn, ip): | 
|  | data = run_over_ssh(conn, "ip a", node='fuel-master') | 
|  | 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)) | 
|  |  | 
|  |  | 
|  | def forward_ssh_port(conn, iface, new_port, ip, clean=False): | 
|  | mode = "-D" if clean is True else "-A" | 
|  | cmd = "iptables -t nat {mode} PREROUTING -p tcp " + \ | 
|  | "-i {iface} --dport {port} -j DNAT --to {ip}:22" | 
|  | run_over_ssh(conn, | 
|  | cmd.format(iface=iface, port=new_port, ip=ip, mode=mode), | 
|  | node='fuel-master') | 
|  |  | 
|  |  | 
|  | def clean_fuel_port_forwarding(clean_data): | 
|  | if clean_data is None: | 
|  | return | 
|  |  | 
|  | conn, iface, ips_ports = clean_data | 
|  | for ip, port in ips_ports: | 
|  | forward_ssh_port(conn, iface, port, ip, clean=True) | 
|  |  | 
|  |  | 
|  | def main(argv): | 
|  | fuel_data = yaml.load(open(sys.argv[1]).read())['clouds']['fuel'] | 
|  | nodes, to_clean, openrc = discover_fuel_nodes(fuel_data, '/tmp') | 
|  |  | 
|  | print nodes | 
|  | print openrc | 
|  | print "Ready to test" | 
|  |  | 
|  | sys.stdin.readline() | 
|  |  | 
|  | clean_fuel_port_forwarding(to_clean) | 
|  |  | 
|  | return 0 | 
|  |  | 
|  |  | 
|  | if __name__ == "__main__": | 
|  | main(sys.argv[1:]) |