root / snf-image-host / copy-monitor.py @ 10bf026d
History | View | Annotate | Download (6 kB)
1 | ebba4508 | Nikos Skalkotos | #!/usr/bin/env python
|
---|---|---|---|
2 | ebba4508 | Nikos Skalkotos | |
3 | ebba4508 | Nikos Skalkotos | # Copyright (C) 2011, 2012 GRNET S.A.
|
4 | ebba4508 | Nikos Skalkotos | #
|
5 | ebba4508 | Nikos Skalkotos | # This program is free software; you can redistribute it and/or modify
|
6 | ebba4508 | Nikos Skalkotos | # it under the terms of the GNU General Public License as published by
|
7 | ebba4508 | Nikos Skalkotos | # the Free Software Foundation; either version 2 of the License, or
|
8 | ebba4508 | Nikos Skalkotos | # (at your option) any later version.
|
9 | ebba4508 | Nikos Skalkotos | #
|
10 | ebba4508 | Nikos Skalkotos | # This program is distributed in the hope that it will be useful, but
|
11 | ebba4508 | Nikos Skalkotos | # WITHOUT ANY WARRANTY; without even the implied warranty of
|
12 | ebba4508 | Nikos Skalkotos | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
13 | ebba4508 | Nikos Skalkotos | # General Public License for more details.
|
14 | ebba4508 | Nikos Skalkotos | #
|
15 | ebba4508 | Nikos Skalkotos | # You should have received a copy of the GNU General Public License
|
16 | ebba4508 | Nikos Skalkotos | # along with this program; if not, write to the Free Software
|
17 | ebba4508 | Nikos Skalkotos | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
18 | ebba4508 | Nikos Skalkotos | # 02110-1301, USA.
|
19 | ebba4508 | Nikos Skalkotos | |
20 | ebba4508 | Nikos Skalkotos | """Utility to monitor the progress of image deployment
|
21 | ebba4508 | Nikos Skalkotos |
|
22 | ebba4508 | Nikos Skalkotos | A small utility to monitor the progress of image deployment
|
23 | ebba4508 | Nikos Skalkotos | by watching the contents of /proc/<pid>/io and producing
|
24 | ebba4508 | Nikos Skalkotos | notifications.
|
25 | ebba4508 | Nikos Skalkotos | """
|
26 | ebba4508 | Nikos Skalkotos | |
27 | ebba4508 | Nikos Skalkotos | import os |
28 | ebba4508 | Nikos Skalkotos | import sys |
29 | ebba4508 | Nikos Skalkotos | import time |
30 | ebba4508 | Nikos Skalkotos | import json |
31 | ebba4508 | Nikos Skalkotos | import prctl |
32 | ebba4508 | Nikos Skalkotos | import signal |
33 | ebba4508 | Nikos Skalkotos | import socket |
34 | ebba4508 | Nikos Skalkotos | |
35 | ebba4508 | Nikos Skalkotos | |
36 | ebba4508 | Nikos Skalkotos | def parse_arguments(args): |
37 | ebba4508 | Nikos Skalkotos | from optparse import OptionParser |
38 | ebba4508 | Nikos Skalkotos | |
39 | ebba4508 | Nikos Skalkotos | kw = {} |
40 | ebba4508 | Nikos Skalkotos | kw['usage'] = "%prog [options] command [args...]" |
41 | ebba4508 | Nikos Skalkotos | kw['description'] = \
|
42 | ebba4508 | Nikos Skalkotos | "%prog runs 'command' with the specified arguments, monitoring the " \
|
43 | ebba4508 | Nikos Skalkotos | "number of bytes read by it. 'command' is assumed to be " \
|
44 | ebba4508 | Nikos Skalkotos | "A program used to install the OS for a Ganeti instance. %prog " \
|
45 | ebba4508 | Nikos Skalkotos | "periodically issues notifications of type 'ganeti-copy-progress'."
|
46 | ebba4508 | Nikos Skalkotos | |
47 | ebba4508 | Nikos Skalkotos | parser = OptionParser(**kw) |
48 | ebba4508 | Nikos Skalkotos | parser.disable_interspersed_args() |
49 | ebba4508 | Nikos Skalkotos | parser.add_option("-r", "--read-bytes", |
50 | ebba4508 | Nikos Skalkotos | action="store", type="int", dest="read_bytes", |
51 | ebba4508 | Nikos Skalkotos | metavar="BYTES_TO_READ",
|
52 | ebba4508 | Nikos Skalkotos | help="The expected number of bytes to be read, " \
|
53 | ebba4508 | Nikos Skalkotos | "used to compute input progress",
|
54 | ebba4508 | Nikos Skalkotos | default=None)
|
55 | ebba4508 | Nikos Skalkotos | parser.add_option("-i", "--instance-name", dest="instance_name", |
56 | ebba4508 | Nikos Skalkotos | default=None, metavar="GANETI_INSTANCE", |
57 | ebba4508 | Nikos Skalkotos | help="The Ganeti instance name to be used in AMQP " \
|
58 | ebba4508 | Nikos Skalkotos | "notifications")
|
59 | ebba4508 | Nikos Skalkotos | parser.add_option("-o", "--output", dest="output", default=None, |
60 | ebba4508 | Nikos Skalkotos | metavar="FILE",
|
61 | ebba4508 | Nikos Skalkotos | help="Write output notifications to this file")
|
62 | ebba4508 | Nikos Skalkotos | |
63 | ebba4508 | Nikos Skalkotos | (opts, args) = parser.parse_args(args) |
64 | ebba4508 | Nikos Skalkotos | if opts.instance_name is None: |
65 | ebba4508 | Nikos Skalkotos | sys.stderr.write("Fatal: Option '-i' is mandatory.\n")
|
66 | ebba4508 | Nikos Skalkotos | parser.print_help() |
67 | ebba4508 | Nikos Skalkotos | sys.exit(1)
|
68 | ebba4508 | Nikos Skalkotos | |
69 | ebba4508 | Nikos Skalkotos | if opts.read_bytes is None: |
70 | ebba4508 | Nikos Skalkotos | sys.stderr.write("Fatal: Option '-r' is mandatory.\n")
|
71 | ebba4508 | Nikos Skalkotos | parser.print_help() |
72 | ebba4508 | Nikos Skalkotos | sys.exit(1)
|
73 | ebba4508 | Nikos Skalkotos | |
74 | ebba4508 | Nikos Skalkotos | if opts.output is None: |
75 | ebba4508 | Nikos Skalkotos | sys.stderr.write("Fatal: Option '-o' is mandatory.\n")
|
76 | ebba4508 | Nikos Skalkotos | parser.print_help() |
77 | ebba4508 | Nikos Skalkotos | sys.exit(1)
|
78 | ebba4508 | Nikos Skalkotos | |
79 | ebba4508 | Nikos Skalkotos | if len(args) == 0: |
80 | ebba4508 | Nikos Skalkotos | sys.stderr.write("Fatal: You need to specify the command to run.\n")
|
81 | ebba4508 | Nikos Skalkotos | parser.print_help() |
82 | ebba4508 | Nikos Skalkotos | sys.exit(1)
|
83 | ebba4508 | Nikos Skalkotos | |
84 | ebba4508 | Nikos Skalkotos | return (opts, args)
|
85 | ebba4508 | Nikos Skalkotos | |
86 | ebba4508 | Nikos Skalkotos | |
87 | ebba4508 | Nikos Skalkotos | def report_wait_status(pid, status): |
88 | ebba4508 | Nikos Skalkotos | if os.WIFEXITED(status):
|
89 | ebba4508 | Nikos Skalkotos | sys.stderr.write("Child PID = %d exited, status = %d\n" %
|
90 | ebba4508 | Nikos Skalkotos | (pid, os.WEXITSTATUS(status))) |
91 | ebba4508 | Nikos Skalkotos | elif os.WIFSIGNALED(status):
|
92 | ebba4508 | Nikos Skalkotos | sys.stderr.write("Child PID = %d died by signal, signal = %d\n" %
|
93 | ebba4508 | Nikos Skalkotos | (pid, os.WTERMSIG(status))) |
94 | ebba4508 | Nikos Skalkotos | elif os.WIFSTOPPED(status):
|
95 | ebba4508 | Nikos Skalkotos | sys.stderr.write("Child PID = %d stopped by signal, signal = %d\n" %
|
96 | ebba4508 | Nikos Skalkotos | (pid, os.WSTOPSIG(status))) |
97 | ebba4508 | Nikos Skalkotos | else:
|
98 | ebba4508 | Nikos Skalkotos | sys.stderr.write("Internal error: Unhandled case, " \
|
99 | ebba4508 | Nikos Skalkotos | "PID = %d, status = %d\n" % (pid, status))
|
100 | ebba4508 | Nikos Skalkotos | sys.exit(1)
|
101 | ebba4508 | Nikos Skalkotos | sys.stderr.flush() |
102 | ebba4508 | Nikos Skalkotos | |
103 | ebba4508 | Nikos Skalkotos | |
104 | ebba4508 | Nikos Skalkotos | def send_message(to, message): |
105 | ebba4508 | Nikos Skalkotos | message['timestamp'] = int(time.time()) |
106 | ebba4508 | Nikos Skalkotos | to.write("%s\n" % json.dumps(message))
|
107 | ebba4508 | Nikos Skalkotos | to.flush() |
108 | ebba4508 | Nikos Skalkotos | |
109 | ebba4508 | Nikos Skalkotos | |
110 | ebba4508 | Nikos Skalkotos | def main(): |
111 | ebba4508 | Nikos Skalkotos | (opts, args) = parse_arguments(sys.argv[1:])
|
112 | ebba4508 | Nikos Skalkotos | |
113 | ebba4508 | Nikos Skalkotos | with open(opts.output, 'w') as out: |
114 | ebba4508 | Nikos Skalkotos | pid = os.fork() |
115 | ebba4508 | Nikos Skalkotos | if pid == 0: |
116 | ebba4508 | Nikos Skalkotos | # In child process:
|
117 | ebba4508 | Nikos Skalkotos | |
118 | ebba4508 | Nikos Skalkotos | # Make sure we die with the parent and are not left behind
|
119 | ebba4508 | Nikos Skalkotos | # WARNING: This uses the prctl(2) call and is Linux-specific.
|
120 | ebba4508 | Nikos Skalkotos | prctl.set_pdeathsig(signal.SIGHUP) |
121 | ebba4508 | Nikos Skalkotos | |
122 | ebba4508 | Nikos Skalkotos | # exec command specified in arguments,
|
123 | ebba4508 | Nikos Skalkotos | # searching the $PATH, keeping all environment
|
124 | ebba4508 | Nikos Skalkotos | os.execvpe(args[0], args, os.environ)
|
125 | ebba4508 | Nikos Skalkotos | sys.stderr.write("execvpe failed, exiting with non-zero status")
|
126 | ebba4508 | Nikos Skalkotos | os.exit(1)
|
127 | ebba4508 | Nikos Skalkotos | |
128 | ebba4508 | Nikos Skalkotos | # In parent process:
|
129 | ebba4508 | Nikos Skalkotos | iofname = "/proc/%d/io" % pid
|
130 | ebba4508 | Nikos Skalkotos | iof = open(iofname, "r", 0) # 0: unbuffered open |
131 | ebba4508 | Nikos Skalkotos | sys.stderr.write("%s: created child PID = %d, monitoring file %s\n" %
|
132 | ebba4508 | Nikos Skalkotos | (sys.argv[0], pid, iofname))
|
133 | ebba4508 | Nikos Skalkotos | |
134 | ebba4508 | Nikos Skalkotos | message = {} |
135 | ebba4508 | Nikos Skalkotos | message['id'] = opts.instance_name
|
136 | ebba4508 | Nikos Skalkotos | message['type'] = 'ganeti-copy-progress' |
137 | ebba4508 | Nikos Skalkotos | message['total'] = opts.read_bytes
|
138 | ebba4508 | Nikos Skalkotos | |
139 | ebba4508 | Nikos Skalkotos | while True: |
140 | ebba4508 | Nikos Skalkotos | # check if the child process is still alive
|
141 | ebba4508 | Nikos Skalkotos | (wpid, status) = os.waitpid(pid, os.WNOHANG) |
142 | ebba4508 | Nikos Skalkotos | if wpid == pid:
|
143 | ebba4508 | Nikos Skalkotos | report_wait_status(pid, status) |
144 | ebba4508 | Nikos Skalkotos | if (os.WIFEXITED(status) or os.WIFSIGNALED(status)): |
145 | ebba4508 | Nikos Skalkotos | if not (os.WIFEXITED(status) and |
146 | ebba4508 | Nikos Skalkotos | os.WEXITSTATUS(status) == 0):
|
147 | ebba4508 | Nikos Skalkotos | return 1 |
148 | ebba4508 | Nikos Skalkotos | else:
|
149 | ebba4508 | Nikos Skalkotos | message['position'] = message['total'] |
150 | ebba4508 | Nikos Skalkotos | message['rprogress'] = float(100) |
151 | ebba4508 | Nikos Skalkotos | send_message(out, message) |
152 | ebba4508 | Nikos Skalkotos | return 0 |
153 | ebba4508 | Nikos Skalkotos | |
154 | ebba4508 | Nikos Skalkotos | iof.seek(0)
|
155 | ebba4508 | Nikos Skalkotos | for l in iof.readlines(): |
156 | ebba4508 | Nikos Skalkotos | if l.startswith("rchar:"): |
157 | ebba4508 | Nikos Skalkotos | message['position'] = int(l.split(': ')[1]) |
158 | ebba4508 | Nikos Skalkotos | message['rprogress'] = float(100) if opts.read_bytes == 0 \ |
159 | ebba4508 | Nikos Skalkotos | else float("%2.2f" % ( |
160 | ebba4508 | Nikos Skalkotos | message['position'] * 100.0 / message['total'])) |
161 | ebba4508 | Nikos Skalkotos | send_message(out, message) |
162 | ebba4508 | Nikos Skalkotos | break
|
163 | ebba4508 | Nikos Skalkotos | |
164 | ebba4508 | Nikos Skalkotos | # Sleep for a while
|
165 | ebba4508 | Nikos Skalkotos | time.sleep(3)
|
166 | ebba4508 | Nikos Skalkotos | |
167 | ebba4508 | Nikos Skalkotos | |
168 | ebba4508 | Nikos Skalkotos | if __name__ == "__main__": |
169 | ebba4508 | Nikos Skalkotos | sys.exit(main()) |
170 | ebba4508 | Nikos Skalkotos | |
171 | ebba4508 | Nikos Skalkotos | # vim: set sta sts=4 shiftwidth=4 sw=4 et ai : |