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