koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 1 | import os |
| 2 | import re |
| 3 | import sys |
| 4 | import socket |
| 5 | import logging |
| 6 | from urlparse import urlparse |
| 7 | |
| 8 | import yaml |
| 9 | from wally.fuel_rest_api import (KeystoneAuth, get_cluster_id, |
| 10 | reflect_cluster, FuelInfo) |
| 11 | from wally.utils import parse_creds |
| 12 | from wally.ssh_utils import run_over_ssh, connect |
| 13 | |
| 14 | from .node import Node |
| 15 | |
| 16 | |
| 17 | logger = logging.getLogger("wally.discover") |
koder aka kdanilov | 168f609 | 2015-04-19 02:33:38 +0300 | [diff] [blame] | 18 | BASE_PF_PORT = 44006 |
koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 19 | |
| 20 | |
koder aka kdanilov | e87ae65 | 2015-04-20 02:14:35 +0300 | [diff] [blame] | 21 | def discover_fuel_nodes(fuel_data, var_dir, discover_nodes=True): |
koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 22 | username, tenant_name, password = parse_creds(fuel_data['creds']) |
| 23 | creds = {"username": username, |
| 24 | "tenant_name": tenant_name, |
| 25 | "password": password} |
| 26 | |
| 27 | conn = KeystoneAuth(fuel_data['url'], creds, headers=None) |
| 28 | |
| 29 | cluster_id = get_cluster_id(conn, fuel_data['openstack_env']) |
| 30 | cluster = reflect_cluster(conn, cluster_id) |
koder aka kdanilov | e87ae65 | 2015-04-20 02:14:35 +0300 | [diff] [blame] | 31 | |
| 32 | if not discover_nodes: |
| 33 | logger.warning("Skip fuel cluster discovery") |
| 34 | return ([], None, cluster.get_openrc()) |
| 35 | |
koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 36 | version = FuelInfo(conn).get_version() |
| 37 | |
| 38 | fuel_nodes = list(cluster.get_nodes()) |
| 39 | |
koder aka kdanilov | 168f609 | 2015-04-19 02:33:38 +0300 | [diff] [blame] | 40 | logger.debug("Found FUEL {0}".format(".".join(map(str, version)))) |
koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 41 | |
| 42 | network = 'fuelweb_admin' if version >= [6, 0] else 'admin' |
| 43 | |
| 44 | ssh_creds = fuel_data['ssh_creds'] |
| 45 | |
| 46 | fuel_host = urlparse(fuel_data['url']).hostname |
| 47 | fuel_ip = socket.gethostbyname(fuel_host) |
| 48 | ssh_conn = connect("{0}@@{1}".format(ssh_creds, fuel_host)) |
| 49 | |
| 50 | fuel_ext_iface = get_external_interface(ssh_conn, fuel_ip) |
| 51 | |
| 52 | # TODO: keep ssh key in memory |
| 53 | # http://stackoverflow.com/questions/11994139/how-to-include-the-private-key-in-paramiko-after-fetching-from-string |
| 54 | fuel_key_file = os.path.join(var_dir, "fuel_master_node_id_rsa") |
| 55 | download_master_key(ssh_conn, fuel_key_file) |
| 56 | |
| 57 | nodes = [] |
| 58 | ports = range(BASE_PF_PORT, BASE_PF_PORT + len(fuel_nodes)) |
| 59 | ips_ports = [] |
| 60 | |
| 61 | for fuel_node, port in zip(fuel_nodes, ports): |
| 62 | ip = fuel_node.get_ip(network) |
| 63 | forward_ssh_port(ssh_conn, fuel_ext_iface, port, ip) |
| 64 | |
| 65 | conn_url = "ssh://root@{0}:{1}:{2}".format(fuel_host, |
| 66 | port, |
| 67 | fuel_key_file) |
koder aka kdanilov | 168f609 | 2015-04-19 02:33:38 +0300 | [diff] [blame] | 68 | node = Node(conn_url, fuel_node['roles']) |
| 69 | node.monitor_url = None |
| 70 | nodes.append(node) |
koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 71 | ips_ports.append((ip, port)) |
| 72 | |
| 73 | logger.debug("Found %s fuel nodes for env %r" % |
| 74 | (len(nodes), fuel_data['openstack_env'])) |
| 75 | |
koder aka kdanilov | 168f609 | 2015-04-19 02:33:38 +0300 | [diff] [blame] | 76 | # return ([], |
| 77 | # (ssh_conn, fuel_ext_iface, ips_ports), |
| 78 | # cluster.get_openrc()) |
koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 79 | |
| 80 | return (nodes, |
| 81 | (ssh_conn, fuel_ext_iface, ips_ports), |
| 82 | cluster.get_openrc()) |
| 83 | |
| 84 | |
| 85 | def download_master_key(conn, dest): |
| 86 | # download master key |
| 87 | sftp = conn.open_sftp() |
| 88 | sftp.get('/root/.ssh/id_rsa', dest) |
| 89 | os.chmod(dest, 0o400) |
| 90 | sftp.close() |
| 91 | |
| 92 | logger.debug("Fuel master key stored in {0}".format(dest)) |
| 93 | |
| 94 | |
| 95 | def get_external_interface(conn, ip): |
| 96 | data = run_over_ssh(conn, "ip a", node='fuel-master') |
| 97 | curr_iface = None |
| 98 | for line in data.split("\n"): |
| 99 | |
| 100 | match1 = re.match(r"\d+:\s+(?P<name>.*?):\s\<", line) |
| 101 | if match1 is not None: |
| 102 | curr_iface = match1.group('name') |
| 103 | |
| 104 | match2 = re.match(r"\s+inet\s+(?P<ip>[0-9.]+)/", line) |
| 105 | if match2 is not None: |
| 106 | if match2.group('ip') == ip: |
| 107 | assert curr_iface is not None |
| 108 | return curr_iface |
| 109 | raise KeyError("Can't found interface for ip {0}".format(ip)) |
| 110 | |
| 111 | |
| 112 | def forward_ssh_port(conn, iface, new_port, ip, clean=False): |
| 113 | mode = "-D" if clean is True else "-A" |
| 114 | cmd = "iptables -t nat {mode} PREROUTING -p tcp " + \ |
| 115 | "-i {iface} --dport {port} -j DNAT --to {ip}:22" |
| 116 | run_over_ssh(conn, |
| 117 | cmd.format(iface=iface, port=new_port, ip=ip, mode=mode), |
| 118 | node='fuel-master') |
| 119 | |
| 120 | |
| 121 | def clean_fuel_port_forwarding(clean_data): |
koder aka kdanilov | e87ae65 | 2015-04-20 02:14:35 +0300 | [diff] [blame] | 122 | if clean_data is None: |
| 123 | return |
| 124 | |
koder aka kdanilov | cff7b2e | 2015-04-18 20:48:15 +0300 | [diff] [blame] | 125 | conn, iface, ips_ports = clean_data |
| 126 | for ip, port in ips_ports: |
| 127 | forward_ssh_port(conn, iface, port, ip, clean=True) |
| 128 | |
| 129 | |
| 130 | def main(argv): |
| 131 | fuel_data = yaml.load(open(sys.argv[1]).read())['clouds']['fuel'] |
| 132 | nodes, to_clean, openrc = discover_fuel_nodes(fuel_data, '/tmp') |
| 133 | |
| 134 | print nodes |
| 135 | print openrc |
| 136 | print "Ready to test" |
| 137 | |
| 138 | sys.stdin.readline() |
| 139 | |
| 140 | clean_fuel_port_forwarding(to_clean) |
| 141 | |
| 142 | return 0 |
| 143 | |
| 144 | |
| 145 | if __name__ == "__main__": |
| 146 | main(sys.argv[1:]) |