5 from openvswitch.parser_tools import ParseTools
6 import xml.dom.minidom as md
13 OPERATIONAL_DELAY = 11
16 FLOW_ID_TEMPLATE = 'FLOW_ID_TEMPLATE'
17 COOKIE_TEMPLATE = 'COOKIE_TEMPLATE'
18 HARD_TO_TEMPLATE = 'HARD_TO_TEMPLATE'
19 FLOW_NAME_TEMPLATE = 'FLOW_NAME_TEMPLATE'
20 IPV4DST_TEMPLATE = 'IPV4DST_TEMPLATE'
21 PRIORITY_TEMPLATE = 'PRIORITY_TEMPLATE'
23 xml_template = '<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>' \
24 '<flow xmlns=\"urn:opendaylight:flow:inventory\">' \
25 '<strict>false</strict>' \
26 '<instructions><instruction><order>0</order><apply-actions><action><order>0</order><dec-nw-ttl/>' \
27 '</action></apply-actions></instruction></instructions><table_id>2</table_id>' \
28 '<id>'+FLOW_ID_TEMPLATE+'</id><cookie_mask>255</cookie_mask><installHw>false</installHw>' \
29 '<match><ethernet-match><ethernet-type><type>2048</type></ethernet-type></ethernet-match>' \
30 '<ipv4-destination>'+IPV4DST_TEMPLATE+'</ipv4-destination></match><hard-timeout>'+HARD_TO_TEMPLATE+'</hard-timeout>' \
31 '<flags>FlowModFlags [_cHECKOVERLAP=false, _rESETCOUNTS=false, _nOPKTCOUNTS=false, _nOBYTCOUNTS=false, _sENDFLOWREM=false]</flags>' \
32 '<cookie>'+COOKIE_TEMPLATE+'</cookie><idle-timeout>34000</idle-timeout><flow-name>'+FLOW_NAME_TEMPLATE+'</flow-name><priority>'+PRIORITY_TEMPLATE+'</priority>' \
33 '<barrier>false</barrier></flow>'
40 'debug' : logging.DEBUG,
41 'info' : logging.INFO,
42 'warning' : logging.WARNING,
43 'error' : logging.ERROR
47 class FlowAdderThread(threading.Thread):
49 Thread to remove flows from TestClassAdd
51 TestClassAdd should implement methods inc_flow(value, key) and inc_error()
54 def __init__(self, test_class, thread_id, host, port, net, flows_ids_from=0, flows_ids_to=1):
56 test_class: should be type of TestClassAdd
57 thread_id: id of thread
58 host: controller's ip address
59 port: controller's port
61 flows_ids_from: minimum id of flow to be added (including)
62 flows_ids_to: maximum id of flow to be added (excluding)
65 threading.Thread.__init__(self)
66 self.thread_id = thread_id
69 self.flows_ids_from = flows_ids_from
70 self.flows_ids_to = flows_ids_to
72 self.test_class = test_class
80 self.log = logging.getLogger('FlowAdderThread: ' + str(thread_id))
81 self.log.propagate = False
82 self.channel = logging.StreamHandler()
83 self.log.addHandler(self.channel)
84 formatter = logging.Formatter('THREAD A{0}: %(levelname)s: %(message)s'.format(self.thread_id))
85 self.channel.setFormatter(formatter)
87 self.log.info('created new FlowAdderThread->id:{0}, flows id: {1} -> {2}'.format(self.thread_id, self.flows_ids_from, self.flows_ids_to))
89 def make_cookie_marker(self, val_input, number=0):
90 return '0x' + "{0:x}".format(int(''.join("{0:x}".format(ord(c)) for c in (val_input)), 16) + number)
92 def make_ipv4_address(self, number, octet_count=4, octet_size=255):
94 ip = ['10', '0', '0', '0']
97 for o in range(1, octet_count):
98 ip[octet_count - 1 - o] = str(number % octet_size)
99 number = number / octet_size
104 return '.'.join(ip) + '/{0}'.format(mask)
106 def __add_flows(self, act_flow_id):
110 self.log.debug('adding flow id: {0}'.format(act_flow_id))
112 cookie_id = self.make_cookie_marker('stress', act_flow_id)
114 xml_string = str(xml_template).replace(FLOW_ID_TEMPLATE, str(act_flow_id))\
115 .replace(COOKIE_TEMPLATE, str(int(cookie_id, 16)))\
116 .replace(HARD_TO_TEMPLATE, '1200').replace(FLOW_NAME_TEMPLATE,'FooXf{0}'.format(act_flow_id))\
117 .replace(IPV4DST_TEMPLATE,self.make_ipv4_address(act_flow_id)).replace(PRIORITY_TEMPLATE,str(act_flow_id))
119 #TestRestartMininet.log.info('loaded xml: {0}'.format(''.join(xml_string.split())))
120 tree = md.parseString(xml_string)
121 ids = ParseTools.get_values(tree.documentElement, 'table_id', 'id')
122 data = (self.host, self.port, ids['table_id'], ids['id'])
124 url = 'http://%s:%d/restconf/config/opendaylight-inventory:nodes' \
125 '/node/openflow:1/table/%s/flow/%s' % data
126 # send request via RESTCONF
128 'Content-Type': 'application/xml',
129 'Accept': 'application/xml',
131 self.log.debug('sending request to url: {0}'.format(url))
132 rsp = requests.put(url, auth=('admin', 'admin'), data=xml_string,
133 headers=headers, timeout=TO_PUT)
134 self.log.debug('received status code: {0}'.format(rsp.status_code))
135 self.log.debug('received content: {0}'.format(rsp.text))
136 assert rsp.status_code == 204 or rsp.status_code == 200, 'Status' \
137 ' code returned %d' % rsp.status_code
139 # we expect that controller doesn't fail to store flow on switch
140 self.test_class.inc_flow(flow_id=act_flow_id, cookie_id=cookie_id)
142 except Exception as e:
143 self.log.error('Error storing flow id:{0}, cookie-id:{1}, reason: {2}'.format(act_flow_id, cookie_id, str(e)))
144 self.test_class.inc_error()
148 self.flows, self.errors = 0, 0
149 self.log.info('adding flows {0} to {1}'.format(self.flows_ids_from, self.flows_ids_to))
150 for i in range(self.flows_ids_from, self.flows_ids_to + 1):
153 self.log.info('finished, added {0} flows, {1} errors'.format(self.flows,self.errors))
157 class FlowRemoverThread(threading.Thread):
159 Thread to remove flows from TestClassDelete
161 TestClassDelete should implement method delete_flows_from_map(value, key)
165 def __init__(self, test_class, thread_id, host, port, net, flows_to_delete=[]):
167 test_class: should be type of TestClassDelete
168 thread_id: id of thread
169 host: controller's ip address
170 port: controller's port
171 net: mininet instance
172 flows_to_delete: dictionary of flows to delete with items to match method delete_flows_from_map(value, key)
175 threading.Thread.__init__(self)
176 self.thread_id = thread_id
177 self.flows_to_delete = flows_to_delete
178 self.test_class = test_class
188 self.log = logging.getLogger('FlowRemoverThread: ' + str(thread_id))
189 self.log.propagate = False
190 self.channel = logging.StreamHandler()
191 self.log.addHandler(self.channel)
192 formatter = logging.Formatter('THREAD R{0}: %(levelname)s: %(message)s'.format(self.thread_id))
193 self.channel.setFormatter(formatter)
195 self.log.info('created new FlowRemoverThread->id:{0}'.format(self.thread_id))
197 def __remove_flows(self, act_flow_id, cookie_id):
198 headers = {'Content-Type': 'application/xml', 'Accept': 'application/xml'}
199 url = 'http://%s:%d/restconf/config/opendaylight-inventory:nodes' \
200 '/node/openflow:1/table/2/flow/%s' % (self.host, self.port, act_flow_id)
203 response = requests.delete(url, auth=('admin','admin'), headers=headers, timeout=TO_DEL)
204 self.log.debug('deletion flow: {0} from controller: response: {1}'.format(act_flow_id, response.status_code))
206 assert response.status_code == 200 or response.status_code == 204, 'Delete response should be 200 or 204 is {0}'.format(response.status_code)
207 self.test_class.delete_flow_from_map(act_flow_id, cookie_id)
210 except Exception as e:
211 self.log.error('Error deleting flow:{0}, reason: {1}'.format(act_flow_id, str(e)))
213 except requests.exceptions.Timeout as te:
214 self.log.error('Error deleting flow: {0}, timeout reached: {1}'.format(act_flow_id, str(te)))
219 self.log.debug('started removing flows')
220 for flow_ids in set(self.flows_to_delete):
221 self.__remove_flows(flow_ids[1], flow_ids[0])
223 self.log.info('finished removing {0} flows, {1} errors'.format(self.removed, self.errors))