Statistics
| Branch: | Tag: | Revision:

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 :