blob: ab7a2f78b665ad25344c738bb36c128fc57fec68 [file] [log] [blame]
Srikanth Vavilapalli1725e492014-12-01 17:50:52 -08001#
2# Copyright (c) 2012,2013 Big Switch Networks, Inc.
3#
4# Licensed under the Eclipse Public License, Version 1.0 (the
5# "License"); you may not use this file except in compliance with the
6# License. You may obtain a copy of the License at
7#
8# http://www.eclipse.org/legal/epl-v10.html
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13# implied. See the License for the specific language governing
14# permissions and limitations under the License.
15#
16
17import collections
18import urllib
19import error
20import json
21import modi
22import utif
23import url_cache
24
25#
26# json based on non model based query
27#
28
29def rest_to_model_init(bs, modi):
30 global sdnsh, mi
31
32 sdnsh = bs
33 mi = modi
34
35#
36# --------------------------------------------------------------------------------
37
38def check_rest_result(result, message = None):
39 if isinstance(result, collections.Mapping):
40 error_type = result.get('error_type')
41 if error_type:
42 raise error.CommandRestError(result, message)
43
44
45#
46# --------------------------------------------------------------------------------
47
48def get_model_from_url(obj_type, data):
49 """
50 Intended to be used as a conversion tool, to provide a way
51 to move model requests to specific url requests
52 """
53
54 onos = 1
55 startswith = '__startswith'
56 data = dict(data)
57
58 if mi.obj_type_has_model(obj_type):
59 print "MODEL_URL: %s not supported" % obj_type
60 return []
61 #
62 # save sort field
63 sort = data.get('orderby', None)
64 if sort:
65 del data['orderby']
66
67 obj_type_url = mi.obj_type_has_url(obj_type)
68 url = "http://%s/rest/v1/" % sdnsh.controller + obj_type_url
69
70 # for the devices query, be sensitive to the types of queries
71 # since the devices query doesn't understand the relationship
72 # between the vlan=='' for address_space == 'default'.
73 if obj_type_url == 'device':
74 if 'vlan' in data and data['vlan'] == '':
75 del data['vlan']
76
77 # data items need to be included into the query.
78 if data:
79 url += '?'
80 url += urllib.urlencode(data)
81 if sdnsh.description: # description debugging
82 print "get_model_from_url: request ", obj_type, data, url
83 #
84 # cache small, short time results
85 entries = url_cache.get_cached_url(url)
86 if entries != None:
87 if sdnsh.description: # description debugging
88 print 'get_model_from_url: using cached result for ', url
89 else:
90 try:
91 result = sdnsh.store.rest_simple_request(url)
92 check_rest_result(result)
93 entries = json.loads(result)
94 except Exception, e:
95 if sdnsh.description: # description debugging
96 print "get_model_from_url: failed request %s: '%s'" % (url, e)
97 entries = []
98
99 url_cache.save_url(url, entries)
100
101 key = mi.pk(obj_type)
102
103 # convert specific types XXX need better generic mechanism
104 result = []
105 if obj_type == 'host-attachment-point':
106 for entry in entries:
107 # it is possible for multiple mac's to appear here
108 # as long as a single ip is associated with the device.
109 if len(entry['mac']) > 1:
110 raise error.CommandInternalError("host (attachment point): >1 mac")
111 mac = entry['mac'][0]
112 aps = entry['attachmentPoint']
113 lastseen = entry['lastSeen']
114 for ap in aps:
115 status = ap.get('errorStatus', '')
116 if status == None:
117 status = ''
118 if len(entry['vlan']) == 0:
119 result.append({'id' : mac + '|' + ap['switchDPID'] +
120 '|' + str(ap['port']),
121 'mac' : mac,
122 'switch' : ap['switchDPID'],
123 'ingress-port' : ap['port'],
124 'status' : status,
125 'last-seen' : lastseen,
126 'address-space': entry['entityClass'],
127 })
128 else:
129 for vlan in entry['vlan']:
130 result.append({'id' : mac + '|' + ap['switchDPID'] +
131 '|' + str(ap['port']),
132 'mac' : mac,
133 'vlan' : vlan,
134 'switch' : ap['switchDPID'],
135 'ingress-port' : ap['port'],
136 'status' : status,
137 'last-seen' : lastseen,
138 'address-space': entry['entityClass'],
139 })
140 elif obj_type == 'host-network-address':
141
142 for entry in entries:
143 if len(entry['mac']) > 1:
144 raise error.CommandInternalError("host (network-address): >1 mac")
145 mac = entry['mac'][0]
146 ips = entry['ipv4']
147 lastseen = entry['lastSeen']
148 for ip in ips:
149 if len(entry['vlan']) == 0:
150 result.append({'id' : mac + '|' + ip,
151 'mac' : mac,
152 'ip-address' : ip,
153 'address-space' : entry['entityClass'],
154 'last-seen' : lastseen })
155 else:
156 for vlan in entry['vlan']:
157 result.append({'id' : mac + '|' + ip,
158 'mac' : mac,
159 'vlan' : vlan,
160 'ip-address' : ip,
161 'address-space' : entry['entityClass'],
162 'last-seen' : lastseen })
163 elif obj_type == 'host':
164 # For host queries, the device manager returns 'devices' which
165 # match specific criteria, for example, if you say ipv4 == 'xxx',
166 # the device which matches that criteris is returned. However,
167 # there may be multiple ip addresses associated.
168 #
169 # this means that two different groups of values for these
170 # entities are returned. fields like 'ipv4' is the collection
171 # of matching values, while 'ips' is the complee list.
172 # 'switch', 'port' are the match values, while 'attachment-points'
173 # is the complete list.
174 #
175
176 ip_match = data.get('ipv4')
177 ip_prefix = data.get('ipv4__startswith')
178
179 dpid_match = data.get('dpid')
180 dpid_prefix = data.get('dpid__startswith')
181
182 port_match = data.get('port')
183 port_prefix = data.get('port__startswith')
184
185 address_space_match = data.get('address-space')
186 address_space_prefix = data.get('address-space__startswith')
187
188 for entry in entries:
189 if onos==0:
190 if len(entry['mac']) > 1:
191 raise error.CommandInternalError("host: >1 mac")
192 mac = entry['mac'][0]
193 lastseen = entry['lastSeen']
194 else:
195 mac = entry['mac']
196 lastseen = 0
197
198 ips = None
199 if not ip_match and not ip_prefix:
200 ipv4 = entry['ipv4']
201 elif ip_match:
202 ipv4 = [x for x in entry['ipv4'] if x == ip_match]
203 elif ip_prefix:
204 ipv4 = [x for x in entry['ipv4'] if x.startswith(ip_prefix)]
205
206 if len(entry['ipv4']):
207 ips = [{'ip-address' : entry['ipv4'], 'last-seen' : lastseen}]
208 #for x in entry['ipv4'] ]
209 aps = None
210 switch = []
211 port = []
212
213 #dpid_match = entry.get('dpid')
214 if onos == 0:
215 attachPoint = 'attachmentPoint'
216 attachDpid = 'switchDPID'
217 attachPort = 'port'
218 else:
219 attachPoint = 'attachmentPoints'
220 attachDpid = 'dpid'
221 attachPort = 'portNumber'
222
223 if len(entry[attachPoint]):
224 aps = [{'switch' : x[attachDpid], 'ingress-port' : x[attachPort] }
225 for x in entry[attachPoint]]
226
227 if not dpid_match and not dpid_prefix:
228 switch = [x[attachDpid] for x in entry[attachPoint]]
229 elif dpid_match:
230 switch = [x[attachDpid] for x in entry[attachPoint]
231 if x[attachDpid] == dpid_match]
232 elif dpid_prefix:
233 switch = [x[attachDpid] for x in entry[attachPoint]
234 if x[attachDpid].startswith(dpid_prefix)]
235
236 if not port_match and not port_prefix:
237 port = [x[attachPort] for x in entry[attachPoint]]
238 elif port_match:
239 port = [x[attachPort] for x in entry[attachPoint]
240 if x[attachPort] == port_match]
241 elif port_prefix:
242 port = [x[attachPort] for x in entry[attachPoint]
243 if x[attachPort].startswith(port_prefix)]
244
245 if onos == 0:
246 address_space = entry['entityClass']
247 dhcp_client_name = entry.get('dhcpClientName', '')
248 if address_space_match and address_space != address_space_match:
249 continue
250 if address_space_prefix and not address_space.startswith(address_space_prefix):
251 continue
252
253 if onos == 1:
254 id = '%s' % (mac)
255 result.append({'id' : id,
256 'mac' : mac,
257 'ips' : ips,
258 'ipv4' : ipv4,
259 'attachment-points' : aps,
260 'dpid' : switch,
261 'port' : port,
262 'last-seen' : 0})
263 else:
264 if len(entry['vlan']) == 0:
265 id = '%s||%s' % (address_space, mac)
266 result.append({'id' : id,
267 'mac' : mac,
268 'ips' : ips,
269 'ipv4' : ipv4,
270 'attachment-points' : aps,
271 'dpid' : switch,
272 'port' : port,
273 'address-space' : address_space,
274 'dhcp-client-name' : dhcp_client_name,
275 'last-seen' : lastseen})
276 else:
277 for vlan in entry['vlan']:
278 if address_space != 'default':
279 id = '%s||%s' % (address_space, mac)
280 else:
281 id = '%s|%s|%s' % (address_space, vlan, mac)
282 result.append({'id' : id,
283 'mac' : mac,
284 'vlan' : vlan,
285 'ips' : ips,
286 'ipv4' : ipv4,
287 'attachment-points' : aps,
288 'dpid' : switch,
289 'port' : port,
290 'address-space' : address_space,
291 'dhcp-client-name' : dhcp_client_name,
292 'last-seen' : lastseen})
293
294 # Also need to add hostConfig entries.
295 if not mi.obj_type_has_model('host-config'):
296 raise error.CommandInternalError("host-config: not served via model")
297 host_config = sdnsh.rest_query_objects('host-config', data)
298 if sdnsh.description: # description debugging
299 print "get_model_from_url: adding host-config ", data, host_config
300 known_ids = [x['id'] for x in result]
301
302 for hc in host_config:
303 id = hc['id']
304 if id not in known_ids:
305 # be sensitive to search fields:
306 query_match = True
307 if len(data):
308 for (d, dv) in data.items():
309 # other ops aside from '='?
310 if d.endswith(startswith):
311 fn = d[:-len(startswith)]
312 if not fn in hc or not hc[fn].startswith(dv):
313 query_match = False
314 elif (not d in hc) or dv != hc[d]:
315 query_match = False
316 break
317
318 if query_match:
319 hc['attachment-points'] = None
320 hc['dpid'] = None
321 hc['ips'] = None
322 hc['last-seen'] = None
323 result.append(hc)
324
325 elif obj_type == 'vns-interface':
326 for entry in entries:
327 vns = entry['parentVNS']['name']
328 rule = entry['parentRule']
329 rule_name = 'default'
330 if rule and 'name' in rule:
331 rule_name = rule['name']
332
333 result.append({ key : vns + '|' + entry['name'],
334 'vns' : vns,
335 'address-space' : entry['parentVNS']['addressSpaceName'],
336 'interface' : entry['name'],
337 'rule' : rule_name,
338 'last-seen' : entry['lastSeen'],
339 })
340
341 # also need to add vns-interface-config
342 if not mi.obj_type_has_model('vns-interface-config'):
343 raise error.CommandInternalError("vns-interface-config: not service via model")
344 vns_intf_config = sdnsh.rest_query_objects('vns-interface-config', data)
345 known_vns_intfs = [x[key] for x in result]
346 for vns_intf in vns_intf_config:
347 if not vns_intf[key] in known_vns_intfs:
348 vns_intf['rule'] = 'default'
349 result.append(vns_intf)
350
351 elif obj_type == 'host-vns-interface':
352
353 # mac matching works for the request
354
355 vns_match = data.get('vns')
356 vns_prefix = data.get('vns__startswith')
357
358 for entry in entries:
359 device = entry['device']
360 if len(device['mac']) > 1:
361 raise error.CommandInternalError("host (vns-interface): >1 mac")
362
363 device_last_seen = device['lastSeen']
364 address_space = device['entityClass']
365 mac = device['mac'][0] # currently, should only be one
366
367 ips = None
368 if len(device['ipv4']):
369 ips = [{'ip-address' : x, 'last-seen' : device_last_seen}
370 for x in device['ipv4'] ]
371
372 vlans = device.get('vlan', []) # currently, should only be one
373 if len(vlans) == 0: # iterate once when list is empty
374 vlans = [ '' ]
375
376 aps = None
377 if len(device['attachmentPoint']):
378 aps = [{'switch' : x['switchDPID'], 'ingress-port' : x['port'] }
379 for x in device['attachmentPoint']]
380
381 for iface in entry.get('iface', []):
382 vns = iface['parentVNS']['name']
383 last_seen = iface['lastSeen']
384 if vns_match and vns_match != vns:
385 continue
386 if vns_prefix and not vns.startswith(vns_prefix):
387 continue
388
389 for vlan in vlans: # there's supposed to only be at most one vlan.
390 if address_space != 'default' or type(vlan) != int:
391 host = '%s||%s' % (address_space, mac)
392 else:
393 host = '%s|%s|%s' % (address_space, vlan, mac)
394
395 result.append({key : mac + '|' + vns + '|' + iface['name'],
396 'host' : host,
397 'mac' : mac,
398 'vlan' : vlan,
399 'address-space' : address_space,
400 'ips' : ips,
401 'attachment-points' : aps,
402 'vns' : vns,
403 'interface' : vns + '|' + iface['name'],
404 'last-seen' : device_last_seen})
405 elif obj_type == 'switches':
406 switch_match = data.get('dpid')
407 switch_prefix = data.get('dpid__startswith')
408
Srikanth Vavilapalli59ddcde2015-03-10 14:40:38 -0700409 if onos == 1:
410 # this synthetic obj_type's name is 'switches' in an attempt
411 # to disabigutate it from 'class Switch'
412 #TODO: Need figure out a better way to get url (Through sdncon framework)
413 url = "http://%s/rest/v1/mastership" % sdnsh.controller
414 try:
415 result2 = sdnsh.store.rest_simple_request(url)
416 check_rest_result(result2)
417 mastership_data = json.loads(result2)
418 except Exception, e:
419 if sdnsh.description: # description debugging
420 print "get_model_from_url: failed request %s: '%s'" % (url, e)
421 entries = []
422 for entry in entries:
423 dpid = entry.get('dpid')
424 if(dpid in mastership_data.keys()):
425 #As there is only one master for switch
426 controller = mastership_data[dpid][0].get('controllerId')
427 else:
428 controller = None
429 if switch_match and switch_match != entry['dpid']:
430 continue
431 if switch_prefix and not entry['dpid'].startswith(switch_prefix):
432 continue
433 if onos == 1:
434 result.append({
435 'dpid' : entry['dpid'],
436 'switch-alias' : entry['stringAttributes']['name'],
437 'connected-since' : entry['stringAttributes']['ConnectedSince'],
438 'ip-address' : entry['stringAttributes']['remoteAddress'],
439 'type' : entry['stringAttributes']['type'],
440 'controller' : controller
441 })
442 else:
443 attrs = entry['attributes']
444 actions = entry['actions']
445 capabilities = entry['capabilities']
446 inet_address = entry.get('inetAddress')
447 ip_address = ''
448 tcp_port = ''
449 if inet_address:
450 # Current Java value looks like: /192.168.2.104:38420
451 inet_parts = inet_address.split(':')
452 ip_address = inet_parts[0][1:]
453 tcp_port = inet_parts[1]
454
455 result.append({
456 'dpid' : entry['dpid'],
457 'connected-since' : entry['connectedSince'],
458 'ip-address' : ip_address,
459 'tcp-port' : tcp_port,
460 'actions' : actions,
461 'capabilities' : capabilities,
462 'dp-desc' : attrs.get('DescriptionData', ''),
463 'fast-wildcards' : attrs.get('FastWildcards', ''),
464 'supports-nx-role' : attrs.get('supportsNxRole', ''),
465 'supports-ofpp-flood' : attrs.get('supportsOfppFlood', ''),
466 'supports-ofpp-table' : attrs.get('supportsOfppTable', ''),
467 'core-switch' : False,
468 })
469 elif onos == 2:
470 for entry in entries.get('devices'):
471 dpid = entry.get('id')
472 #if(dpid in mastership_data.keys()):
473 #As there is only one master for switch
474 # controller = mastership_data[dpid][0].get('controllerId')
475 #else:
476 # controller = None
477 if switch_match and switch_match != entry['id']:
478 continue
479 if switch_prefix and not entry['id'].startswith(switch_prefix):
480 continue
481 switchType = entry['mfr'] + '' + \
482 entry['hw'] + '' + entry['sw'] + '' + \
483 entry['annotations']['protocol']
Srikanth Vavilapalli1725e492014-12-01 17:50:52 -0800484 result.append({
Srikanth Vavilapalli59ddcde2015-03-10 14:40:38 -0700485 'dpid' : entry['id'],
486 'switch-alias' : None,
487 'connected-since' : None,
488 'ip-address' : entry['annotations']['channelId'],
489 'type' : switchType,
490 'controller' : None
Srikanth Vavilapalli1725e492014-12-01 17:50:52 -0800491 })
492 # now add switch-config
493
494 switch_config = sdnsh.rest_query_objects('switch-config', data)
495 known_dpids = dict([[x['dpid'], x] for x in result])
496
497 if onos == 0:
498 for sw in switch_config:
499 dpid = sw['dpid']
500 if not dpid in known_dpids:
501 # be sensitive to search fields:
502 query_match = True
503 if len(data):
504 for (d, dv) in data.items():
505 # other ops aside from '='?
506 if d.endswith(startswith):
507 fn = d[:-len(startswith)]
508 if not fn in sw or not sw[fn].startswith(dv):
509 query_match = False
510 elif (not d in sw) or dv != sw[d]:
511 query_match = False
512 break
513
514 if query_match:
515 sw['ip-address'] = ''
516 sw['tcp-port'] = ''
517 sw['connected-since'] = ''
518 result.append(sw)
519 [dpid].update(sw)
520 elif obj_type == 'interfaces':
521
522 # These are called interfaces because the primary
523 # key is constructed with the interface name, not
524 # the port number.
525
526 # the 'switches' query to sdnplatform currently
527 # doesn't support searching for the interface
528 # names. its done here instead
529
530 switch_match = data.get('dpid')
531 switch_prefix = data.get('dpid__startswith')
532
533 name_match = data.get('portName')
534 if name_match:
535 name_match = name_match.lower()
536 name_prefix = data.get('portName__startswith')
537 if name_prefix:
538 name_prefix = name_prefix.lower()
539
540 # this synthetic obj_type's name is 'switches' in an attempt
541 # to disabigutate it from 'class Switch'
542 for entry in entries:
543 dpid = entry['dpid']
544
545 if switch_match and switch_match != dpid:
546 continue
547 if switch_prefix and not dpid.startswith(switch_prefix):
548 continue
549
550 for p in entry['ports']:
551 portNumber = p['portNumber']
552 if onos == 0:
553 name = p['name']
554 else:
555 name = p['stringAttributes']['name']
556
557 if name_match and name.lower() != name_match:
558 continue
559 if name_prefix and not name.lower().startswith(name_prefix):
560 continue
561 if onos == 0:
562 result.append({
563 'id' : '%s|%s' % (dpid,name),
564 'portNumber' : portNumber,
565 'switch' : dpid,
566 'portName' : p['name'],
567 'config' : p['config'],
568 'state' : p['state'],
569 'advertisedFeatures' : p['advertisedFeatures'],
570 'currentFeatures' : p['currentFeatures'],
571 'hardwareAddress' : p['hardwareAddress'],
572 })
573 else:
574 result.append({
575 'id' : '%s|%s' % (dpid,name),
576 'portNumber' : portNumber,
577 'switch' : dpid,
578 'portName' : name,
579 'config' : 0,
580 'state' : p['state'],
581 'advertisedFeatures' : 0,
582 'currentFeatures' : 0,
583 'hardwareAddress' : 0,
584 })
585
586
587 #
588 # order the result
589 if sort:
590 if sort[0] == '-':
591 sort = sort[1:]
592 # decreasing
593 if sdnsh.description: # description debugging
594 print "get_model_from_url: order decreasing ", sort
595 result = sorted(result, key=lambda k:k.get(sort, ''),
596 cmp=lambda x,y : cmp(y,x))
597 else:
598 # increasing
599 if sdnsh.description: # description debugging
600 print "get_model_from_url: order increasing ", sort
601 result = sorted(result, key=lambda k:k.get(sort, ''),
602 cmp=lambda x,y : cmp(x,y))
603 else:
604 # use tail-integer on the entries
605 if sdnsh.description: # description debugging
606 print "get_model_from_url: pk ordering ", key
607 def sort_cmp(x,y):
608 for (idx, x_v) in enumerate(x):
609 c = cmp(utif.try_int(x_v), utif.try_int(y[idx]))
610 if c != 0:
611 return c
612 return 0
613 result = sorted(result, key=lambda k:k.get(key, '').split('|'),
614 cmp=lambda x,y : cmp(utif.try_int(x),utif.try_int(y)))
615
616 if sdnsh.description: # description debugging
617 print "get_model_from_url: result ", obj_type, url, len(result)
618
619 return result
620
621
622def validate_switch():
623 """
624 If /rest/v1/switches is cached, perform some validations on it.
625
626 -- verify that the announced interfaces names are case insensitive
627 -- verify the names only appear once
628 """
629
630 def duplicate_port(entry, name):
631 dpid = entry['dpid']
632
633 print 'Warning: switch %s duplicate interface names: %s' % (dpid, name)
634 if sdnsh.debug_backtrace:
635 for port in entry['ports']:
636 if port['name'] == name:
637 print 'SWTICH %s:%s PORT %s' % (entry, name, port)
638
639 def not_case_sensitive(entry, name):
640 dpid = entry['dpid']
641
642 ports = {}
643 for port in entry['ports']:
644 if port['name'].lower() == name:
645 ports[port['name']] = port
646
647 print 'Warning: switch %s case insentive interface names: %s' % \
648 (dpid, ' - '.join(ports.keys()))
649 if sdnsh.debug_backtrace:
650 for port in ports:
651 print 'SWTICH %s PORT %s' % (dpid, port)
652
653 url = "http://%s/rest/v1/switches" % sdnsh.controller
654 entries = url_cache.get_cached_url(url)
655 if entries:
656 for entry in entries:
657 dpid = entry['dpid']
658
659 # verify that the port names are unique even when case
660 # sensitive
661 all_names = [p['name'] for p in entry['ports']]
662 one_case_names = utif.unique_list_from_list([x.lower() for x in all_names])
663 if len(all_names) != len(one_case_names):
664 # Something is rotten, find out what.
665 for (i, port_name) in enumerate(all_names):
666 # use enumerate to drive upper-triangle comparison
667 for other_name in all_names[i+1:]:
668 if port_name == other_name:
669 duplicate_port(entry, port_name)
670 elif port_name.lower() == other_name.lower():
671 not_case_sensitive(entry, port_name)
672
673