updated to work with current plcapi
[myplc.git] / support-scripts / gen-sites-xml.py
1 #!/usr/bin/python
2 #
3 # Write out sites.xml
4 #
5 # Mark Huang <mlhuang@cs.princeton.edu>
6 # Copyright (C) 2006 The Trustees of Princeton University
7 #
8 # $Id: gen-sites-xml.py,v 1.8 2007/09/14 20:08:28 tmack Exp $
9 #
10
11 import os, sys
12 import getopt
13 import time
14 from xml.sax.saxutils import escape, quoteattr, XMLGenerator
15
16 PID_FILE= "/var/run/all_planetlab_xml.pid"
17
18 #
19 # Web server document root
20 #
21 DOCROOT = '/var/www/html/xml'
22
23 #
24 # DTD and version number for site information
25 #
26 ENCODING= "utf-8"
27 SITE_VERSION="0.4"
28
29 # Debug
30 dryrun = False
31
32 # Parse options
33 def usage():
34     print "Usage: %s [OPTION]..." % sys.argv[0]
35     print "Options:"
36     print "     -n, --dryrun            Dry run, do not write files (default: %s)" % dryrun
37     print "     -d, --docroot=DIR       Document root (default: %s)" % DOCROOT
38     print "     -h, --help              This message"
39     sys.exit(1)
40
41 # Get options
42 try:
43     (opts, argv) = getopt.getopt(sys.argv[1:], "nd:h", ["dryrun", "docroot=", "help"])
44 except getopt.GetoptError, err:
45     print "Error: " + err.msg
46     usage()
47
48 for (opt, optval) in opts:
49     if opt == "-n" or opt == "--dryrun":
50         dryrun = True
51     elif opt == "-d" or opt == "--docroot":
52         DOCROOT = optval
53     else:
54         usage()
55
56 # Write out lock file
57 if not dryrun:
58     if os.access(PID_FILE, os.R_OK):
59         pid= file(PID_FILE).readline().strip()
60         if pid <> "":
61             if os.system("/bin/kill -0 %s > /dev/null 2>&1" % pid) == 0:
62                 sys.exit(0)
63
64     # write out our process id
65     pidfile= file( PID_FILE, 'w' )
66     pidfile.write( "%d\n" % os.getpid() )
67     pidfile.close()
68
69 # Load shell with default configuration
70 sys.path.append('/usr/share/plc_api')
71 from PLC.Shell import Shell
72 plc = Shell(globals())
73
74 #
75 # Get information from API
76 #
77
78 begin()
79 GetNodes(None, ['node_id', 'model', 'boot_state', 'hostname', 'version', 'ssh_rsa_key', 'interface_ids', 'slice_ids_whitelist'])
80 GetInterfaces({'is_primary': True}, ['interface_id', 'node_id', 'ip', 'mac', 'bwlimit'])
81 GetSites(None, ['name', 'latitude', 'longitude', 'url', 'site_id', 'login_base', 'abbreviated_name', 'node_ids'])
82 GetNodeGroups(['Alpha', 'Beta', 'Rollout', 'Production'], ['groupname', 'node_ids'])
83 (nodes, nodenetworks, sites, groups) = commit()
84
85 # remove whitelisted nodes
86 remove_whitelisted = lambda node: not node['slice_ids_whitelist']
87 nodes = filter(remove_whitelisted, nodes)
88
89 nodes = dict([(node['node_id'], node) for node in nodes])
90
91 for nodenetwork in nodenetworks:
92     if nodes.has_key(nodenetwork['node_id']):
93         node = nodes[nodenetwork['node_id']]
94         for key, value in nodenetwork.iteritems():
95             node[key] = value
96
97 group_node_ids = dict([(group['groupname'], group['node_ids']) for group in groups])
98
99 class PrettyXMLGenerator(XMLGenerator):
100     """
101     Adds indentation to the beginning and newlines to the end of
102     opening and closing tags.
103     """
104
105     def __init__(self, out = sys.stdout, encoding = "utf-8", indent = "", addindent = "", newl = ""):
106         XMLGenerator.__init__(self, out, encoding)
107         # XMLGenerator does not export _write()
108         self.write = self.ignorableWhitespace
109         self.indents = [indent]
110         self.addindent = addindent
111         self.newl = newl
112
113     def startDocument(self):
114         XMLGenerator.startDocument(self)
115
116     def startElement(self, name, attrs, indent = True, newl = True):
117         if indent:
118             self.ignorableWhitespace("".join(self.indents))
119         self.indents.append(self.addindent)
120
121         XMLGenerator.startElement(self, name, attrs)
122
123         if newl:
124             self.ignorableWhitespace(self.newl)
125
126     def characters(self, content):
127         # " to &quot;
128         # ' to &apos;
129         self.write(escape(content, {
130             '"': '&quot;',
131             "'": '&apos;',
132             }))
133
134     def endElement(self, name, indent = True, newl = True):
135         self.indents.pop()
136         if indent:
137             self.ignorableWhitespace("".join(self.indents))
138
139         XMLGenerator.endElement(self, name)
140
141         if newl:
142             self.ignorableWhitespace(self.newl)
143
144     def simpleElement(self, name, attrs = {}, indent = True, newl = True):
145         if indent:
146             self.ignorableWhitespace("".join(self.indents))
147
148         self.write('<' + name)
149         for (name, value) in attrs.items():
150             self.write(' %s=%s' % (name, quoteattr(value.strip())))
151         self.write('/>')
152
153         if newl:
154             self.ignorableWhitespace(self.newl)
155
156 #
157 # Write out sites.xml
158 #
159
160 if dryrun:
161     sites_xml = sys.stdout
162 else:
163     sites_xml = open(DOCROOT + "/sites.xml", mode = "w")
164
165 xml = PrettyXMLGenerator(out = sites_xml, encoding = ENCODING, indent = "", addindent = "  ", newl = "\n")
166 xml.startDocument()
167
168 # Write embedded DTD verbatim
169 xml.ignorableWhitespace("""
170 <!DOCTYPE PLANETLAB_SITES [
171   <!ELEMENT PLANETLAB_SITES (SITE)*>
172   <!ATTLIST PLANETLAB_SITES VERSION CDATA #REQUIRED
173                             TIME    CDATA #REQUIRED>
174
175   <!ELEMENT SITE (HOST)*>
176   <!ATTLIST SITE NAME            CDATA #REQUIRED
177                  LATITUDE        CDATA #REQUIRED
178                  LONGITUDE       CDATA #REQUIRED
179                  URL             CDATA #REQUIRED
180                  SITE_ID         CDATA #REQUIRED
181                  LOGIN_BASE      CDATA #REQUIRED
182                  FULL_SITE_NAME  CDATA #REQUIRED
183                  SHORT_SITE_NAME CDATA #REQUIRED
184   >
185
186   <!ELEMENT HOST EMPTY>
187   <!ATTLIST HOST NAME         CDATA #REQUIRED
188                  IP           CDATA #REQUIRED
189                  MODEL        CDATA #REQUIRED
190                  MAC          CDATA #IMPLIED
191                  BOOTCD       (y|n) "n"
192                  VERSION      CDATA #REQUIRED
193                  NODE_ID      CDATA #REQUIRED
194                  BOOT_VERSION CDATA ""
195                  STATUS       CDATA ""
196                  BOOT_STATE   CDATA #REQUIRED
197                  RSA_KEY      CDATA ""
198                  BWLIMIT      CDATA ""
199   >
200 ]>
201 """)
202
203 def format_tc_rate(rate):
204     """
205     Formats a bits/second rate into a tc rate string
206     """
207
208     if rate >= 1000000000 and (rate % 1000000000) == 0:
209         return "%.0fgbit" % (rate / 1000000000.)
210     elif rate >= 1000000 and (rate % 1000000) == 0:
211         return "%.0fmbit" % (rate / 1000000.)
212     elif rate >= 1000:
213         return "%.0fkbit" % (rate / 1000.)
214     else:
215         return "%.0fbit" % rate
216
217 # <PLANETLAB_SITES VERSION="major.minor" TIME="seconds_since_epoch">
218 xml.startElement('PLANETLAB_SITES', {'VERSION': SITE_VERSION,
219                                      'TIME': str(int(time.time()))})
220
221 for site in sites:
222     # <SITE ...>
223     attrs = {}
224     for attr in ['name', 'latitude', 'longitude', 'url', 'site_id', 'login_base']:
225         attrs[attr.upper()] = unicode(site[attr])
226     attrs['FULL_SITE_NAME'] = unicode(site['name'])
227     attrs['SHORT_SITE_NAME'] = unicode(site['abbreviated_name'])
228     xml.startElement('SITE', attrs)
229
230     for node_id in site['node_ids']:
231         if nodes.has_key(node_id):
232             node = nodes[node_id]
233
234             # <HOST ...>
235             attrs = {}
236             attrs['NAME'] = unicode(node['hostname'])
237             attrs['VERSION'] = "2.0"
238             for attr in ['model', 'node_id', 'boot_state']:
239                 attrs[attr.upper()] = unicode(node[attr]).strip()
240
241             # If the node is in Alpha, Beta, or Rollout, otherwise Production
242             for group in ['Alpha', 'Beta', 'Rollout', 'Production']:
243                 if group_node_ids.has_key(group) and \
244                    node_id in group_node_ids[group]:
245                     break
246             attrs['STATUS'] = group
247
248             if node['version']:
249                 attrs['BOOT_VERSION'] = unicode(node['version'].splitlines()[0])
250             if node['ssh_rsa_key']:
251                 attrs['RSA_KEY'] = unicode(node['ssh_rsa_key'].splitlines()[0])
252
253             if node.has_key('ip') and node['ip']:
254                 attrs['IP'] = unicode(node['ip'])
255             if node.has_key('mac') and node['mac']:
256                 attrs['MAC'] = unicode(node['mac'])
257             if node.has_key('bwlimit') and node['bwlimit']:
258                 attrs['BWLIMIT'] = unicode(format_tc_rate(node['bwlimit']))
259
260             xml.simpleElement('HOST', attrs)
261
262     # </SITE>
263     xml.endElement('SITE')
264
265 xml.endElement('PLANETLAB_SITES')
266
267 if not dryrun:
268     # remove the PID file
269     os.unlink( PID_FILE )