1# Copyright (c) 2009-2012, Andrew McNabb
2# Copyright (c) 2003-2008, Brent N. Chun
3
4import fcntl
5import sys
6import subprocess
7
8HOST_FORMAT = 'Host format is [user@]host[:port] [user]'
9
10
11def read_host_files(paths, default_user=None, default_port=None):
12    """Reads the given host files.
13
14    Returns a list of (host, port, user) triples.
15    """
16    hosts = []
17    if paths:
18        for path in paths:
19            hosts.extend(read_host_file(path, default_user=default_user))
20    return hosts
21
22
23def read_host_file(path, default_user=None, default_port=None):
24    """Reads the given host file.
25
26    Lines are of the form: host[:port] [login].
27    Returns a list of (host, port, user) triples.
28    """
29    lines = []
30    f = open(path)
31    for line in f:
32        lines.append(line.strip())
33    f.close()
34
35    hosts = []
36    for line in lines:
37        # Skip blank lines or lines starting with #
38        line = line.strip()
39        if not line or line.startswith('#'):
40            continue
41        host, port, user = parse_host_entry(line, default_user, default_port)
42        if host:
43            hosts.append((host, port, user))
44    return hosts
45
46
47# TODO: deprecate the second host field and standardize on the
48# [user@]host[:port] format.
49def parse_host_entry(line, default_user, default_port):
50    """Parses a single host entry.
51
52    This may take either the of the form [user@]host[:port] or
53    host[:port][ user].
54
55    Returns a (host, port, user) triple.
56    """
57    fields = line.split()
58    if len(fields) > 2:
59        sys.stderr.write('Bad line: "%s". Format should be'
60                         ' [user@]host[:port] [user]\n' % line)
61        return None, None, None
62    host_field = fields[0]
63    host, port, user = parse_host(host_field, default_port=default_port)
64    if len(fields) == 2:
65        if user is None:
66            user = fields[1]
67        else:
68            sys.stderr.write('User specified twice in line: "%s"\n' % line)
69            return None, None, None
70    if user is None:
71        user = default_user
72    return host, port, user
73
74
75def parse_host_string(host_string, default_user=None, default_port=None):
76    """Parses a whitespace-delimited string of "[user@]host[:port]" entries.
77
78    Returns a list of (host, port, user) triples.
79    """
80    hosts = []
81    entries = host_string.split()
82    for entry in entries:
83        hosts.append(parse_host(entry, default_user, default_port))
84    return hosts
85
86
87def parse_host(host, default_user=None, default_port=None):
88    """Parses host entries of the form "[user@]host[:port]".
89
90    Returns a (host, port, user) triple.
91    """
92    # TODO: when we stop supporting Python 2.4, switch to using str.partition.
93    user = default_user
94    port = default_port
95    if '@' in host:
96        user, host = host.split('@', 1)
97    if ':' in host:
98        host, port = host.rsplit(':', 1)
99    return (host, port, user)
100
101
102def get_pacemaker_nodes():
103    """Get the list of nodes from crm_node -l.
104
105    Returns a list of (host, port, user) triples.
106    """
107    hosts = []
108    if subprocess.call("which crm_node >/dev/null 2>&1", shell=True) != 0:
109        sys.stderr.write('crm_node not available\n')
110        return hosts
111    cmd = "crm_node -l"
112    p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
113    try:
114        outp = p.communicate()[0]
115        p.wait()
116        rc = p.returncode
117    except IOError as msg:
118        sys.stderr.write('%s failed: %s\n' % (cmd, msg))
119        return hosts
120    if rc != 0:
121        sys.stderr.write('%s failed: exit code %d\n' % (cmd, rc))
122        return hosts
123    for s in outp.split('\n'):
124        a = s.split()
125        if len(a) < 2:
126            continue
127        hosts.append((a[1], None, None))
128    return hosts
129
130
131def set_cloexec(filelike):
132    """Sets the underlying filedescriptor to automatically close on exec.
133
134    If set_cloexec is called for all open files, then subprocess.Popen does
135    not require the close_fds option.
136    """
137    fcntl.fcntl(filelike.fileno(), fcntl.FD_CLOEXEC, 1)
138
139# vim:ts=4:sw=4:et:
140