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