Statistics
| Branch: | Tag: | Revision:

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

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
MSG_TYPE="image-copy-progress"
36

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

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

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

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

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

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

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

    
77
    return (opts, args)
78

    
79

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

    
96

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

    
101

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

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

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

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

    
125
    message = {}
126
    message['type'] = MSG_TYPE
127
    message['total'] = opts.read_bytes
128

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

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

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

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

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