renamged get_element_type() to get_rspec_element(). Added load_rspec_elements() method
[sfa.git] / sfa / rspecs / rspec.py
1 #!/usr/bin/python 
2 from lxml import etree
3 from StringIO import StringIO
4 from datetime import datetime, timedelta
5 from sfa.util.xrn import *
6 from sfa.util.plxrn import hostname_to_urn
7 from sfa.rspecs.rspec_elements import RSpecElement, RSpecElements 
8 from sfa.util.faults import SfaNotImplemented, InvalidRSpec, InvalidRSpecElement
9
10 class XpathFilter:
11     @staticmethod
12     def xpath(filter={}):
13         xpath = ""
14         if filter:
15             filter_list = []
16             for (key, value) in filter.items():
17                 if key == 'text':
18                     key = 'text()'
19                 else:
20                     key = '@'+key
21                 if isinstance(value, str):
22                     filter_list.append('%s="%s"' % (key, value))
23                 elif isinstance(value, list):
24                     filter_list.append('contains("%s", %s)' % (' '.join(map(str, value)), key))
25             if filter_list:
26                 xpath = ' and '.join(filter_list)
27                 xpath = '[' + xpath + ']'
28         return xpath
29
30 class RSpec:
31     header = '<?xml version="1.0"?>\n'
32     template = """<RSpec></RSpec>"""
33     xml = None
34     type = None
35     version = None
36     namespaces = None
37     user_options = {}
38  
39     def __init__(self, rspec="", namespaces={}, type=None, user_options={}):
40         self.type = type
41         self.user_options = user_options
42         self.elements = {}
43         if rspec:
44             self.parse_rspec(rspec, namespaces)
45         else:
46             self.create()
47
48     def create(self):
49         """
50         Create root element
51         """
52         # eg. 2011-03-23T19:53:28Z 
53         date_format = '%Y-%m-%dT%H:%M:%SZ'
54         now = datetime.utcnow()
55         generated_ts = now.strftime(date_format)
56         expires_ts = (now + timedelta(hours=1)).strftime(date_format) 
57         self.parse_rspec(self.template, self.namespaces)
58         self.xml.set('expires', expires_ts)
59         self.xml.set('generated', generated_ts)
60     
61     def parse_rspec(self, rspec, namespaces={}):
62         """
63         parse rspec into etree
64         """
65         parser = etree.XMLParser(remove_blank_text=True)
66         try:
67             tree = etree.parse(rspec, parser)
68         except IOError:
69             # 'rspec' file doesnt exist. 'rspec' is proably an xml string
70             try:
71                 tree = etree.parse(StringIO(rspec), parser)
72             except Exception, e:
73                 raise InvalidRSpec(str(e))
74         self.xml = tree.getroot()  
75         if namespaces:
76            self.namespaces = namespaces
77
78     def validate(self, schema):
79         """
80         Validate against rng schema
81         """
82
83         relaxng_doc = etree.parse(schema)
84         relaxng = etree.RelaxNG(relaxng_doc)
85         if not relaxng(self.xml):
86             error = relaxng.error_log.last_error
87             message = "%s (line %s)" % (error.message, error.line)
88             raise InvalidRSpec(message)
89         return True
90
91     def xpath(self, xpath):
92         return self.xml.xpath(xpath, namespaces=self.namespaces)
93
94     def load_rspec_elements(self, rspec_elements):
95         self.elements = {}
96         for rspec_element in rspec_elements:
97             if isinstance(rspec_element, RSpecElement):
98                 self.elements[rspec_element.type] = rspec_element
99
100     def register_rspec_element(self, element_type, element_name, element_path):
101         if element_type not in RSpecElements:
102             raise InvalidRSpecElement(element_type, extra="no such element type: %s. Must specify a valid RSpecElement" % element_type)
103         self.elements[element_type] = RSpecElement(element_type, element_name, element_path)
104
105     def get_rspec_element(self, element_type):
106         if element_type not in self.elements:
107             msg = "ElementType %s not registerd for this rspec" % element_type
108             raise InvalidRSpecElement(element_type, extra=msg)
109         return self.elements[element_type]
110
111     def add_attribute(self, elem, name, value):
112         """
113         Add attribute to specified etree element    
114         """
115         opt = etree.SubElement(elem, name)
116         opt.text = value
117
118     def add_element(self, name, attrs={}, parent=None, text=""):
119         """
120         Generic wrapper around etree.SubElement(). Adds an element to 
121         specified parent node. Adds element to root node is parent is 
122         not specified. 
123         """
124         if parent == None:
125             parent = self.xml
126         element = etree.SubElement(parent, name)
127         if text:
128             element.text = text
129         if isinstance(attrs, dict):
130             for attr in attrs:
131                 element.set(attr, attrs[attr])  
132         return element
133
134     def remove_attribute(self, elem, name, value):
135         """
136         Removes an attribute from an element
137         """
138         if elem is not None:
139             opts = elem.iterfind(name)
140             if opts is not None:
141                 for opt in opts:
142                     if opt.text == value:
143                         elem.remove(opt)
144
145     def remove_element(self, element_name, root_node = None):
146         """
147         Removes all occurences of an element from the tree. Start at 
148         specified root_node if specified, otherwise start at tree's root.   
149         """
150         if not root_node:
151             root_node = self.xml
152
153         if not element_name.startswith('//'):
154             element_name = '//' + element_name
155
156         elements = root_node.xpath('%s ' % element_name, namespaces=self.namespaces)
157         for element in elements:
158             parent = element.getparent()
159             parent.remove(element)
160
161     def attributes_list(self, elem):
162         # convert a list of attribute tags into list of tuples
163         # (tagnme, text_value)
164         opts = []
165         if elem is not None:
166             for e in elem:
167                 opts.append((e.tag, str(e.text).strip()))
168         return opts
169
170     def get_element_attributes(self, elem=None, depth=0):
171         if elem == None:
172             elem = self.root_node
173         if not hasattr(elem, 'attrib'):
174             # this is probably not an element node with attribute. could be just and an
175             # attribute, return it
176             return elem
177         attrs = dict(elem.attrib)
178         attrs['text'] = str(elem.text).strip()
179         attrs['parent'] = elem.getparent()
180         if isinstance(depth, int) and depth > 0:
181             for child_elem in list(elem):
182                 key = str(child_elem.tag)
183                 if key not in attrs:
184                     attrs[key] = [self.get_element_attributes(child_elem, depth-1)]
185                 else:
186                     attrs[key].append(self.get_element_attributes(child_elem, depth-1))
187         else:
188             attrs['child_nodes'] = list(elem)
189         return attrs
190
191     def get(self, element_type, filter={}, depth=0):
192         elements = self.get_elements(element_type, filter)
193         elements = [self.get_element_attributes(element, depth=depth) for element in elements]
194         return elements
195
196     def get_elements(self, element_type, filter={}):
197         """
198         search for a registered element
199         """
200         if element_type not in self.elements:
201             msg = "Unable to search for element %s in rspec, expath expression not found." % \
202                    element_type
203             raise InvalidRSpecElement(element_type, extra=msg)
204         rspec_element = self.get_rspec_element(element_type)
205         xpath = rspec_element.path + XpathFilter.xpath(filter)
206         return self.xpath(xpath)
207
208     def merge(self, in_rspec):
209         pass
210
211     def cleanup(self):
212         """
213         Optional method which inheriting classes can choose to implent. 
214         """
215         pass 
216
217     def _process_slivers(self, slivers):
218         """
219         Creates a dict of sliver details for each sliver host
220         
221         @param slivers a single hostname, list of hostanmes or list of dicts keys on hostname,
222         Returns a list of dicts 
223         """
224         if not isinstance(slivers, list):
225             slivers = [slivers]
226         dicts = []
227         for sliver in slivers:
228             if isinstance(sliver, dict):
229                 dicts.append(sliver)
230             elif isinstance(sliver, basestring):
231                 dicts.append({'hostname': sliver}) 
232         return dicts
233
234     def __str__(self):
235         return self.toxml()
236
237     def toxml(self, cleanup=False):
238         if cleanup:
239             self.cleanup()
240         return self.header + etree.tostring(self.xml, pretty_print=True)  
241         
242     def save(self, filename):
243         f = open(filename, 'w')
244         f.write(self.toxml())
245         f.close()
246  
247 if __name__ == '__main__':
248     rspec = RSpec('/tmp/resources.rspec')
249     print rspec
250     #rspec.register_rspec_element(RSpecElements.NETWORK, 'network', '//network')
251     #rspec.register_rspec_element(RSpecElements.NODE, 'node', '//node')
252     #print rspec.find(RSpecElements.NODE)[0]
253     #print rspec.find(RSpecElements.NODE, depth=1)[0]
254