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