Statistics
| Branch: | Tag: | Revision:

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())