Statistics
| Branch: | Tag: | Revision:

root / snf-image-host / copy-monitor.py @ 87d1bf2e

History | View | Annotate | Download (5.4 kB)

1
#!/usr/bin/env python
2

    
3
# Copyright (C) 2011, 2012 GRNET S.A.
4
#
5
# This program is free software; you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 2 of the License, or
8
# (at your option) any later version.
9
#
10
# This program is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
# General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with this program; if not, write to the Free Software
17
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18
# 02110-1301, USA.
19

    
20
"""Utility to monitor the progress of image deployment
21

22
A small utility to monitor the progress of image deployment
23
by watching the contents of /proc/<pid>/io and producing
24
notifications.
25
"""
26

    
27
import os
28
import sys
29
import time
30
import json
31
import prctl
32
import signal
33
import socket
34

    
35

    
36
def parse_arguments(args):
37
    from optparse import OptionParser
38

    
39
    kw = {}
40
    kw['usage'] = "%prog [options] command [args...]"
41
    kw['description'] = \
42
        "%prog runs 'command' with the specified arguments, monitoring the " \
43
        "number of bytes read by it. 'command' is assumed to be " \
44
        "A program used to install the OS for a Ganeti instance. %prog " \
45
        "periodically issues notifications of type 'copy-progress'."
46

    
47
    parser = OptionParser(**kw)
48
    parser.disable_interspersed_args()
49
    parser.add_option("-r", "--read-bytes",
50
                      action="store", type="int", dest="read_bytes",
51
                      metavar="BYTES_TO_READ",
52
                      help="The expected number of bytes to be read, " \
53
                           "used to compute input progress",
54
                      default=None)
55
    parser.add_option("-o", "--output_fd", dest="output", default=None,
56
                    metavar="FILE", type="int",
57
                    help="Write output notifications to this file descriptor")
58

    
59
    (opts, args) = parser.parse_args(args)
60

    
61
    if opts.read_bytes is None:
62
        sys.stderr.write("Fatal: Option '-r' is mandatory.\n")
63
        parser.print_help()
64
        sys.exit(1)
65

    
66
    if opts.output is None:
67
        sys.stderr.write("Fatal: Option '-o' is mandatory.\n")
68
        parser.print_help()
69
        sys.exit(1)
70

    
71
    if len(args) == 0:
72
        sys.stderr.write("Fatal: You need to specify the command to run.\n")
73
        parser.print_help()
74
        sys.exit(1)
75

    
76
    return (opts, args)
77

    
78

    
79
def report_wait_status(pid, status):
80
    if os.WIFEXITED(status):
81
        sys.stderr.write("Child PID = %d exited, status = %d\n" %
82
                         (pid, os.WEXITSTATUS(status)))
83
    elif os.WIFSIGNALED(status):
84
        sys.stderr.write("Child PID = %d died by signal, signal = %d\n" %
85
                         (pid, os.WTERMSIG(status)))
86
    elif os.WIFSTOPPED(status):
87
        sys.stderr.write("Child PID = %d stopped by signal, signal = %d\n" %
88
                         (pid, os.WSTOPSIG(status)))
89
    else:
90
        sys.stderr.write("Internal error: Unhandled case, " \
91
                         "PID = %d, status = %d\n" % (pid, status))
92
        sys.exit(1)
93
    sys.stderr.flush()
94

    
95

    
96
def send_message(to, message):
97
    message['timestamp'] = time.time()
98
    os.write(to, "%s\n" % json.dumps(message))
99

    
100

    
101
def main():
102
    (opts, args) = parse_arguments(sys.argv[1:])
103
    out = opts.output
104
    pid = os.fork()
105
    if pid == 0:
106
        # In child process:
107

    
108
        # Make sure we die with the parent and are not left behind
109
        # WARNING: This uses the prctl(2) call and is Linux-specific.
110
        prctl.set_pdeathsig(signal.SIGHUP)
111

    
112
        # exec command specified in arguments,
113
        # searching the $PATH, keeping all environment
114
        os.execvpe(args[0], args, os.environ)
115
        sys.stderr.write("execvpe failed, exiting with non-zero status")
116
        os.exit(1)
117

    
118
    # In parent process:
119
    iofname = "/proc/%d/io" % pid
120
    iof = open(iofname, "r", 0)   # 0: unbuffered open
121
    sys.stderr.write("%s: created child PID = %d, monitoring file %s\n" %
122
                     (sys.argv[0], pid, iofname))
123

    
124
    message = {}
125
    message['type'] = 'copy-progress'
126
    message['total'] = opts.read_bytes
127

    
128
    while True:
129
        # check if the child process is still alive
130
        (wpid, status) = os.waitpid(pid, os.WNOHANG)
131
        if wpid == pid:
132
            report_wait_status(pid, status)
133
            if (os.WIFEXITED(status) or os.WIFSIGNALED(status)):
134
                if not (os.WIFEXITED(status) and
135
                                            os.WEXITSTATUS(status) == 0):
136
                    return 1
137
                else:
138
                    message['position'] = message['total']
139
                    message['progress'] = float(100)
140
                    send_message(out, message)
141
                    return 0
142

    
143
        iof.seek(0)
144
        for l in iof.readlines():
145
            if l.startswith("rchar:"):
146
                message['position'] = int(l.split(': ')[1])
147
                message['progress'] = float(100) if opts.read_bytes == 0 \
148
                    else float("%2.2f" % (
149
                        message['position'] * 100.0 / message['total']))
150
                send_message(out, message)
151
                break
152

    
153
        # Sleep for a while
154
        time.sleep(3)
155

    
156
if __name__ == "__main__":
157
    sys.exit(main())
158

    
159
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :