- only print a single warning per run if NM query times out
[mom.git] / pl_mom.py
1 #!/usr/bin/python
2 #
3 # Utility functions
4 #
5 # Mark Huang <mlhuang@cs.princeton.edu>
6 # Copyright (C) 2006 The Trustees of Princeton University
7 #
8 # $Id: pl_mom.py,v 1.2 2006/05/08 17:37:28 mlhuang Exp $
9 #
10
11 import os
12 import sys
13 import syslog
14 import socket
15 import xmlrpclib
16
17 # /etc/planetlab/plc_config.py is a Python fragment maintained by
18 # PlanetLabConf that contains PLC configuration variables.
19 try:
20     sys.path.append("/etc/planetlab")
21     from plc_config import *
22 except:
23     print "Warning: Configuration file /etc/planetlab/plc_config.py not found"
24     PLC_NAME = "PlanetLab"
25     PLC_SLICE_PREFIX = "pl"
26     PLC_MAIL_SUPPORT_ADDRESS = "support@planet-lab.org"
27     PLC_MAIL_SLICE_ADDRESS = "SLICE@slices.planet-lab.org"
28
29 def format_bytes(bytes, si = True):
30     """
31     Formats bytes into a string
32     """
33     if si:
34         kilo = 1000.
35     else:
36         # Officially, a kibibyte
37         kilo = 1024.
38
39     if bytes >= (kilo * kilo * kilo):
40         return "%.1f GB" % (bytes / (kilo * kilo * kilo))
41     elif bytes >= 1000000:
42         return "%.1f MB" % (bytes / (kilo * kilo))
43     elif bytes >= 1000:
44         return "%.1f KB" % (bytes / kilo)
45     else:
46         return "%.0f bytes" % bytes
47
48 def format_period(seconds):
49     """
50     Formats a period in seconds into a string
51     """
52
53     if seconds == (24 * 60 * 60):
54         return "day"
55     elif seconds == (60 * 60):
56         return "hour"
57     elif seconds > (24 * 60 * 60):
58         return "%.1f days" % (seconds / 24. / 60. / 60.)
59     elif seconds > (60 * 60):
60         return "%.1f hours" % (seconds / 60. / 60.)
61     elif seconds > (60):
62         return "%.1f minutes" % (seconds / 60.)
63     else:
64         return "%.0f seconds" % seconds
65
66 def writepid(prog):
67     """
68     Check PID file. Exit if already running. Update PID file.
69     """
70
71     try:
72         pidfile = file("/var/run/%s.pid" % prog, "r")
73         pid = pidfile.readline().strip()
74         pidfile.close()
75         if os.path.isdir("/proc/" + pid):
76             print "Error: Another copy of %s is still running (%s)" % (prog, pid)
77             sys.exit(1)
78     except IOError:
79         pass
80
81     pidfile = file("/var/run/%s.pid" % prog, "w")
82     pidfile.write(str(os.getpid()))
83     pidfile.close()
84
85 def removepid(prog):
86     os.unlink("/var/run/%s.pid" % prog)
87
88 def slicemail(slice, subject, body):
89     sendmail = os.popen("/usr/sbin/sendmail -t -f%s" % PLC_MAIL_SUPPORT_ADDRESS, "w")
90
91     # PLC has a separate list for pl_mom messages
92     if PLC_MAIL_SUPPORT_ADDRESS == "support@planet-lab.org":
93         to = ["pl-mom@planet-lab.org"]
94     else:
95         to = [PLC_MAIL_SUPPORT_ADDRESS]
96
97     if slice is not None and slice != "root":
98         to.append(PLC_MAIL_SLICE_ADDRESS.replace("SLICE", slice))
99
100     header = {'from': "%s Support <%s>" % (PLC_NAME, PLC_MAIL_SUPPORT_ADDRESS),
101               'to': ", ".join(to),
102               'version': sys.version.split(" ")[0],
103               'subject': subject}
104
105     # Write headers
106     sendmail.write(
107 """
108 Content-type: text/plain
109 From: %(from)s
110 Reply-To: %(from)s
111 To: %(to)s
112 X-Mailer: Python/%(version)s
113 Subject: %(subject)s
114
115 """.lstrip() % header)
116
117     # Write body
118     sendmail.write(body)
119
120     # Done
121     sendmail.close()
122
123 class Logger:
124     """
125     Simple file-like class for redirecting stdout and stderr to /var/log/messages
126     """
127
128     def write(self, text):
129         text = text.strip()
130         if text:
131             syslog.syslog(text)
132
133 def daemonize():
134     """
135     Daemonize self. Detach from terminal, close all open files, and fork twice.
136     """
137
138     pid = os.fork()
139     if pid == 0:
140         # Detach from terminal
141         os.setsid()
142         # Orphan myself
143         pid = os.fork()
144         if pid == 0:
145             os.chdir("/")
146         else:
147             os._exit(0)
148     else:
149         os._exit(0)
150
151     # Close all open file descriptors
152     import resource
153     maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
154     if (maxfd == resource.RLIM_INFINITY):
155         maxfd = 1024
156     for fd in range(0, maxfd):
157         try:
158             os.close(fd)
159         except OSError:
160             pass
161
162     # Redirect stdin to /dev/null
163     os.open("/dev/null", os.O_RDWR)
164
165 class NM:
166     """
167     Simple interface to local Node Manager API
168     """
169
170     def __init__(self, url = "http://localhost:812/", timeout = 10):
171         """
172         Open a new connection to the local Node Manager
173         """
174
175         socket.setdefaulttimeout(timeout)
176         try:
177             self.nm = xmlrpclib.ServerProxy(url)
178         except Exception, err:
179             print "Warning: Exception received while opening connection to Node Manager:", err
180             self.nm = None
181
182     def query(self, slice, attributes):
183         """
184         Get values of various slice attributes from the local Node Manager
185
186         slice - slice name
187         attributes - [attribute_name, (attribute_name, return_value_if_not_set), ...]
188         """
189
190         values = [None for attribute in attributes]
191
192         if self.nm is not None:
193             try:
194                 # Read rspec (the NM hash code for the slice)
195                 rcap = open("/var/run/pl_nm/%s.vm_rcap" % slice, "r")
196                 rspec = rcap.readline().strip()
197                 rcap.close()
198
199                 for i, attribute in enumerate(attributes):
200                     # NM interface allows you to pass in a tuple
201                     # (attribute, default) instead of just an
202                     # attribute name. default is returned if the
203                     # attribute is not set.
204                     if type(attribute) != tuple:
205                         attribute = (attribute, 0)
206                     (rc, (value,)) = self.nm.nm_inspect(rspec, [attribute])
207                     if rc == 0 and value != attribute[1]:
208                         values[i] = value
209             except Exception, err:
210                 print "Warning: Exception received while querying Node Manager:", err
211                 self.nm = None
212
213         return values