Statistics
| Branch: | Tag: | Revision:

root / snf-image-host / copy-monitor.py @ master

History | View | Annotate | Download (5.3 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 f58bbc57 Nikos Skalkotos
MSG_TYPE = "image-copy-progress"
36 f58bbc57 Nikos Skalkotos
37 ebba4508 Nikos Skalkotos
38 ebba4508 Nikos Skalkotos
def parse_arguments(args):
39 ebba4508 Nikos Skalkotos
    from optparse import OptionParser
40 ebba4508 Nikos Skalkotos
41 ebba4508 Nikos Skalkotos
    kw = {}
42 ebba4508 Nikos Skalkotos
    kw['usage'] = "%prog [options] command [args...]"
43 ebba4508 Nikos Skalkotos
    kw['description'] = \
44 ebba4508 Nikos Skalkotos
        "%prog runs 'command' with the specified arguments, monitoring the " \
45 ebba4508 Nikos Skalkotos
        "number of bytes read by it. 'command' is assumed to be " \
46 ebba4508 Nikos Skalkotos
        "A program used to install the OS for a Ganeti instance. %prog " \
47 df1c62e2 Nikos Skalkotos
        "periodically issues notifications of type 'copy-progress'."
48 ebba4508 Nikos Skalkotos
49 ebba4508 Nikos Skalkotos
    parser = OptionParser(**kw)
50 ebba4508 Nikos Skalkotos
    parser.disable_interspersed_args()
51 ebba4508 Nikos Skalkotos
    parser.add_option("-r", "--read-bytes",
52 ebba4508 Nikos Skalkotos
                      action="store", type="int", dest="read_bytes",
53 ebba4508 Nikos Skalkotos
                      metavar="BYTES_TO_READ",
54 f58bbc57 Nikos Skalkotos
                      help="The expected number of bytes to be read, "
55 ebba4508 Nikos Skalkotos
                           "used to compute input progress",
56 ebba4508 Nikos Skalkotos
                      default=None)
57 f58bbc57 Nikos Skalkotos
    parser.add_option(
58 f58bbc57 Nikos Skalkotos
        "-o", "--output_fd", dest="output", default=None, metavar="FILE",
59 f58bbc57 Nikos Skalkotos
        type="int", help="Write output notifications to this file descriptor")
60 ebba4508 Nikos Skalkotos
61 ebba4508 Nikos Skalkotos
    (opts, args) = parser.parse_args(args)
62 ebba4508 Nikos Skalkotos
63 ebba4508 Nikos Skalkotos
    if opts.read_bytes is None:
64 ebba4508 Nikos Skalkotos
        sys.stderr.write("Fatal: Option '-r' is mandatory.\n")
65 ebba4508 Nikos Skalkotos
        parser.print_help()
66 ebba4508 Nikos Skalkotos
        sys.exit(1)
67 ebba4508 Nikos Skalkotos
68 ebba4508 Nikos Skalkotos
    if opts.output is None:
69 ebba4508 Nikos Skalkotos
        sys.stderr.write("Fatal: Option '-o' is mandatory.\n")
70 ebba4508 Nikos Skalkotos
        parser.print_help()
71 ebba4508 Nikos Skalkotos
        sys.exit(1)
72 ebba4508 Nikos Skalkotos
73 ebba4508 Nikos Skalkotos
    if len(args) == 0:
74 ebba4508 Nikos Skalkotos
        sys.stderr.write("Fatal: You need to specify the command to run.\n")
75 ebba4508 Nikos Skalkotos
        parser.print_help()
76 ebba4508 Nikos Skalkotos
        sys.exit(1)
77 ebba4508 Nikos Skalkotos
78 ebba4508 Nikos Skalkotos
    return (opts, args)
79 ebba4508 Nikos Skalkotos
80 ebba4508 Nikos Skalkotos
81 ebba4508 Nikos Skalkotos
def report_wait_status(pid, status):
82 ebba4508 Nikos Skalkotos
    if os.WIFEXITED(status):
83 ebba4508 Nikos Skalkotos
        sys.stderr.write("Child PID = %d exited, status = %d\n" %
84 ebba4508 Nikos Skalkotos
                         (pid, os.WEXITSTATUS(status)))
85 ebba4508 Nikos Skalkotos
    elif os.WIFSIGNALED(status):
86 ebba4508 Nikos Skalkotos
        sys.stderr.write("Child PID = %d died by signal, signal = %d\n" %
87 ebba4508 Nikos Skalkotos
                         (pid, os.WTERMSIG(status)))
88 ebba4508 Nikos Skalkotos
    elif os.WIFSTOPPED(status):
89 ebba4508 Nikos Skalkotos
        sys.stderr.write("Child PID = %d stopped by signal, signal = %d\n" %
90 ebba4508 Nikos Skalkotos
                         (pid, os.WSTOPSIG(status)))
91 ebba4508 Nikos Skalkotos
    else:
92 f58bbc57 Nikos Skalkotos
        sys.stderr.write("Internal error: Unhandled case, "
93 ebba4508 Nikos Skalkotos
                         "PID = %d, status = %d\n" % (pid, status))
94 ebba4508 Nikos Skalkotos
        sys.exit(1)
95 ebba4508 Nikos Skalkotos
    sys.stderr.flush()
96 ebba4508 Nikos Skalkotos
97 ebba4508 Nikos Skalkotos
98 ebba4508 Nikos Skalkotos
def send_message(to, message):
99 df1c62e2 Nikos Skalkotos
    message['timestamp'] = time.time()
100 87d1bf2e Nikos Skalkotos
    os.write(to, "%s\n" % json.dumps(message))
101 ebba4508 Nikos Skalkotos
102 ebba4508 Nikos Skalkotos
103 ebba4508 Nikos Skalkotos
def main():
104 ebba4508 Nikos Skalkotos
    (opts, args) = parse_arguments(sys.argv[1:])
105 87d1bf2e Nikos Skalkotos
    out = opts.output
106 87d1bf2e Nikos Skalkotos
    pid = os.fork()
107 87d1bf2e Nikos Skalkotos
    if pid == 0:
108 87d1bf2e Nikos Skalkotos
        # In child process:
109 87d1bf2e Nikos Skalkotos
110 87d1bf2e Nikos Skalkotos
        # Make sure we die with the parent and are not left behind
111 87d1bf2e Nikos Skalkotos
        # WARNING: This uses the prctl(2) call and is Linux-specific.
112 87d1bf2e Nikos Skalkotos
        prctl.set_pdeathsig(signal.SIGHUP)
113 87d1bf2e Nikos Skalkotos
114 87d1bf2e Nikos Skalkotos
        # exec command specified in arguments,
115 87d1bf2e Nikos Skalkotos
        # searching the $PATH, keeping all environment
116 87d1bf2e Nikos Skalkotos
        os.execvpe(args[0], args, os.environ)
117 87d1bf2e Nikos Skalkotos
        sys.stderr.write("execvpe failed, exiting with non-zero status")
118 87d1bf2e Nikos Skalkotos
        os.exit(1)
119 87d1bf2e Nikos Skalkotos
120 87d1bf2e Nikos Skalkotos
    # In parent process:
121 87d1bf2e Nikos Skalkotos
    iofname = "/proc/%d/io" % pid
122 87d1bf2e Nikos Skalkotos
    iof = open(iofname, "r", 0)   # 0: unbuffered open
123 87d1bf2e Nikos Skalkotos
    sys.stderr.write("%s: created child PID = %d, monitoring file %s\n" %
124 87d1bf2e Nikos Skalkotos
                     (sys.argv[0], pid, iofname))
125 87d1bf2e Nikos Skalkotos
126 87d1bf2e Nikos Skalkotos
    message = {}
127 a50a2bff Nikos Skalkotos
    message['type'] = MSG_TYPE
128 87d1bf2e Nikos Skalkotos
    message['total'] = opts.read_bytes
129 87d1bf2e Nikos Skalkotos
130 87d1bf2e Nikos Skalkotos
    while True:
131 87d1bf2e Nikos Skalkotos
        # check if the child process is still alive
132 87d1bf2e Nikos Skalkotos
        (wpid, status) = os.waitpid(pid, os.WNOHANG)
133 87d1bf2e Nikos Skalkotos
        if wpid == pid:
134 87d1bf2e Nikos Skalkotos
            report_wait_status(pid, status)
135 87d1bf2e Nikos Skalkotos
            if (os.WIFEXITED(status) or os.WIFSIGNALED(status)):
136 f58bbc57 Nikos Skalkotos
                if not (os.WIFEXITED(status) and os.WEXITSTATUS(status) == 0):
137 87d1bf2e Nikos Skalkotos
                    return 1
138 87d1bf2e Nikos Skalkotos
                else:
139 87d1bf2e Nikos Skalkotos
                    message['position'] = message['total']
140 87d1bf2e Nikos Skalkotos
                    message['progress'] = float(100)
141 ebba4508 Nikos Skalkotos
                    send_message(out, message)
142 87d1bf2e Nikos Skalkotos
                    return 0
143 87d1bf2e Nikos Skalkotos
144 87d1bf2e Nikos Skalkotos
        iof.seek(0)
145 87d1bf2e Nikos Skalkotos
        for l in iof.readlines():
146 87d1bf2e Nikos Skalkotos
            if l.startswith("rchar:"):
147 87d1bf2e Nikos Skalkotos
                message['position'] = int(l.split(': ')[1])
148 42ed2a70 Nikos Skalkotos
                message['progress'] = float(0) if opts.read_bytes == 0 \
149 87d1bf2e Nikos Skalkotos
                    else float("%2.2f" % (
150 87d1bf2e Nikos Skalkotos
                        message['position'] * 100.0 / message['total']))
151 87d1bf2e Nikos Skalkotos
                send_message(out, message)
152 87d1bf2e Nikos Skalkotos
                break
153 87d1bf2e Nikos Skalkotos
154 87d1bf2e Nikos Skalkotos
        # Sleep for a while
155 87d1bf2e Nikos Skalkotos
        time.sleep(3)
156 ebba4508 Nikos Skalkotos
157 ebba4508 Nikos Skalkotos
if __name__ == "__main__":
158 ebba4508 Nikos Skalkotos
    sys.exit(main())
159 ebba4508 Nikos Skalkotos
160 ebba4508 Nikos Skalkotos
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :