fix for BUG-947 - ErrorHandler classLoader issue
[openflowplugin.git] / test-scripts / xmlvalidator.py
1 import logging
2 import os
3 import re
4 from xml.etree import ElementTree as ET
5
6 class Loader():
7
8     @staticmethod
9     def loadXml(file_name):
10         path_to_xml = os.path.join('', file_name)
11         with open(path_to_xml) as f:
12                 xml_string = f.read()
13                 xml_string = re.sub(' xmlns="[^"]+"', '', xml_string, count=1)
14
15         tree = ET.fromstring(xml_string)
16         return tree, xml_string
17
18     @staticmethod
19     def buildXmlDocDictionaryForComarableElements(element, flow_dict, p_elm_name=None, kwd=None, akwd=None, mkwd=None):
20         act_key_dict = kwd if (kwd > None) else akwd if (akwd > None) else mkwd if (mkwd > None) else None
21         if element > None :
22             elm_alias = element.tag if (act_key_dict.get(element.tag, None) > None) else None
23             if ((element.getchildren() > None) & (len(element.getchildren()) > 0)):
24                 for child in element.getchildren() :
25                     if (element.tag == 'match') :
26                         Loader.buildXmlDocDictionaryForComarableElements(child, flow_dict, mkwd=mkwd)
27                     elif (element.tag == 'actions') :
28                         Loader.buildXmlDocDictionaryForComarableElements(child, flow_dict, akwd=akwd)
29                     else :
30                         Loader.buildXmlDocDictionaryForComarableElements(child, flow_dict, elm_alias, kwd, akwd, mkwd);
31             else :
32                 if element.text > None :
33                     text = re.sub( '[\s]+','', element.text, count=1)
34                     a_key = p_elm_name if (p_elm_name > None) else element.tag
35                     flow_dict[a_key] = text;
36         return
37
38 type_int = 1
39 type_boolean = 2
40 type_ethernet = 3
41 type_ipv4 = 4
42 type_ipv6 = 5
43
44 class Field():
45     """
46         fields to check, arguments:
47         key: element tag from keywords and xml
48         bits: expected length in bits
49         prerequisites: dictionary of elements tag from xml which are required for this field and their values in list
50                        or [None] if value is undefined or it's irrelevant (we just need to check if tag is set)
51         convert_from: format in which is value, that is checked against prerequisite values stored in xml
52
53         e.g.:
54         key:'ipv4-source'
55         bits:32
56         prerequisites: {'ethernet-type': [2048]}
57         convert_from: 10
58
59         OF_IPV4_SRC = Field('ipv4-source', 32, {'ethernet-type': [2048]}, 10)
60         IN_PHY_PORT = Field('in-phy-port', 32, {'in-port': [None]}, 10)
61     """
62
63     def __init__(self, key, bits, prerequisites=None, convert_from=10, value_type=type_int):
64         self.key = str(key)
65         self.bits = bits
66         if prerequisites is not None:
67             self.prerequisites = dict(prerequisites)
68         else:
69             self.prerequisites = None
70         self.convert_from = convert_from
71         self.value_type = value_type
72
73     def __str__(self):
74         return "Field: {}, size: {}, prerequisites: {}"\
75             .format(self.key, self.bits, self.prerequisites)
76
77
78 class XMLValidator():
79
80     log = logging.getLogger('XMLValidator')
81     log.propagate=False
82     channel = logging.StreamHandler()
83     log.addHandler(channel)
84
85     def __init__(self, kwd, akwd, mkwd, loglevel=logging.INFO):
86
87         self.test_name = 'No test loaded'
88         XMLValidator.log.setLevel(loglevel)
89
90         self.xml_ok = True
91         self.fields = list()
92         self.flow_dict = dict()
93
94         self.kwd = kwd
95         self.akwd = akwd
96         self.mkwd = mkwd
97
98     def create_dictionaries(self, file_name):
99         self.test_name = file_name
100
101         formatter = logging.Formatter('TEST {}: %(levelname)s: %(message)s'.format(self.test_name))
102         XMLValidator.channel.setFormatter(formatter)
103
104         self.flow_dict = dict()
105         treeXml1, self.xml_string = Loader.loadXml(file_name)
106         Loader.buildXmlDocDictionaryForComarableElements(treeXml1, self.flow_dict, kwd=self.kwd, akwd=self.akwd, mkwd=self.mkwd)
107         XMLValidator.log.debug('loaded dict from xml: {}'.format(self.flow_dict))
108
109
110     def fill_fields(self):
111         Matchers.fill_validator(self)
112
113     def add_field(self, fields):
114         self.fields.append(fields)
115
116     def integer_check(self, value, bits, convert_from=10):
117         XMLValidator.log.debug('validating integer: {}'.format(value))
118         if (int(value, convert_from) / 2**bits) > 0:
119             XMLValidator.log.error('value: {} is larger than expected: {}'.format(value, 2**bits))
120             raise StandardError
121
122     def boolean_check(self, value, bits):
123         XMLValidator.log.debug('validating boolean: {}'.format(value))
124         if bits < 1:
125             XMLValidator.log.error('value: {} is larger than expected: {}'.format(value, 2**bits))
126             raise StandardError
127
128     def ethernet_check(self, a):
129         XMLValidator.log.debug('validating ethernet address: {}'.format(a))
130         numbers = a.split(':')
131         max_range = (2**8) - 1
132
133         for n in numbers:
134             if int(n, 16) > max_range:
135                 XMLValidator.log.error('octet: {} in ethernet address: {} larger than: {}'.format(n, a, max_range))
136                 raise StandardError
137
138     def mask_check(self, address, mask, base=16, part_len=16, delimiter=':'):
139         if (int(mask) % part_len) != 0:
140             raise StandardError('{} is not valid mask, should be multiples of {}'.format(mask, part_len))
141
142         part_count = int(mask) / part_len
143
144         for part in address.split(delimiter):
145             part_value = int(part, base) if part != '' else 0
146             part_count -= 1
147
148             if part_count < 0 and part_value != 0:
149                 raise StandardError('address part {} should be 0'.format(part))
150
151     def ipv4_check(self, a):
152         XMLValidator.log.debug('validating ipv4 address: {}'.format(a))
153
154         mask_pos = a.find('/')
155         if mask_pos > 0:
156             a_mask = a[mask_pos + 1:]
157             a = a[:mask_pos]
158             self.mask_check(a, a_mask, 10, 8, '.')
159
160         numbers = a.split('.')
161         max_range = (2**8) - 1
162
163         for n in numbers:
164             if int(n) > max_range:
165                 raise StandardError('octet: {} in ipv4 address: {} larger than: {}'.format(n, a, max_range))
166
167     def ipv6_check(self, a):
168         XMLValidator.log.debug('validating ipv6 address: {}'.format(a))
169         mask_pos = a.find('/')
170         if mask_pos > 0:
171             a_mask = a[mask_pos + 1:]
172             a = a[:mask_pos]
173             self.mask_check(a, a_mask)
174
175         numbers = a.split(':')
176         max_range = (2**16) - 1
177
178         for n in numbers:
179             #if n == '' then the number is 0000 which is always smaller than max_range
180             if n != '' and int(n, 16) > max_range:
181                 raise StandardError('number: {} in ipv6 address: {} larger than: {}'.format(n, a, max_range))
182
183     def check_size(self, value, bits, value_type, convert_from=10):
184         XMLValidator.log.debug('checking value: {}, size should be {} bits'.format(value, bits))
185         ipv6_regexp = re.compile("^[0-9,A-F,a-f]{0,4}(:[0-9,A-F,a-f]{0,4}){1,7}(/[0-9]{1,3})?$")
186         ipv4_regexp = re.compile("^([0-9]{1,3}\.){3}[0-9]{1,3}(/[0-9]{1,2})?$")
187         ethernet_regexp = re.compile("^[0-9,A-F,a-f]{2}(:[0-9,A-F,a-f]{2}){5}$")
188
189         try:
190             if value_type == type_boolean and value in ['true', 'false']:  #boolean values
191                     self.boolean_check(value, bits)
192             elif value_type == type_ethernet and ethernet_regexp.match(value):  #ethernet address
193                 self.ethernet_check(value)
194             elif value_type == type_ipv4 and ipv4_regexp.match(value):  #IPV4 address
195                 self.ipv4_check(value)
196             elif value_type == type_ipv6 and ipv6_regexp.match(value):  #IPV6 address
197                 self.ipv6_check(value)
198             elif value_type == type_int:  #integer values
199                 self.integer_check(value, bits, convert_from)
200             else:
201                 raise StandardError
202
203             XMLValidator.log.info('size of: {} < 2^{} validated successfully'.format(value, bits))
204
205         except ValueError:
206             XMLValidator.log.error('problem converting value to int or IP addresses: {}'.format(value))
207             self.xml_ok = False
208
209         except TypeError:
210             XMLValidator.log.error('problem converting value: {}, TypeError'.format(value))
211             self.xml_ok = False
212
213         except StandardError as e:
214             XMLValidator.log.error('problem checking size for value: {}, reason: {}'.format(value, str(e)))
215             self.xml_ok = False
216
217
218     def has_prerequisite(self, key, values, convert_from, flow_dict):
219         XMLValidator.log.debug('checking prerequisite: {} - {}'.format(key, values))
220         try:
221             flow_value_raw = flow_dict[key]
222
223             #if prerequisites values are [None] we don't care about actual value
224             if values != [None]:
225                 flow_value = int(flow_value_raw, convert_from)
226
227                 if flow_value not in values:
228                     raise StandardError()
229
230             XMLValidator.log.info('prerequisite {}: {} to value {} validated successfully'.format(key, values, flow_value_raw))
231
232         except KeyError:
233             XMLValidator.log.error('can\'t find element: {} in xml {} or in keywords {}'.format(key, self.xml_string, self.mkwd.keys()))
234             self.xml_ok = False
235
236         except ValueError or TypeError:
237             # flow_value_raw is string that cannot be converted to decimal or hex number or None
238             if flow_value_raw not in values:
239                 XMLValidator.log.error('can\'t find element: {} with value value: {} '
240                                'in expected values {}'.format(key, flow_value_raw, values))
241                 self.xml_ok = False
242             else:
243                 XMLValidator.log.info('prerequisite {}: {} to value {} validated successfully'.format(key, values, flow_value_raw))
244
245         except StandardError:
246             XMLValidator.log.error('can\'t find element: {} with value value: {} '
247                            'in expected values {}'.format(key, flow_value, values))
248             self.xml_ok = False
249
250     def check_all_prerequisites(self, prerequisites_dict, convert_from, flow_dict):
251         XMLValidator.log.debug('checking prerequisites: {}'.format(prerequisites_dict))
252         for k, v in prerequisites_dict.items():
253             self.has_prerequisite(k, v, convert_from, flow_dict)
254
255     def check_single_field(self, field, flow_dict):
256         """
257         @type field MatchField
258         @type flow_dict dict
259         """
260
261         if field.key not in flow_dict:
262             XMLValidator.log.debug('{} is not set in XML, skipping validation'.format(field.key))
263             return
264         else:
265             XMLValidator.log.info('validating: {}'.format(field))
266
267         if field.bits is not None:
268             self.check_size(flow_dict[field.key], field.bits, field.value_type, field.convert_from)
269
270         if field.prerequisites is not None:
271             self.check_all_prerequisites(field.prerequisites, field.convert_from, flow_dict)
272
273     def validate_fields(self):
274         self.xml_ok = True
275         XMLValidator.log.info('validating against flow: {}'.format(self.flow_dict))
276         for field in self.fields:
277             self.check_single_field(field, self.flow_dict)
278
279     def validate_misc_values(self):
280         for kw in self.kwd.keys():
281             if kw in self.flow_dict.keys():
282                 XMLValidator.log.info('validating: {}: {}'.format(kw, self.flow_dict[kw]))
283                 try:
284                     value = int(self.flow_dict[kw])
285                     if value < 0:
286                         XMLValidator.log.error('value: {}: {} should be non-negative'.format(kw, self.flow_dict[kw]))
287                         self.xml_ok = False
288                     else:
289                         XMLValidator.log.info('value: {}: {} validated successfully'.format(kw, self.flow_dict[kw]))
290                 except StandardError:
291                     XMLValidator.log.error('can\'t convert value: {}: {} to integer'.format(kw, self.flow_dict[kw]))
292                     self.xml_ok = False
293             else:
294                 XMLValidator.log.debug('{} is not set in XML, skipping validation'.format(kw))
295
296     def validate(self):
297         self.validate_fields()
298         self.validate_misc_values()
299
300         XMLValidator.log.info('XML valid: {}'.format(self.xml_ok))
301
302         return self.xml_ok
303
304 class Matchers():
305
306     IN_PORT = Field('in-port', 32)
307     IN_PHY_PORT = Field('in-phy-port', 32, {'in-port': [None]})
308     METADATA = Field('metadata', 64, convert_from=16)
309
310     ETH_DST = Field('ethernet-source', 48, value_type=type_ethernet)
311     ETH_SRC = Field('ethernet-destination', 48, value_type=type_ethernet)
312     ETH_TYPE = Field('ethernet-type', 16)
313
314     VLAN_VID = Field('vlan-id', 13)
315     VLAN_PCP = Field('vlan-pcp', 3, {'vlan-id': [None]})
316
317     IP_DSCP = Field('ip-dscp', 6, {'ethernet-type': [2048, 34525]})
318     IP_ENC = Field('ip-ecn', 2, {'ethernet-type': [2048, 34525]})
319     IP_PROTO = Field('ip-protocol', 8, {'ethernet-type': [2048, 34525]})
320
321     IPV4_SRC = Field('ipv4-source', 32, {'ethernet-type': [2048]}, value_type=type_ipv4)
322     IPV4_DST = Field('ipv4-destination', 32, {'ethernet-type': [2048]}, value_type=type_ipv4)
323
324     TCP_SRC = Field('tcp-source-port', 16, {'ip-protocol': [6]})
325     TCP_DST = Field('tcp-destination-port', 16, {'ip-protocol': [6]})
326     UDP_SRC = Field('udp-source-port', 16, {'ip-protocol': [17]})
327     UDP_DST = Field('udp-destination-port', 16, {'ip-protocol': [17]})
328     SCTP_SRC = Field('sctp-source-port', 16, {'ip-protocol': [132]})
329     SCTP_DST = Field('sctp-destination-port', 16, {'ip-protocol': [132]})
330     ICMPV4_TYPE = Field('icmpv4-type', 8, {'ip-protocol': [1]})
331     ICMPV4_CODE = Field('icmpv4-code', 8, {'ip-protocol': [1]})
332
333     ARP_OP = Field('arp-op', 16, {'ethernet-type': [2054]})
334     ARP_SPA = Field('arp-source-transport-address', 32, {'ethernet-type': [2054]}, value_type=type_ipv4)
335     ARP_TPA = Field('arp-target-transport-address', 32, {'ethernet-type': [2054]}, value_type=type_ipv4)
336     ARP_SHA = Field('arp-source-hardware-address', 48, {'ethernet-type': [2054]}, value_type=type_ethernet)
337     ARP_THA = Field('arp-target-hardware-address', 48, {'ethernet-type': [2054]}, value_type=type_ethernet)
338
339     IPV6_SRC = Field('ipv6-source', 128, {'ethernet-type': [34525]}, value_type=type_ipv6)
340     IPV6_DST = Field('ipv6-destination', 128, {'ethernet-type': [34525]}, value_type=type_ipv6)
341     IPV6_FLABEL = Field('ipv6-flabel', 20, {'ethernet-type': [34525]})
342
343     ICMPV6_TYPE = Field('icmpv6-type', 8, {'ip-protocol': [58]})
344     ICMPV6_CODE = Field('icmpv6-code', 8, {'ip-protocol': [58]})
345
346     IPV6_ND_TARGET = Field('ipv6-nd-target', 128, {'icmpv6-type': [135, 136]}, value_type=type_ipv6)
347     IPV6_ND_SLL = Field('ipv6-nd-sll', 48, {'icmpv6-type': [135]}, value_type=type_ethernet)
348     IPV6_ND_TLL = Field('ipv6-nd-tll', 48, {'icmpv6-type': [136]}, value_type=type_ethernet)
349
350     MPLS_LABEL = Field('mpls-label', 20, {'ethernet-type': [34887, 34888]})
351     MPLS_TC = Field('mpls-tc', 3, {'ethernet-type': [34887, 34888]})
352     MPLS_BOS = Field('mpls-bos', 1, {'ethernet-type': [34887, 34888]})
353
354     PBB_ISID = Field('pbb-isid', 24, {'ethernet-type': [35047]})
355     TUNNEL_ID = Field('tunnel-id', 64)
356     IPV6_EXTHDR = Field('ipv6-exthdr', 9, {'ethernet-type': [34525]})
357
358
359     @staticmethod
360     def fill_validator(validator):
361         """
362         @type validator XMLValidator
363         """
364
365         validator.add_field(Matchers.IN_PORT)
366         validator.add_field(Matchers.IN_PHY_PORT)
367         validator.add_field(Matchers.METADATA)
368         validator.add_field(Matchers.ETH_DST)
369         validator.add_field(Matchers.ETH_SRC)
370         validator.add_field(Matchers.ETH_TYPE)
371         #validator.add_field(Matchers.VLAN_VID) - incorrenct XML parsing, if vlan-id-present is present its overriden by it, need to fix loader
372         validator.add_field(Matchers.VLAN_PCP)
373         validator.add_field(Matchers.IP_DSCP)
374         validator.add_field(Matchers.IP_ENC)
375         validator.add_field(Matchers.IP_PROTO)
376         validator.add_field(Matchers.IPV4_SRC)
377         validator.add_field(Matchers.IPV4_DST)
378         validator.add_field(Matchers.TCP_SRC)
379         validator.add_field(Matchers.TCP_DST)
380         validator.add_field(Matchers.UDP_SRC)
381         validator.add_field(Matchers.UDP_DST)
382         validator.add_field(Matchers.SCTP_SRC)
383         validator.add_field(Matchers.SCTP_DST)
384         validator.add_field(Matchers.ICMPV4_TYPE)
385         validator.add_field(Matchers.ICMPV4_CODE)
386         validator.add_field(Matchers.ARP_OP)
387         validator.add_field(Matchers.ARP_SPA)
388         validator.add_field(Matchers.ARP_TPA)
389         validator.add_field(Matchers.ARP_SHA)
390         validator.add_field(Matchers.ARP_THA)
391         validator.add_field(Matchers.IPV6_SRC)
392         validator.add_field(Matchers.IPV6_DST)
393         validator.add_field(Matchers.IPV6_FLABEL)
394         validator.add_field(Matchers.ICMPV6_TYPE)
395         validator.add_field(Matchers.ICMPV6_CODE)
396         validator.add_field(Matchers.IPV6_ND_TARGET)
397         validator.add_field(Matchers.IPV6_ND_SLL)
398         validator.add_field(Matchers.IPV6_ND_TLL)
399         validator.add_field(Matchers.MPLS_LABEL)
400         validator.add_field(Matchers.MPLS_TC)
401         validator.add_field(Matchers.MPLS_BOS)
402         validator.add_field(Matchers.PBB_ISID)
403         validator.add_field(Matchers.TUNNEL_ID)
404         validator.add_field(Matchers.IPV6_EXTHDR)
405
406
407 if __name__ == '__main__':
408
409     keywords = None
410     with open('keywords.csv') as f:
411         keywords = dict(line.strip().split(';') for line in f if not line.startswith('#'))
412
413     #print keywords
414
415     match_keywords = None
416     with open('match-keywords.csv') as f:
417         match_keywords = dict(line.strip().split(';') for line in f if not line.startswith('#'))
418
419     #print match_keywords
420
421     action_keywords = None
422     with open('action-keywords.csv') as f:
423         action_keywords = dict(line.strip().split(';') for line in f if not line.startswith('#'))
424
425     paths_to_xml = list()
426     for i in range(1, 50):
427         #paths_to_xml = ['xmls/f5.xml', 'xmls/f14.xml', 'xmls/f23.xml', 'xmls/f25.xml']
428         paths_to_xml.append('xmls/f%d.xml' % i)
429
430     validator = XMLValidator(keywords, action_keywords, match_keywords, logging.ERROR)
431     validator.fill_fields()
432
433     for path in paths_to_xml:
434         validator.create_dictionaries(path)
435         validator.validate()
436
437