root / snf-cyclades-gtools / synnefo / ganeti / progress_monitor.py @ 996e5d53
History | View | Annotate | Download (8.1 kB)
1 | dcb4a587 | Vangelis Koukis | #!/usr/bin/env python
|
---|---|---|---|
2 | dcb4a587 | Vangelis Koukis | # -*- coding: utf-8 -*-
|
3 | dcb4a587 | Vangelis Koukis | #
|
4 | dcb4a587 | Vangelis Koukis | # Copyright 2011 GRNET S.A. All rights reserved.
|
5 | dcb4a587 | Vangelis Koukis | #
|
6 | dcb4a587 | Vangelis Koukis | # Redistribution and use in source and binary forms, with or
|
7 | dcb4a587 | Vangelis Koukis | # without modification, are permitted provided that the following
|
8 | dcb4a587 | Vangelis Koukis | # conditions are met:
|
9 | dcb4a587 | Vangelis Koukis | #
|
10 | dcb4a587 | Vangelis Koukis | # 1. Redistributions of source code must retain the above
|
11 | dcb4a587 | Vangelis Koukis | # copyright notice, this list of conditions and the following
|
12 | dcb4a587 | Vangelis Koukis | # disclaimer.
|
13 | dcb4a587 | Vangelis Koukis | #
|
14 | dcb4a587 | Vangelis Koukis | # 2. Redistributions in binary form must reproduce the above
|
15 | dcb4a587 | Vangelis Koukis | # copyright notice, this list of conditions and the following
|
16 | dcb4a587 | Vangelis Koukis | # disclaimer in the documentation and/or other materials
|
17 | dcb4a587 | Vangelis Koukis | # provided with the distribution.
|
18 | dcb4a587 | Vangelis Koukis | #
|
19 | dcb4a587 | Vangelis Koukis | # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
|
20 | dcb4a587 | Vangelis Koukis | # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
21 | dcb4a587 | Vangelis Koukis | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
22 | dcb4a587 | Vangelis Koukis | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
|
23 | dcb4a587 | Vangelis Koukis | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
24 | dcb4a587 | Vangelis Koukis | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
25 | dcb4a587 | Vangelis Koukis | # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
26 | dcb4a587 | Vangelis Koukis | # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
27 | dcb4a587 | Vangelis Koukis | # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
28 | dcb4a587 | Vangelis Koukis | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
29 | dcb4a587 | Vangelis Koukis | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
30 | dcb4a587 | Vangelis Koukis | # POSSIBILITY OF SUCH DAMAGE.
|
31 | dcb4a587 | Vangelis Koukis | #
|
32 | dcb4a587 | Vangelis Koukis | # The views and conclusions contained in the software and
|
33 | dcb4a587 | Vangelis Koukis | # documentation are those of the authors and should not be
|
34 | dcb4a587 | Vangelis Koukis | # interpreted as representing official policies, either expressed
|
35 | dcb4a587 | Vangelis Koukis | # or implied, of GRNET S.A.
|
36 | dcb4a587 | Vangelis Koukis | #
|
37 | 45ebfd48 | Vangelis Koukis | """Utility to monitor the progress of image deployment
|
38 | 45ebfd48 | Vangelis Koukis |
|
39 | 45ebfd48 | Vangelis Koukis | A small utility to monitor the progress of image deployment
|
40 | 45ebfd48 | Vangelis Koukis | by watching the contents of /proc/<pid>/io and producing
|
41 | 45ebfd48 | Vangelis Koukis | notifications of type 'ganeti-create-progress' to the rest
|
42 | 45ebfd48 | Vangelis Koukis | of the Synnefo infrastructure over AMQP.
|
43 | 45ebfd48 | Vangelis Koukis |
|
44 | 45ebfd48 | Vangelis Koukis | """
|
45 | 45ebfd48 | Vangelis Koukis | |
46 | dcb4a587 | Vangelis Koukis | import os |
47 | dcb4a587 | Vangelis Koukis | import sys |
48 | dcb4a587 | Vangelis Koukis | import time |
49 | dcb4a587 | Vangelis Koukis | import json |
50 | dcb4a587 | Vangelis Koukis | import prctl |
51 | dcb4a587 | Vangelis Koukis | import signal |
52 | dcb4a587 | Vangelis Koukis | import socket |
53 | dcb4a587 | Vangelis Koukis | |
54 | 6d6b8f88 | Kostas Papadimitriou | from synnefo import settings |
55 | c4e55622 | Christos Stavrakakis | from synnefo.lib.amqp import AMQPClient |
56 | c4e55622 | Christos Stavrakakis | from synnefo.lib.utils import split_time |
57 | dcb4a587 | Vangelis Koukis | |
58 | dcb4a587 | Vangelis Koukis | |
59 | dcb4a587 | Vangelis Koukis | def parse_arguments(args): |
60 | dcb4a587 | Vangelis Koukis | from optparse import OptionParser |
61 | dcb4a587 | Vangelis Koukis | |
62 | dcb4a587 | Vangelis Koukis | kw = {} |
63 | dcb4a587 | Vangelis Koukis | kw['usage'] = "%prog [options] command [args...]" |
64 | dcb4a587 | Vangelis Koukis | kw['description'] = \
|
65 | dcb4a587 | Vangelis Koukis | "%prog runs 'command' with the specified arguments, monitoring the " \
|
66 | dcb4a587 | Vangelis Koukis | "number of bytes read and written by it. 'command' is assumed to be " \
|
67 | dcb4a587 | Vangelis Koukis | "A program used to install the OS for a Ganeti instance. %prog " \
|
68 | dcb4a587 | Vangelis Koukis | "periodically issues notifications of type 'ganeti-create-progress' " \
|
69 | dcb4a587 | Vangelis Koukis | "to the rest of the Synnefo infrastructure over AMQP."
|
70 | dcb4a587 | Vangelis Koukis | |
71 | dcb4a587 | Vangelis Koukis | parser = OptionParser(**kw) |
72 | dcb4a587 | Vangelis Koukis | parser.disable_interspersed_args() |
73 | dcb4a587 | Vangelis Koukis | parser.add_option("-r", "--read-bytes", |
74 | dcb4a587 | Vangelis Koukis | action="store", type="int", dest="read_bytes", |
75 | dcb4a587 | Vangelis Koukis | metavar="BYTES_TO_READ",
|
76 | dcb4a587 | Vangelis Koukis | help="The expected number of bytes to be read, " \
|
77 | dcb4a587 | Vangelis Koukis | "used to compute input progress",
|
78 | dcb4a587 | Vangelis Koukis | default=0)
|
79 | dcb4a587 | Vangelis Koukis | parser.add_option("-w", "--write-bytes", |
80 | dcb4a587 | Vangelis Koukis | action="store", type="int", dest="write_bytes", |
81 | dcb4a587 | Vangelis Koukis | metavar="BYTES_TO_WRITE",
|
82 | dcb4a587 | Vangelis Koukis | help="The expected number of bytes to be written, " \
|
83 | dcb4a587 | Vangelis Koukis | "used to compute output progress",
|
84 | dcb4a587 | Vangelis Koukis | default=0)
|
85 | dcb4a587 | Vangelis Koukis | parser.add_option("-i", "--instance-name", |
86 | dcb4a587 | Vangelis Koukis | dest="instance_name",
|
87 | dcb4a587 | Vangelis Koukis | metavar="GANETI_INSTANCE",
|
88 | dcb4a587 | Vangelis Koukis | help="The Ganeti instance name to be used in AMQP " \
|
89 | dcb4a587 | Vangelis Koukis | "notifications")
|
90 | dcb4a587 | Vangelis Koukis | |
91 | dcb4a587 | Vangelis Koukis | (opts, args) = parser.parse_args(args) |
92 | dcb4a587 | Vangelis Koukis | |
93 | dcb4a587 | Vangelis Koukis | if opts.instance_name is None or (opts.read_bytes == 0 and |
94 | dcb4a587 | Vangelis Koukis | opts.write_bytes == 0):
|
95 | dcb4a587 | Vangelis Koukis | sys.stderr.write("Fatal: Options '-i' and at least one of '-r' " \
|
96 | dcb4a587 | Vangelis Koukis | "or '-w' are mandatory.\n")
|
97 | dcb4a587 | Vangelis Koukis | parser.print_help() |
98 | dcb4a587 | Vangelis Koukis | sys.exit(1)
|
99 | dcb4a587 | Vangelis Koukis | |
100 | dcb4a587 | Vangelis Koukis | if len(args) == 0: |
101 | dcb4a587 | Vangelis Koukis | sys.stderr.write("Fatal: You need to specify the command to run.\n")
|
102 | dcb4a587 | Vangelis Koukis | parser.print_help() |
103 | dcb4a587 | Vangelis Koukis | sys.exit(1)
|
104 | dcb4a587 | Vangelis Koukis | |
105 | dcb4a587 | Vangelis Koukis | return (opts, args)
|
106 | dcb4a587 | Vangelis Koukis | |
107 | dcb4a587 | Vangelis Koukis | |
108 | dcb4a587 | Vangelis Koukis | def report_wait_status(pid, status): |
109 | dcb4a587 | Vangelis Koukis | if os.WIFEXITED(status):
|
110 | dcb4a587 | Vangelis Koukis | sys.stderr.write("Child PID = %d exited, status = %d\n" %
|
111 | dcb4a587 | Vangelis Koukis | (pid, os.WEXITSTATUS(status))) |
112 | dcb4a587 | Vangelis Koukis | elif os.WIFSIGNALED(status):
|
113 | dcb4a587 | Vangelis Koukis | sys.stderr.write("Child PID = %d died by signal, signal = %d\n" %
|
114 | dcb4a587 | Vangelis Koukis | (pid, os.WTERMSIG(status))) |
115 | dcb4a587 | Vangelis Koukis | elif os.WIFSTOPPED(status):
|
116 | dcb4a587 | Vangelis Koukis | sys.stderr.write("Child PID = %d stopped by signal, signal = %d\n" %
|
117 | dcb4a587 | Vangelis Koukis | (pid, os.WSTOPSIG(status))) |
118 | dcb4a587 | Vangelis Koukis | else:
|
119 | dcb4a587 | Vangelis Koukis | sys.stderr.write("Internal error: Unhandled case, " \
|
120 | dcb4a587 | Vangelis Koukis | "PID = %d, status = %d\n" % (pid, status))
|
121 | dcb4a587 | Vangelis Koukis | sys.exit(1)
|
122 | dcb4a587 | Vangelis Koukis | sys.stderr.flush() |
123 | dcb4a587 | Vangelis Koukis | |
124 | dcb4a587 | Vangelis Koukis | |
125 | dcb4a587 | Vangelis Koukis | def main(): |
126 | dcb4a587 | Vangelis Koukis | (opts, args) = parse_arguments(sys.argv[1:])
|
127 | dcb4a587 | Vangelis Koukis | |
128 | dcb4a587 | Vangelis Koukis | # WARNING: This assumes that instance names
|
129 | dcb4a587 | Vangelis Koukis | # are of the form prefix-id, and uses prefix to
|
130 | dcb4a587 | Vangelis Koukis | # determine the routekey for AMPQ
|
131 | dcb4a587 | Vangelis Koukis | prefix = opts.instance_name.split('-')[0] |
132 | dcb4a587 | Vangelis Koukis | routekey = "ganeti.%s.event.progress" % prefix
|
133 | 996e5d53 | Christos Stavrakakis | amqp_client = AMQPClient(hosts=settings.AMQP_HOSTS, confirm_buffer=2)
|
134 | c4e55622 | Christos Stavrakakis | amqp_client.connect() |
135 | 996e5d53 | Christos Stavrakakis | amqp_client.exchange_declare(settings.EXCHANGE_GANETI, type='topic')
|
136 | dcb4a587 | Vangelis Koukis | |
137 | dcb4a587 | Vangelis Koukis | pid = os.fork() |
138 | dcb4a587 | Vangelis Koukis | if pid == 0: |
139 | dcb4a587 | Vangelis Koukis | # In child process:
|
140 | dcb4a587 | Vangelis Koukis | |
141 | 3db5ad86 | Vangelis Koukis | # Make sure we die with the parent and are not left behind
|
142 | dcb4a587 | Vangelis Koukis | # WARNING: This uses the prctl(2) call and is Linux-specific.
|
143 | dcb4a587 | Vangelis Koukis | prctl.set_pdeathsig(signal.SIGHUP) |
144 | 3db5ad86 | Vangelis Koukis | |
145 | dcb4a587 | Vangelis Koukis | # exec command specified in arguments,
|
146 | dcb4a587 | Vangelis Koukis | # searching the $PATH, keeping all environment
|
147 | dcb4a587 | Vangelis Koukis | os.execvpe(args[0], args, os.environ)
|
148 | dcb4a587 | Vangelis Koukis | sys.stderr.write("execvpe failed, exiting with non-zero status")
|
149 | dcb4a587 | Vangelis Koukis | os.exit(1)
|
150 | dcb4a587 | Vangelis Koukis | |
151 | dcb4a587 | Vangelis Koukis | # In parent process:
|
152 | dcb4a587 | Vangelis Koukis | iofname = "/proc/%d/io" % pid
|
153 | dcb4a587 | Vangelis Koukis | iof = open(iofname, "r", 0) # 0: unbuffered open |
154 | dcb4a587 | Vangelis Koukis | sys.stderr.write("%s: created child PID = %d, monitoring file %s\n" %
|
155 | dcb4a587 | Vangelis Koukis | (sys.argv[0], pid, iofname))
|
156 | dcb4a587 | Vangelis Koukis | |
157 | dcb4a587 | Vangelis Koukis | while True: |
158 | dcb4a587 | Vangelis Koukis | # check if the child process is still alive
|
159 | dcb4a587 | Vangelis Koukis | (wpid, status) = os.waitpid(pid, os.WNOHANG) |
160 | dcb4a587 | Vangelis Koukis | if wpid == pid:
|
161 | dcb4a587 | Vangelis Koukis | report_wait_status(pid, status) |
162 | dcb4a587 | Vangelis Koukis | if (os.WIFEXITED(status) or os.WIFSIGNALED(status)): |
163 | dcb4a587 | Vangelis Koukis | if not (os.WIFEXITED(status) and os.WEXITSTATUS(status) == 0): |
164 | dcb4a587 | Vangelis Koukis | return 1 |
165 | dcb4a587 | Vangelis Koukis | else:
|
166 | 3d765f0c | Nikos Skalkotos | # send a final notification
|
167 | 3d765f0c | Nikos Skalkotos | final_msg = dict(type="ganeti-create-progress", |
168 | 3d765f0c | Nikos Skalkotos | instance=opts.instance_name) |
169 | c4e55622 | Christos Stavrakakis | final_msg['event_time'] = split_time(time.time())
|
170 | 3d765f0c | Nikos Skalkotos | if opts.read_bytes:
|
171 | 3d765f0c | Nikos Skalkotos | final_msg['rprogress'] = float(100) |
172 | 3d765f0c | Nikos Skalkotos | if opts.write_bytes:
|
173 | 3d765f0c | Nikos Skalkotos | final_msg['wprogress'] = float(100) |
174 | c4e55622 | Christos Stavrakakis | amqp_client.basic_publish(exchange=settings.EXCHANGE_GANETI, |
175 | c4e55622 | Christos Stavrakakis | routing_key=routekey, |
176 | c4e55622 | Christos Stavrakakis | body=json.dumps(final_msg)) |
177 | dcb4a587 | Vangelis Koukis | return 0 |
178 | dcb4a587 | Vangelis Koukis | |
179 | dcb4a587 | Vangelis Koukis | # retrieve the current values of the read/write byte counters
|
180 | dcb4a587 | Vangelis Koukis | iof.seek(0)
|
181 | dcb4a587 | Vangelis Koukis | for l in iof.readlines(): |
182 | dcb4a587 | Vangelis Koukis | if l.startswith("rchar:"): |
183 | dcb4a587 | Vangelis Koukis | rchar = int(l.split(': ')[1]) |
184 | dcb4a587 | Vangelis Koukis | if l.startswith("wchar:"): |
185 | dcb4a587 | Vangelis Koukis | wchar = int(l.split(': ')[1]) |
186 | dcb4a587 | Vangelis Koukis | |
187 | dcb4a587 | Vangelis Koukis | # Construct notification of type 'ganeti-create-progress'
|
188 | dcb4a587 | Vangelis Koukis | msg = dict(type="ganeti-create-progress", |
189 | dcb4a587 | Vangelis Koukis | instance=opts.instance_name) |
190 | c4e55622 | Christos Stavrakakis | msg['event_time'] = split_time(time.time())
|
191 | dcb4a587 | Vangelis Koukis | if opts.read_bytes:
|
192 | 3db5ad86 | Vangelis Koukis | msg['rprogress'] = float("%2.2f" % |
193 | 3db5ad86 | Vangelis Koukis | (rchar * 100.0 / opts.read_bytes))
|
194 | dcb4a587 | Vangelis Koukis | if opts.write_bytes:
|
195 | 3db5ad86 | Vangelis Koukis | msg['wprogress'] = float("%2.2f" % |
196 | 3db5ad86 | Vangelis Koukis | (wchar * 100.0 / opts.write_bytes))
|
197 | dcb4a587 | Vangelis Koukis | |
198 | dcb4a587 | Vangelis Koukis | # and send it over AMQP
|
199 | c4e55622 | Christos Stavrakakis | amqp_client.basic_publish(exchange=settings.EXCHANGE_GANETI, |
200 | c4e55622 | Christos Stavrakakis | routing_key=routekey, |
201 | c4e55622 | Christos Stavrakakis | body=json.dumps(msg)) |
202 | 3db5ad86 | Vangelis Koukis | |
203 | dcb4a587 | Vangelis Koukis | # Sleep for a while
|
204 | dcb4a587 | Vangelis Koukis | time.sleep(3)
|
205 | dcb4a587 | Vangelis Koukis | |
206 | 996e5d53 | Christos Stavrakakis | amqp_client.close() |
207 | 996e5d53 | Christos Stavrakakis | |
208 | dcb4a587 | Vangelis Koukis | |
209 | dcb4a587 | Vangelis Koukis | if __name__ == "__main__": |
210 | dcb4a587 | Vangelis Koukis | sys.exit(main()) |