blob: 1b97136c34e286a9560a9f0c517b566778a1a9d5 [file] [log] [blame]
Khen Nursimuluc60afa12017-03-13 14:33:50 -04001from time import time, sleep
2
3from google.protobuf.json_format import MessageToDict
4
5from voltha.core.flow_decomposer import *
6from voltha.protos.device_pb2 import Device
7from voltha.protos.common_pb2 import AdminState, OperStatus
8from voltha.protos import openflow_13_pb2 as ofp
9from tests.itests.voltha.rest_base import RestBase
10from common.utils.consulhelpers import get_endpoint_from_consul
11
12LOCAL_CONSUL = "localhost:8500"
13
14
15class TestDeviceStateChangeSequence(RestBase):
16 """
17 The prerequisite for this test are:
18 1. voltha ensemble is running
19 docker-compose -f compose/docker-compose-system-test.yml up -d
20 2. ponsim olt is running with 1 OLT and 4 ONUs
21 sudo -s
22 . ./env.sh
23 ./ponsim/main.py -v -o 4
24 """
25
26 # Retrieve details of the REST entry point
Stephane Barbariecd51f992017-09-07 16:37:02 -040027 rest_endpoint = get_endpoint_from_consul(LOCAL_CONSUL, 'envoy-8443')
Khen Nursimuluc60afa12017-03-13 14:33:50 -040028
29 # Construct the base_url
ubuntuc5c83d72017-07-01 17:57:19 -070030 base_url = 'https://' + rest_endpoint
Khen Nursimuluc60afa12017-03-13 14:33:50 -040031
32 def wait_till(self, msg, predicate, interval=0.1, timeout=5.0):
33 deadline = time() + timeout
34 while time() < deadline:
35 if predicate():
36 return
37 sleep(interval)
38 self.fail('Timed out while waiting for condition: {}'.format(msg))
39
40 def test_device_state_changes_scenarios(self):
41
42 self.verify_prerequisites()
43 # Test basic scenario
44
45 self.basic_scenario()
46 self.failure_scenario()
47
48 def basic_scenario(self):
49 """
50 Test the enable -> disable -> enable -> disable -> delete for OLT
51 and ONU.
52 """
53 self.assert_no_device_present()
54 olt_id = self.add_olt_device()
55 self.verify_device_preprovisioned_state(olt_id)
56 self.enable_device(olt_id)
57 ldev_id = self.wait_for_logical_device(olt_id)
58 onu_ids = self.wait_for_onu_discovery(olt_id)
59 self.verify_logical_ports(ldev_id, 5)
60 self.simulate_eapol_flow_install(ldev_id, olt_id, onu_ids)
61 self.verify_olt_eapol_flow(olt_id)
62 olt_ids, onu_ids = self.get_devices()
63 self.disable_device(onu_ids[0])
64 self.verify_logical_ports(ldev_id, 4)
65 self.enable_device(onu_ids[0])
66 self.verify_logical_ports(ldev_id, 5)
67 self.simulate_eapol_flow_install(ldev_id, olt_id, onu_ids)
68 self.verify_olt_eapol_flow(olt_id)
69 self.disable_device(olt_ids[0])
70 self.assert_all_onus_state(olt_ids[0], 'DISABLED', 'UNKNOWN')
71 self.assert_no_logical_device()
72 self.enable_device(olt_ids[0])
73 self.assert_all_onus_state(olt_ids[0], 'ENABLED', 'ACTIVE')
74 self.wait_for_logical_device(olt_ids[0])
75 self.simulate_eapol_flow_install(ldev_id, olt_id, onu_ids)
76 self.verify_olt_eapol_flow(olt_id)
77 self.disable_device(onu_ids[0])
78 self.delete_device(onu_ids[0])
79 self.verify_logical_ports(ldev_id, 4)
80 self.disable_device(olt_ids[0])
81 self.delete_device(olt_ids[0])
82 self.assert_no_device_present()
83
84 def failure_scenario(self):
85 self.assert_no_device_present()
86 olt_id = self.add_olt_device()
87 self.verify_device_preprovisioned_state(olt_id)
88 self.enable_device(olt_id)
89 ldev_id = self.wait_for_logical_device(olt_id)
90 onu_ids = self.wait_for_onu_discovery(olt_id)
91 self.verify_logical_ports(ldev_id, 5)
92 self.simulate_eapol_flow_install(ldev_id, olt_id, onu_ids)
93 self.verify_olt_eapol_flow(olt_id)
94 self.delete_device_incorrect_state(olt_id)
95 self.delete_device_incorrect_state(onu_ids[0])
96 unknown_id = '9999999999'
97 self.enable_unknown_device(unknown_id)
98 self.disable_unknown_device(unknown_id)
99 self.delete_unknown_device(unknown_id)
100 latest_olt_ids, latest_onu_ids = self.get_devices()
101 self.assertEqual(len(latest_olt_ids), 1)
102 self.assertEqual(len(latest_onu_ids), 4)
103 self.verify_logical_ports(ldev_id, 5)
104 self.simulate_eapol_flow_install(ldev_id, olt_id, onu_ids)
105 # Cleanup
106 self.disable_device(olt_id)
107 self.delete_device(olt_id)
108 self.assert_no_device_present()
109
110 def verify_prerequisites(self):
111 # all we care is that Voltha is available via REST using the base uri
112 self.get('/api/v1')
113
114 def get_devices(self):
115 devices = self.get('/api/v1/devices')['items']
116 olt_ids = []
117 onu_ids = []
118 for d in devices:
119 if d['adapter'] == 'ponsim_olt':
120 olt_ids.append(d['id'])
121 elif d['adapter'] == 'ponsim_onu':
122 onu_ids.append(d['id'])
123 else:
124 onu_ids.append(d['id'])
125 return olt_ids, onu_ids
126
127 def add_olt_device(self):
128 device = Device(
129 type='ponsim_olt',
130 host_and_port='172.17.0.1:50060'
131 )
132 device = self.post('/api/v1/devices', MessageToDict(device),
Stephane Barbariecd51f992017-09-07 16:37:02 -0400133 expected_http_code=200)
Khen Nursimuluc60afa12017-03-13 14:33:50 -0400134 return device['id']
135
136 def verify_device_preprovisioned_state(self, olt_id):
137 # we also check that so far what we read back is same as what we get
138 # back on create
139 device = self.get('/api/v1/devices/{}'.format(olt_id))
140 self.assertNotEqual(device['id'], '')
141 self.assertEqual(device['adapter'], 'ponsim_olt')
142 self.assertEqual(device['admin_state'], 'PREPROVISIONED')
143 self.assertEqual(device['oper_status'], 'UNKNOWN')
144
145 def enable_device(self, olt_id):
146 path = '/api/v1/devices/{}'.format(olt_id)
Stephane Barbariecd51f992017-09-07 16:37:02 -0400147 self.post(path + '/enable', expected_http_code=200)
Khen Nursimuluc60afa12017-03-13 14:33:50 -0400148 device = self.get(path)
149 self.assertEqual(device['admin_state'], 'ENABLED')
150
151 self.wait_till(
152 'admin state moves to ACTIVATING or ACTIVE',
153 lambda: self.get(path)['oper_status'] in ('ACTIVATING', 'ACTIVE'),
154 timeout=0.5)
155
156 # eventually, it shall move to active state and by then we shall have
157 # device details filled, connect_state set, and device ports created
158 self.wait_till(
159 'admin state ACTIVE',
160 lambda: self.get(path)['oper_status'] == 'ACTIVE',
161 timeout=0.5)
162 device = self.get(path)
163 self.assertEqual(device['connect_status'], 'REACHABLE')
164
165 ports = self.get(path + '/ports')['items']
166 self.assertEqual(len(ports), 2)
167
168 def wait_for_logical_device(self, olt_id):
169 # we shall find the logical device id from the parent_id of the olt
170 # (root) device
171 device = self.get(
172 '/api/v1/devices/{}'.format(olt_id))
173 self.assertNotEqual(device['parent_id'], '')
174 logical_device = self.get(
175 '/api/v1/logical_devices/{}'.format(device['parent_id']))
176
177 # the logical device shall be linked back to the hard device,
178 # its ports too
179 self.assertEqual(logical_device['root_device_id'], device['id'])
180
181 logical_ports = self.get(
182 '/api/v1/logical_devices/{}/ports'.format(
183 logical_device['id'])
184 )['items']
185 self.assertGreaterEqual(len(logical_ports), 1)
186 logical_port = logical_ports[0]
187 self.assertEqual(logical_port['id'], 'nni')
188 self.assertEqual(logical_port['ofp_port']['name'], 'nni')
189 self.assertEqual(logical_port['ofp_port']['port_no'], 0)
190 self.assertEqual(logical_port['device_id'], device['id'])
191 self.assertEqual(logical_port['device_port_no'], 2)
192 return logical_device['id']
193
194 def find_onus(self, olt_id):
195 devices = self.get('/api/v1/devices')['items']
196 return [
197 d for d in devices
198 if d['parent_id'] == olt_id
199 ]
200
201 def wait_for_onu_discovery(self, olt_id):
202 # shortly after we shall see the discovery of four new onus, linked to
203 # the olt device
204 self.wait_till(
205 'find four ONUs linked to the olt device',
206 lambda: len(self.find_onus(olt_id)) >= 4,
207 2
208 )
209 # verify that they are properly set
210 onus = self.find_onus(olt_id)
211 for onu in onus:
212 self.assertEqual(onu['admin_state'], 'ENABLED')
213 self.assertEqual(onu['oper_status'], 'ACTIVE')
214
215 return [onu['id'] for onu in onus]
216
217 def assert_all_onus_state(self, olt_id, admin_state, oper_state):
218 # verify all onus are in a given state
219 onus = self.find_onus(olt_id)
220 for onu in onus:
221 self.assertEqual(onu['admin_state'], admin_state)
222 self.assertEqual(onu['oper_status'], oper_state)
223
224 return [onu['id'] for onu in onus]
225
226 def assert_onu_state(self, onu_id, admin_state, oper_state):
227 # Verify the onu states are correctly set
228 onu = self.get('/api/v1/devices/{}'.format(onu_id))
229 self.assertEqual(onu['admin_state'], admin_state)
230 self.assertEqual(onu['oper_status'], oper_state)
231
232 def verify_logical_ports(self, ldev_id, num_ports):
233
234 # at this point we shall see num_ports logical ports on the
235 # logical device
236 logical_ports = self.get(
237 '/api/v1/logical_devices/{}/ports'.format(ldev_id)
238 )['items']
239 self.assertGreaterEqual(len(logical_ports), num_ports)
240
241 # verify that all logical ports are LIVE (state=4)
242 for lport in logical_ports:
243 self.assertEqual(lport['ofp_port']['state'], 4)
244
245 def simulate_eapol_flow_install(self, ldev_id, olt_id, onu_ids):
246
247 # emulate the flow mod requests that shall arrive from the SDN
248 # controller, one for each ONU
249 lports = self.get(
250 '/api/v1/logical_devices/{}/ports'.format(ldev_id)
251 )['items']
252
253 # device_id -> logical port map, which we will use to construct
254 # our flows
255 lport_map = dict((lp['device_id'], lp) for lp in lports)
256 for onu_id in onu_ids:
257 # if eth_type == 0x888e => send to controller
258 _in_port = lport_map[onu_id]['ofp_port']['port_no']
259 req = ofp.FlowTableUpdate(
Stephane Barbariecd51f992017-09-07 16:37:02 -0400260 id=ldev_id,
Khen Nursimuluc60afa12017-03-13 14:33:50 -0400261 flow_mod=mk_simple_flow_mod(
262 match_fields=[
263 in_port(_in_port),
264 vlan_vid(ofp.OFPVID_PRESENT | 0),
265 eth_type(0x888e)],
266 actions=[
267 output(ofp.OFPP_CONTROLLER)
268 ],
269 priority=1000
270 )
271 )
272 res = self.post('/api/v1/logical_devices/{}/flows'.format(ldev_id),
273 MessageToDict(req,
274 preserving_proto_field_name=True),
Stephane Barbariecd51f992017-09-07 16:37:02 -0400275 expected_http_code=200)
Khen Nursimuluc60afa12017-03-13 14:33:50 -0400276
277 # for sanity, verify that flows are in flow table of logical device
278 flows = self.get(
279 '/api/v1/logical_devices/{}/flows'.format(ldev_id))['items']
280 self.assertGreaterEqual(len(flows), 4)
281
282 def verify_olt_eapol_flow(self, olt_id):
283 # olt shall have two flow rules, one is the default and the
284 # second is the result of eapol forwarding with rule:
285 # if eth_type == 0x888e => push vlan(1000); out_port=nni_port
286 flows = self.get('/api/v1/devices/{}/flows'.format(olt_id))['items']
287 self.assertEqual(len(flows), 2)
288 flow = flows[1]
289 self.assertEqual(flow['table_id'], 0)
290 self.assertEqual(flow['priority'], 1000)
291
292 # TODO refine this
293 # self.assertEqual(flow['match'], {})
294 # self.assertEqual(flow['instructions'], [])
295
296 def disable_device(self, id):
297 path = '/api/v1/devices/{}'.format(id)
Stephane Barbariecd51f992017-09-07 16:37:02 -0400298 self.post(path + '/disable', expected_http_code=200)
Khen Nursimuluc60afa12017-03-13 14:33:50 -0400299 device = self.get(path)
300 self.assertEqual(device['admin_state'], 'DISABLED')
301
302 self.wait_till(
303 'operational state moves to UNKNOWN',
304 lambda: self.get(path)['oper_status'] == 'UNKNOWN',
305 timeout=0.5)
306
307 # eventually, the connect_state should be UNREACHABLE
308 self.wait_till(
309 'connest status UNREACHABLE',
310 lambda: self.get(path)['connect_status'] == 'UNREACHABLE',
311 timeout=0.5)
312
313 # Device's ports should be INACTIVE
314 ports = self.get(path + '/ports')['items']
315 self.assertEqual(len(ports), 2)
316 for p in ports:
317 self.assertEqual(p['admin_state'], 'DISABLED')
318 self.assertEqual(p['oper_status'], 'UNKNOWN')
319
320 def delete_device(self, id):
321 path = '/api/v1/devices/{}'.format(id)
Stephane Barbariecd51f992017-09-07 16:37:02 -0400322 self.delete(path + '/delete', expected_http_code=200, grpc_status=0)
323 device = self.get(path, expected_http_code=200, grpc_status=5)
Khen Nursimuluc60afa12017-03-13 14:33:50 -0400324 self.assertIsNone(device)
325
326 def assert_no_device_present(self):
327 path = '/api/v1/devices'
328 devices = self.get(path)['items']
329 self.assertEqual(devices, [])
330
331 def assert_no_logical_device(self):
332 path = '/api/v1/logical_devices'
333 ld = self.get(path)['items']
334 self.assertEqual(ld, [])
335
336 def delete_device_incorrect_state(self, id):
337 path = '/api/v1/devices/{}'.format(id)
Stephane Barbariecd51f992017-09-07 16:37:02 -0400338 self.delete(path + '/delete', expected_http_code=200, grpc_status=3)
Khen Nursimuluc60afa12017-03-13 14:33:50 -0400339
340 def enable_unknown_device(self, id):
341 path = '/api/v1/devices/{}'.format(id)
Stephane Barbariecd51f992017-09-07 16:37:02 -0400342 self.post(path + '/enable', expected_http_code=200, grpc_status=5)
Khen Nursimuluc60afa12017-03-13 14:33:50 -0400343
344 def disable_unknown_device(self, id):
345 path = '/api/v1/devices/{}'.format(id)
Stephane Barbariecd51f992017-09-07 16:37:02 -0400346 self.post(path + '/disable', expected_http_code=200, grpc_status=5)
Khen Nursimuluc60afa12017-03-13 14:33:50 -0400347
348 def delete_unknown_device(self, id):
349 path = '/api/v1/devices/{}'.format(id)
Stephane Barbariecd51f992017-09-07 16:37:02 -0400350 self.delete(path + '/delete', expected_http_code=200, grpc_status=5)