* Xrn object is initialized with a single non-optional xrn string. hrn/urn is detecte...
[sfa.git] / sfa / util / rspecHelper.py
1 #! /usr/bin/env python
2
3 import sys
4 from copy import deepcopy
5 from lxml import etree
6 from StringIO import StringIO
7 from optparse import OptionParser
8
9
10 def merge_rspecs(rspecs):
11     """
12     Merge merge a list of RSpecs into 1 RSpec, and return the result.
13     rspecs must be a valid RSpec string or list of RSpec strings.
14     """
15     if not rspecs or not isinstance(rspecs, list):
16         return rspecs
17
18     rspec = None
19     for tmp_rspec in rspecs:
20         try:
21             tree = etree.parse(StringIO(tmp_rspec))
22         except etree.XMLSyntaxError:
23             # consider failing silently here
24             message = str(agg_rspec) + ": " + str(sys.exc_info()[1])
25             raise InvalidRSpec(message)
26
27         root = tree.getroot()
28         if root.get("type") in ["SFA"]:
29             if rspec == None:
30                 rspec = root
31             else:
32                 for network in root.iterfind("./network"):
33                     rspec.append(deepcopy(network))
34                 for request in root.iterfind("./request"):
35                     rspec.append(deepcopy(request))
36     return etree.tostring(rspec, xml_declaration=True, pretty_print=True)
37
38 class RSpec:
39     def __init__(self, xml):
40         parser = etree.XMLParser(remove_blank_text=True)
41         tree = etree.parse(StringIO(xml), parser)
42         self.rspec = tree.getroot()
43
44     def get_node_element(self, hostname):
45         names = self.rspec.iterfind("./network/site/node/hostname")
46         for name in names:
47             if name.text == hostname:
48                 return name.getparent()
49         return None
50         
51     def get_node_list(self):
52         result = self.rspec.xpath("./network/site/node/hostname/text()")
53         return result
54
55     def get_network_list(self):
56         return self.rspec.xpath("./network[@name]/@name")
57
58     def get_nodes_from_network(self, network):
59         return self.rspec.xpath("./network[@name='%s']/site/node/hostname/text()" % network)
60
61     def get_sliver_list(self):
62         result = self.rspec.xpath("./network/site/node[sliver]/hostname/text()")
63         return result
64
65     def add_sliver(self, hostname):
66         node = self.get_node_element(hostname)
67         etree.SubElement(node, "sliver")
68
69     def remove_sliver(self, hostname):
70         node = self.get_node_element(hostname)
71         node.remove(node.find("sliver"))
72
73     def attributes_list(self, elem):
74         opts = []
75         if elem is not None:
76             for e in elem:
77                 opts.append((e.tag, e.text))
78         return opts
79
80     def get_default_sliver_attributes(self):
81         defaults = self.rspec.find(".//sliver_defaults")
82         return self.attributes_list(defaults)
83
84     def get_sliver_attributes(self, hostname):
85         node = self.get_node_element(hostname)
86         sliver = node.find("sliver")
87         return self.attributes_list(sliver)
88
89     def add_attribute(self, elem, name, value):
90         opt = etree.SubElement(elem, name)
91         opt.text = value
92
93     def add_default_sliver_attribute(self, name, value):
94         defaults = self.rspec.find(".//sliver_defaults")
95         if defaults is None:
96             defaults = etree.Element("sliver_defaults")
97             network = self.rspec.find(".//network")
98             network.insert(0, defaults)
99         self.add_attribute(defaults, name, value)
100
101     def add_sliver_attribute(self, hostname, name, value):
102         node = self.get_node_element(hostname)
103         sliver = node.find("sliver")
104         self.add_attribute(sliver, name, value)
105
106     def remove_attribute(self, elem, name, value):
107         if elem is not None:
108             opts = elem.iterfind(name)
109             if opts is not None:
110                 for opt in opts:
111                     if opt.text == value:
112                         elem.remove(opt)
113
114     def remove_default_sliver_attribute(self, name, value):
115         defaults = self.rspec.find(".//sliver_defaults")
116         self.remove_attribute(defaults, name, value)
117
118     def remove_sliver_attribute(self, hostname, name, value):
119         node = self.get_node_element(hostname)
120         sliver = node.find("sliver")
121         self.remove_attribute(sliver, name, value)
122
123     def get_site_nodes(self, siteid):
124         query = './/site[@id="%s"]/node/hostname/text()' % siteid
125         result = self.rspec.xpath(query)
126         return result
127         
128     def get_link_list(self):
129         linklist = []
130         links = self.rspec.iterfind(".//link")
131         for link in links:
132             (end1, end2) = link.get("endpoints").split()
133             name = link.find("description")
134             linklist.append((name.text, 
135                              self.get_site_nodes(end1), 
136                              self.get_site_nodes(end2)))
137         return linklist
138
139     def get_vlink_list(self):
140         vlinklist = []
141         vlinks = self.rspec.iterfind(".//vlink")
142         for vlink in vlinks:
143             endpoints = vlink.get("endpoints")
144             (end1, end2) = endpoints.split()
145             query = './/node[@id="%s"]/hostname/text()'
146             node1 = self.rspec.xpath(query % end1)[0]
147             node2 = self.rspec.xpath(query % end2)[0]
148             desc = "%s <--> %s" % (node1, node2) 
149             kbps = vlink.find("kbps")
150             vlinklist.append((endpoints, desc, kbps.text))
151         return vlinklist
152
153     def query_links(self, fromnode, tonode):
154         fromsite = fromnode.getparent()
155         tosite = tonode.getparent()
156         fromid = fromsite.get("id")
157         toid = tosite.get("id")
158
159         query = ".//link[@endpoints = '%s %s']" % (fromid, toid)
160         results = self.rspec.xpath(query)
161         if results == None:
162             query = ".//link[@endpoints = '%s %s']" % (toid, fromid)
163             results = self.rspec.xpath(query)
164         return results
165
166     def query_vlinks(self, endpoints):
167         query = ".//vlink[@endpoints = '%s']" % endpoints
168         results = self.rspec.xpath(query)
169         return results
170             
171     
172     def add_vlink(self, fromhost, tohost, kbps):
173         fromnode = self.get_node_element(fromhost)
174         tonode = self.get_node_element(tohost)
175         links = self.query_links(fromnode, tonode)
176
177         for link in links:
178             vlink = etree.SubElement(link, "vlink")
179             fromid = fromnode.get("id")
180             toid = tonode.get("id")
181             vlink.set("endpoints", "%s %s" % (fromid, toid))
182             self.add_attribute(vlink, "kbps", kbps)
183         
184
185     def remove_vlink(self, endpoints):
186         vlinks = self.query_vlinks(endpoints)
187         for vlink in vlinks:
188             vlink.getparent().remove(vlink)
189
190     def toxml(self):
191         return etree.tostring(self.rspec, pretty_print=True, 
192                               xml_declaration=True)
193
194     def __str__(self):
195         return self.toxml()
196
197     def save(self, filename):
198         f = open(filename, "w")
199         f.write(self.toxml())
200         f.close()
201
202
203 class Commands:
204     def __init__(self, usage, description, epilog=None):
205         self.parser = OptionParser(usage=usage, description=description,
206                                    epilog=epilog)
207         self.parser.add_option("-i", "", dest="infile", metavar="FILE",
208                                help="read RSpec from FILE (default is stdin)")
209         self.parser.add_option("-o", "", dest="outfile", metavar="FILE",
210                                help="write output to FILE (default is stdout)")
211         self.nodefile = False
212         self.attributes = {}
213
214     def add_nodefile_option(self):
215         self.nodefile = True
216         self.parser.add_option("-n", "", dest="nodefile", 
217                                metavar="FILE",
218                                help="read node list from FILE"),
219
220     def add_show_attributes_option(self):
221         self.parser.add_option("-s", "--show-attributes", action="store_true", 
222                                dest="showatt", default=False, 
223                                help="show sliver attributes")
224
225     def add_attribute_options(self):
226         self.parser.add_option("", "--capabilities", action="append",
227                                metavar="<cap1,cap2,...>",
228                                help="Vserver bcapabilities")
229         self.parser.add_option("", "--codemux", action="append",
230                                metavar="<host,local-port>",
231                                help="Demux HTTP between slices using " +
232                                "localhost ports")
233         self.parser.add_option("", "--cpu-pct", action="append",
234                                metavar="<num>", 
235                                help="Reserved CPU percent (e.g., 25)")
236         self.parser.add_option("", "--cpu-share", action="append",
237                                metavar="<num>", 
238                                help="Number of CPU shares (e.g., 5)")
239         self.parser.add_option("", "--delegations", 
240                                metavar="<slice1,slice2,...>", action="append",
241                                help="List of slices with delegation authority")
242         self.parser.add_option("", "--disk-max", 
243                                metavar="<num>", action="append",
244                                help="Disk quota (1k disk blocks)")
245         self.parser.add_option("", "--initscript", 
246                                metavar="<name>", action="append",
247                                help="Slice initialization script (e.g., stork)")
248         self.parser.add_option("", "--ip-addresses", action="append",
249                                metavar="<IP addr>", 
250                                help="Add an IP address to a sliver")
251         self.parser.add_option("", "--net-i2-max-kbyte", 
252                                metavar="<KBytes>", action="append",
253                                help="Maximum daily network Tx limit " +
254                                "to I2 hosts.")
255         self.parser.add_option("", "--net-i2-max-rate", 
256                                metavar="<Kbps>", action="append",
257                                help="Maximum bandwidth over I2 routes")
258         self.parser.add_option("", "--net-i2-min-rate", 
259                                metavar="<Kbps>", action="append",
260                                help="Minimum bandwidth over I2 routes")
261         self.parser.add_option("", "--net-i2-share", 
262                                metavar="<num>", action="append",
263                                help="Number of bandwidth shares over I2 routes")
264         self.parser.add_option("", "--net-i2-thresh-kbyte", 
265                                metavar="<KBytes>", action="append",
266                                help="Limit sent to I2 hosts before warning, " +
267                                "throttling")
268         self.parser.add_option("", "--net-max-kbyte", 
269                                metavar="<KBytes>", action="append",
270                                help="Maximum daily network Tx limit " +
271                                "to non-I2 hosts.")
272         self.parser.add_option("", "--net-max-rate", 
273                                metavar="<Kbps>", action="append",
274                                help="Maximum bandwidth over non-I2 routes")
275         self.parser.add_option("", "--net-min-rate", 
276                                metavar="<Kbps>", action="append",
277                                help="Minimum bandwidth over non-I2 routes")
278         self.parser.add_option("", "--net-share", 
279                                metavar="<num>", action="append",
280                                help="Number of bandwidth shares over non-I2 " +
281                                "routes")
282         self.parser.add_option("", "--net-thresh-kbyte", 
283                                metavar="<KBytes>", action="append",
284                                help="Limit sent to non-I2 hosts before " +
285                                "warning, throttling")
286         self.parser.add_option("", "--vsys", 
287                                metavar="<name>", action="append",
288                                help="Vsys script (e.g., fd_fusemount)")
289         self.parser.add_option("", "--vsys-vnet", 
290                                metavar="<IP network>", action="append",
291                                help="Allocate a virtual private network")
292
293     def get_attribute_dict(self):
294         attrlist = ['capabilities','codemux','cpu_pct','cpu_share',
295                     'delegations','disk_max','initscript','ip_addresses',
296                     'net_i2_max_kbyte','net_i2_max_rate','net_i2_min_rate',
297                     'net_i2_share','net_i2_thresh_kbyte',
298                     'net_max_kbyte','net_max_rate','net_min_rate',
299                     'net_share','net_thresh_kbyte',
300                     'vsys','vsys_vnet']
301         attrdict = {}
302         for attr in attrlist:
303             value = getattr(self.opts, attr, None)
304             if value is not None:
305                 attrdict[attr] = value
306         return attrdict
307
308     def prep(self):
309         (self.opts, self.args) = self.parser.parse_args()
310
311         if self.opts.infile:
312             sys.stdin = open(self.opts.infile, "r")
313         xml = sys.stdin.read()
314         self.rspec = RSpec(xml)
315             
316         if self.nodefile:
317             if self.opts.nodefile:
318                 f = open(self.opts.nodefile, "r")
319                 self.nodes = f.read().split()
320                 f.close()
321             else:
322                 self.nodes = self.args
323
324         if self.opts.outfile:
325             sys.stdout = open(self.opts.outfile, "w")
326
327
328
329
330
331
332