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