11 from multiprocessing import Process
12 import xml.dom.minidom as md
13 from xml.etree import ElementTree as ET
15 from odl_tests_new import MininetTools, ParseTools
17 FLOW_ID_TEMPLATE = 'FLOW_ID_TEMPLATE'
18 COOKIE_TEMPLATE = 'COOKIE_TEMPLATE'
19 HARD_TO_TEMPLATE = 'HARD_TO_TEMPLATE'
20 FLOW_NAME_TEMPLATE = 'FLOW_NAME_TEMPLATE'
21 IPV4DST_TEMPLATE = 'IPV4DST_TEMPLATE'
22 PRIORITY_TEMPLATE = 'PRIORITY_TEMPLATE'
24 xml_template = '<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>' \
25 '<flow xmlns=\"urn:opendaylight:flow:inventory\">' \
26 '<strict>false</strict>' \
27 '<instructions><instruction><order>0</order><apply-actions><action><order>0</order><dec-nw-ttl/>' \
28 '</action></apply-actions></instruction></instructions><table_id>2</table_id>' \
29 '<id>'+FLOW_ID_TEMPLATE+'</id><cookie_mask>255</cookie_mask><installHw>false</installHw>' \
30 '<match><ethernet-match><ethernet-type><type>2048</type></ethernet-type></ethernet-match>' \
31 '<ipv4-destination>'+IPV4DST_TEMPLATE+'</ipv4-destination></match><hard-timeout>'+HARD_TO_TEMPLATE+'</hard-timeout>' \
32 '<flags>FlowModFlags [_cHECKOVERLAP=false, _rESETCOUNTS=false, _nOPKTCOUNTS=false, _nOBYTCOUNTS=false, _sENDFLOWREM=false]</flags>' \
33 '<cookie>'+COOKIE_TEMPLATE+'</cookie><idle-timeout>34</idle-timeout><flow-name>'+FLOW_NAME_TEMPLATE+'</flow-name><priority>'+PRIORITY_TEMPLATE+'</priority>' \
34 '<barrier>false</barrier></flow>'
40 def get_flows_string(net=None):
44 switch = net.switches[0]
45 output = switch.cmdPrint(
46 'ovs-ofctl -O OpenFlow13 dump-flows %s' % switch.name)
48 return output.splitlines()[1:]
51 class MultiTest(unittest.TestCase):
53 log = logging.getLogger('MultiTest')
58 MultiTest.log.info('setUp')
59 self.threads_count = 50
60 self.thread_pool = list()
63 self.__setup_threads()
67 MultiTest.log.info('tearDown')
71 def inc_error(value=1):
72 MultiTest.total_errors += value
75 def inc_flow(value=1):
76 MultiTest.total_flows += 1
81 self.net = MininetTools.create_network(self.host, self.mn_port)
83 MultiTest.log.info('mininet stared')
84 MultiTest.log.info('waiting {} seconds...'.format(wait_time))
88 def __setup_threads(self):
89 if args.threads is not None:
90 self.threads_count = int(args.threads)
92 for i in range(0, self.threads_count):
93 #thread will have predetermined flows id to avoid using shared resource
94 t = FlowAdderThread(i, self.host, self.port, self.net, flows_ids_from=i*MultiTest.flows + 1, flows_ids_to=(i+1)*MultiTest.flows + 1)
96 self.thread_pool.append(t)
98 def __run_threads(self):
100 for t in self.thread_pool:
103 # wait for them to finish
104 for t in self.thread_pool:
108 #for t in self.thread_pool:
109 # MultiTest.inc_flow(t.flows)
110 # MultiTest.inc_error(t.errors)
116 assert MultiTest.total_flows > 0, 'Stored flows should be greater than 0, actual is {}'.format(MultiTest.total_flows)
118 MultiTest.log.info('\n\n---------- preparation finished, running test ----------')
120 url = 'http://%s:%d/restconf/config/opendaylight-inventory:nodes' \
121 '/node/openflow:1/table/2/' % (self.host, self.port)
122 MultiTest.log.info('checking flows in controller - sending request to url: {}'.format(url))
123 response = requests.get(url, auth=('admin', 'admin'),
124 headers={'Accept': 'application/xml'})
125 assert response.status_code == 200
127 tree = ET.ElementTree(ET.fromstring(response.text))
128 flows_on_controller = len(tree.getroot())
131 url = 'http://%s:%d/restconf/operational/opendaylight-inventory:nodes' \
132 '/node/openflow:1/table/2/' % (self.host, self.port)
133 MultiTest.log.info('checking flows in controller - sending request to url: {}'.format(url))
134 response = requests.get(url, auth=('admin', 'admin'),
135 headers={'Accept': 'application/xml'})
136 #assert response.status_code == 200
137 MultiTest.log.info('got resposnse: {}'.format(response.status_code))
138 MultiTest.log.info('operational dump:\n{}'.format(response.text))
140 MultiTest.log.info('{} flows are stored by results from threads, {} errors'.format(MultiTest.total_flows, MultiTest.total_errors))
141 MultiTest.log.info('{} flows are stored in controller config'.format(flows_on_controller))
143 switch_flows_list = Tool.get_flows_string(self.net)
144 switch_flows = len(switch_flows_list)
145 MultiTest.log.info('{} flows are stored on switch'.format(switch_flows))
146 MultiTest.log.debug('switch flow-dump:\n{}'.format(switch_flows_list))
149 assert MultiTest.total_flows == switch_flows, 'Added amount of flows to switch should be equal to successfully added flows to controller {} <> {}'.format(switch_flows,MultiTest.total_flows)
152 class FlowAdderThread(threading.Thread):
154 def __init__(self, thread_id, host, port, net, flows_ids_from=0, flows_ids_to=1):
155 threading.Thread.__init__(self)
156 self.thread_id = thread_id
159 self.flows_ids_from = flows_ids_from
160 self.flows_ids_to = flows_ids_to
166 self.log = logging.getLogger('FlowAdderThread: ' + str(thread_id))
167 self.log.propagate = False
168 self.channel = logging.StreamHandler()
169 self.log.addHandler(self.channel)
170 formatter = logging.Formatter('THREAD {}: %(levelname)s: %(message)s'.format(self.thread_id))
171 self.channel.setFormatter(formatter)
172 #self.log.setLevel(logging.INFO)
174 self.log.info('created new FlowAdderThread->id:{}, flows id: {} -> {}'.format(self.thread_id, self.flows_ids_from, self.flows_ids_to))
176 def make_ipv4_address(self, number, octet_count=4, octet_size=255):
178 ip = ['10', '0', '0', '0']
180 if number < (255**3):
181 for o in range(1, octet_count):
182 ip[octet_count - 1 - o] = str(number % octet_size)
183 number = number / octet_size
188 return '.'.join(ip) + '/{}'.format(mask)
190 def __add_flows(self, act_flow_id):
192 self.log.info('adding flow id: {}'.format(act_flow_id))
193 self.log.debug('flow ip address from id: {}'.format(self.make_ipv4_address(act_flow_id)))
195 xml_string = str(xml_template).replace(FLOW_ID_TEMPLATE, str(act_flow_id)).replace(COOKIE_TEMPLATE, str(act_flow_id))\
196 .replace(HARD_TO_TEMPLATE, '12').replace(FLOW_NAME_TEMPLATE,'FooXf{}'.format(act_flow_id))\
197 .replace(IPV4DST_TEMPLATE,self.make_ipv4_address(act_flow_id)).replace(PRIORITY_TEMPLATE,str(act_flow_id))
199 #TestRestartMininet.log.info('loaded xml: {}'.format(''.join(xml_string.split())))
200 tree = md.parseString(xml_string)
201 ids = ParseTools.get_values(tree.documentElement, 'table_id', 'id')
202 data = (self.host, self.port, ids['table_id'], ids['id'])
204 url = 'http://%s:%d/restconf/config/opendaylight-inventory:nodes' \
205 '/node/openflow:1/table/%s/flow/%s' % data
206 # send request via RESTCONF
208 'Content-Type': 'application/xml',
209 'Accept': 'application/xml',
211 self.log.debug('sending request to url: {}'.format(url))
212 rsp = requests.put(url, auth=('admin', 'admin'), data=xml_string,
214 self.log.debug('received status code: {}'.format(rsp.status_code))
215 self.log.debug('received content: {}'.format(rsp.text))
216 assert rsp.status_code == 204 or rsp.status_code == 200, 'Status' \
217 ' code returned %d' % rsp.status_code
219 # check request content against restconf's datastore
220 #response = requests.get(url, auth=('admin', 'admin'),
221 # headers={'Accept': 'application/xml'})
222 #assert response.status_code == 200, 'Conifg response should be 200, is {}'.format(response.status_code)
224 #switch_flows = Tool.get_flows_string(self.net)
225 #assert len(switch_flows) > 0, 'Flows stored on switch shoul be greater than 0'
227 # we expect that controller doesn't fail to store flow on switch
230 # store last used table id which got flows for later checkup
231 #self.log.debug('{} successfully stored flows - {} flows are on switch'.format(self.flows, len(switch_flows)))
232 except AssertionError as e:
234 self.log.error('AssertionError storing flow id:{}, reason: {}'.format(act_flow_id, str(e)))
235 MultiTest.inc_error()
236 except Exception as e:
238 self.log.error('Error storing flow id:{}, reason: {}'.format(act_flow_id, str(e)))
239 MultiTest.inc_error()
242 self.log.info('started... adding flows {} to {}'.format(self.flows_ids_from, self.flows_ids_to))
243 for i in range(self.flows_ids_from, self.flows_ids_to):
246 self.log.info('finished, successfully added {} flows, {} errors'.format(self.flows,self.errors))
249 if __name__ == '__main__':
250 logging.basicConfig(level=logging.INFO)
252 # parse cmdline arguments
253 parser = argparse.ArgumentParser(description='Test for flow addition to'
254 ' switch after the switch has been restarted')
255 parser.add_argument('--odlhost', default='127.0.0.1', help='host where '
256 'odl controller is running')
257 parser.add_argument('--odlport', type=int, default=8080, help='port on '
258 'which odl\'s RESTCONF is listening')
259 parser.add_argument('--mnport', type=int, default=6653, help='port on '
260 'which odl\'s controller is listening')
261 parser.add_argument('--xmls', default=None, help='generete tests only '
262 'from some xmls (i.e. 1,3,34) ')
263 parser.add_argument('--threads', default=50, help='how many threads '
265 parser.add_argument('--flows', default=20, help='how many flows will add'
267 args = parser.parse_args()
269 # set host and port of ODL controller for test cases
270 MultiTest.port = args.odlport
271 MultiTest.host = args.odlhost
272 MultiTest.mn_port = args.mnport
273 MultiTest.threads = int(args.threads)
274 MultiTest.flows = int(args.flows)