blob: 7ef25a97eb9ae9ff37682c2ab2792bdf3a537647 [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
409 # this synthetic obj_type's name is 'switches' in an attempt
410 # to disabigutate it from 'class Switch'
411 #TODO: Need figure out a better way to get url (Through sdncon framework)
412 url = "http://%s/rest/v1/mastership" % sdnsh.controller
413 try:
414 result2 = sdnsh.store.rest_simple_request(url)
415 check_rest_result(result2)
416 mastership_data = json.loads(result2)
417 except Exception, e:
418 if sdnsh.description: # description debugging
419 print "get_model_from_url: failed request %s: '%s'" % (url, e)
420 entries = []
421 for entry in entries:
422 dpid = entry.get('dpid')
423 if(dpid in mastership_data.keys()):
424 #As there is only one master for switch
425 controller = mastership_data[dpid][0].get('controllerId')
426 else:
427 controller = None
428 if switch_match and switch_match != entry['dpid']:
429 continue
430 if switch_prefix and not entry['dpid'].startswith(switch_prefix):
431 continue
432 if onos == 1:
433 result.append({
434 'dpid' : entry['dpid'],
435 'switch-alias' : entry['stringAttributes']['name'],
436 'connected-since' : entry['stringAttributes']['ConnectedSince'],
437 'ip-address' : entry['stringAttributes']['remoteAddress'],
438 'type' : entry['stringAttributes']['type'],
439 'controller' : controller
440 })
441 else:
442 attrs = entry['attributes']
443 actions = entry['actions']
444 capabilities = entry['capabilities']
445 inet_address = entry.get('inetAddress')
446 ip_address = ''
447 tcp_port = ''
448 if inet_address:
449 # Current Java value looks like: /192.168.2.104:38420
450 inet_parts = inet_address.split(':')
451 ip_address = inet_parts[0][1:]
452 tcp_port = inet_parts[1]
453
454 result.append({
455 'dpid' : entry['dpid'],
456 'connected-since' : entry['connectedSince'],
457 'ip-address' : ip_address,
458 'tcp-port' : tcp_port,
459 'actions' : actions,
460 'capabilities' : capabilities,
461 'dp-desc' : attrs.get('DescriptionData', ''),
462 'fast-wildcards' : attrs.get('FastWildcards', ''),
463 'supports-nx-role' : attrs.get('supportsNxRole', ''),
464 'supports-ofpp-flood' : attrs.get('supportsOfppFlood', ''),
465 'supports-ofpp-table' : attrs.get('supportsOfppTable', ''),
466 'core-switch' : False,
467 })
468 # now add switch-config
469
470 switch_config = sdnsh.rest_query_objects('switch-config', data)
471 known_dpids = dict([[x['dpid'], x] for x in result])
472
473 if onos == 0:
474 for sw in switch_config:
475 dpid = sw['dpid']
476 if not dpid in known_dpids:
477 # be sensitive to search fields:
478 query_match = True
479 if len(data):
480 for (d, dv) in data.items():
481 # other ops aside from '='?
482 if d.endswith(startswith):
483 fn = d[:-len(startswith)]
484 if not fn in sw or not sw[fn].startswith(dv):
485 query_match = False
486 elif (not d in sw) or dv != sw[d]:
487 query_match = False
488 break
489
490 if query_match:
491 sw['ip-address'] = ''
492 sw['tcp-port'] = ''
493 sw['connected-since'] = ''
494 result.append(sw)
495 [dpid].update(sw)
496 elif obj_type == 'interfaces':
497
498 # These are called interfaces because the primary
499 # key is constructed with the interface name, not
500 # the port number.
501
502 # the 'switches' query to sdnplatform currently
503 # doesn't support searching for the interface
504 # names. its done here instead
505
506 switch_match = data.get('dpid')
507 switch_prefix = data.get('dpid__startswith')
508
509 name_match = data.get('portName')
510 if name_match:
511 name_match = name_match.lower()
512 name_prefix = data.get('portName__startswith')
513 if name_prefix:
514 name_prefix = name_prefix.lower()
515
516 # this synthetic obj_type's name is 'switches' in an attempt
517 # to disabigutate it from 'class Switch'
518 for entry in entries:
519 dpid = entry['dpid']
520
521 if switch_match and switch_match != dpid:
522 continue
523 if switch_prefix and not dpid.startswith(switch_prefix):
524 continue
525
526 for p in entry['ports']:
527 portNumber = p['portNumber']
528 if onos == 0:
529 name = p['name']
530 else:
531 name = p['stringAttributes']['name']
532
533 if name_match and name.lower() != name_match:
534 continue
535 if name_prefix and not name.lower().startswith(name_prefix):
536 continue
537 if onos == 0:
538 result.append({
539 'id' : '%s|%s' % (dpid,name),
540 'portNumber' : portNumber,
541 'switch' : dpid,
542 'portName' : p['name'],
543 'config' : p['config'],
544 'state' : p['state'],
545 'advertisedFeatures' : p['advertisedFeatures'],
546 'currentFeatures' : p['currentFeatures'],
547 'hardwareAddress' : p['hardwareAddress'],
548 })
549 else:
550 result.append({
551 'id' : '%s|%s' % (dpid,name),
552 'portNumber' : portNumber,
553 'switch' : dpid,
554 'portName' : name,
555 'config' : 0,
556 'state' : p['state'],
557 'advertisedFeatures' : 0,
558 'currentFeatures' : 0,
559 'hardwareAddress' : 0,
560 })
561
562
563 #
564 # order the result
565 if sort:
566 if sort[0] == '-':
567 sort = sort[1:]
568 # decreasing
569 if sdnsh.description: # description debugging
570 print "get_model_from_url: order decreasing ", sort
571 result = sorted(result, key=lambda k:k.get(sort, ''),
572 cmp=lambda x,y : cmp(y,x))
573 else:
574 # increasing
575 if sdnsh.description: # description debugging
576 print "get_model_from_url: order increasing ", sort
577 result = sorted(result, key=lambda k:k.get(sort, ''),
578 cmp=lambda x,y : cmp(x,y))
579 else:
580 # use tail-integer on the entries
581 if sdnsh.description: # description debugging
582 print "get_model_from_url: pk ordering ", key
583 def sort_cmp(x,y):
584 for (idx, x_v) in enumerate(x):
585 c = cmp(utif.try_int(x_v), utif.try_int(y[idx]))
586 if c != 0:
587 return c
588 return 0
589 result = sorted(result, key=lambda k:k.get(key, '').split('|'),
590 cmp=lambda x,y : cmp(utif.try_int(x),utif.try_int(y)))
591
592 if sdnsh.description: # description debugging
593 print "get_model_from_url: result ", obj_type, url, len(result)
594
595 return result
596
597
598def validate_switch():
599 """
600 If /rest/v1/switches is cached, perform some validations on it.
601
602 -- verify that the announced interfaces names are case insensitive
603 -- verify the names only appear once
604 """
605
606 def duplicate_port(entry, name):
607 dpid = entry['dpid']
608
609 print 'Warning: switch %s duplicate interface names: %s' % (dpid, name)
610 if sdnsh.debug_backtrace:
611 for port in entry['ports']:
612 if port['name'] == name:
613 print 'SWTICH %s:%s PORT %s' % (entry, name, port)
614
615 def not_case_sensitive(entry, name):
616 dpid = entry['dpid']
617
618 ports = {}
619 for port in entry['ports']:
620 if port['name'].lower() == name:
621 ports[port['name']] = port
622
623 print 'Warning: switch %s case insentive interface names: %s' % \
624 (dpid, ' - '.join(ports.keys()))
625 if sdnsh.debug_backtrace:
626 for port in ports:
627 print 'SWTICH %s PORT %s' % (dpid, port)
628
629 url = "http://%s/rest/v1/switches" % sdnsh.controller
630 entries = url_cache.get_cached_url(url)
631 if entries:
632 for entry in entries:
633 dpid = entry['dpid']
634
635 # verify that the port names are unique even when case
636 # sensitive
637 all_names = [p['name'] for p in entry['ports']]
638 one_case_names = utif.unique_list_from_list([x.lower() for x in all_names])
639 if len(all_names) != len(one_case_names):
640 # Something is rotten, find out what.
641 for (i, port_name) in enumerate(all_names):
642 # use enumerate to drive upper-triangle comparison
643 for other_name in all_names[i+1:]:
644 if port_name == other_name:
645 duplicate_port(entry, port_name)
646 elif port_name.lower() == other_name.lower():
647 not_case_sensitive(entry, port_name)
648
649