Statistics
| Branch: | Tag: | Revision:

root / snf-image-host / copy-monitor.py @ 0b51f509

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