blob: debece3a1fa6d03d56437e18cf070150e75df870 [file] [log] [blame]
Srikanth Vavilapalli1725e492014-12-01 17:50:52 -08001#
2# Copyright (c) 2011,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 re
18import numbers
19import collections
20import traceback
21import types
22import json
23import time
24import sys
25import datetime
26import os
27import subprocess
28import socket
29import urllib2 # exception, dump_log()
30
31import modi
32import error
33import command
34import run_config
35import rest_to_model
36import url_cache
37
38from midw import *
39from vnsw import *
40#from html5lib.constants import DataLossWarning
41
42onos=1
43#
44# ACTION PROCS
45#Format actions for stats per table
46def remove_unicodes(actions):
47
48 if actions:
49 #TODO: Check:- Why I have to remove last two character from string
50 #instead of 1 character to get rid of comma from last aciton
51 a=''
52 b=''
53 newActions=''
54 isRemoved_u = False
55 for ch in actions:
56 if ch =='u':
57 a= 'u'
58 if ch =='\'':
59 b= '\''
60 if isRemoved_u:
61 isRemoved_u=False
62 continue
63 if (a+b) == 'u\'':
64 newActions = newActions[:-1]
65 a= ''
66 isRemoved_u = True
67 else:
68 newActions += ch
69 return newActions
70 else:
71 ''
72def renameActions(actions):
73
74 actions = actions.replace('GOTO_TABLE','GOTO')
75 actions = actions.replace('WRITE_ACTIONS','WRITE')
76 actions = actions.replace('APPLY_ACTIONS','APPLY')
77 actions = actions.replace('DEC_NW_TTL: True','DEC_NW_TTL')
78 actions = actions.replace('POP_MPLS: True','POP_MPLS')
79 actions = actions.replace('COPY_TTL_IN: True','COPY_TTL_IN')
80 actions = actions.replace('COPY_TTL_OUT: True','COPY_TTL_OUT')
81 actions = actions.replace('DEC_MPLS_TTL: True','DEC_MPLS_TTL')
82 actions = actions.replace('SET_DL_SRC','SRC_MAC')
83 actions = actions.replace('SET_DL_DST','DST_MAC')
84 actions = actions.replace('SET_NW_SRC','SRC_IP')
85 actions = actions.replace('SET_NW_DST','DST_IP')
86 actions = actions.replace('CLEAR_ACTIONS: {CLEAR_ACTIONS: True}','CLEAR_ACTIONS')
87
88 return actions
89
90def check_rest_result(result, message=None):
91 if isinstance(result, collections.Mapping):
92 error_type = result.get('error_type')
93 if error_type:
94 raise error.CommandRestError(result, message)
95
Srikanth Vavilapallib5c3ca52014-12-15 15:59:33 -080096tunnelset_id=None
97tunnelset_dict=[]
Srikanth Vavilapalli40d79f82014-12-17 14:29:24 -080098tunnelset_remove_tunnels=[]
Srikanth Vavilapallib5c3ca52014-12-15 15:59:33 -080099def tunnelset_create(data=None):
Srikanth Vavilapalli40d79f82014-12-17 14:29:24 -0800100 global tunnelset_id,tunnelset_dict,tunnelset_remove_tunnels
Srikanth Vavilapallib5c3ca52014-12-15 15:59:33 -0800101 if sdnsh.description: # description debugging
102 print "tunnelset_create:" , data
103 if data.has_key('tunnelset-id'):
104 if (tunnelset_id != None):
105 if sdnsh.description: # description debugging
106 print "tunnelset_create: previous data is not cleaned up"
107 tunnelset_id=None
108 tunnelset_dict=[]
Srikanth Vavilapalli40d79f82014-12-17 14:29:24 -0800109 tunnelset_remove_dict=[]
Srikanth Vavilapallib5c3ca52014-12-15 15:59:33 -0800110 tunnelset_id=data['tunnelset-id']
111 tunnelset_dict=[]
Srikanth Vavilapalli40d79f82014-12-17 14:29:24 -0800112 tunnelset_remove_dict=[]
Srikanth Vavilapallib5c3ca52014-12-15 15:59:33 -0800113 if sdnsh.description: # description debugging
114 print "tunnelset_create:" , tunnelset_id
115
116def tunnelset_config_exit():
Srikanth Vavilapalli40d79f82014-12-17 14:29:24 -0800117 global tunnelset_id,tunnelset_dict,tunnelset_remove_tunnels
Srikanth Vavilapallib5c3ca52014-12-15 15:59:33 -0800118 if sdnsh.description: # description debugging
119 print "tunnelset_config_exit entered", tunnelset_dict
Srikanth Vavilapalli40d79f82014-12-17 14:29:24 -0800120 if (len(tunnelset_dict) > 0) or (len(tunnelset_remove_tunnels)>0):
Srikanth Vavilapallib5c3ca52014-12-15 15:59:33 -0800121 url_str = ""
122 entries = tunnelset_dict
123 url_str = "http://%s/rest/v1/tunnelset/" % (sdnsh.controller)
124 obj_data = {}
125 obj_data['tunnelset_id']=tunnelset_id
Srikanth Vavilapalli40d79f82014-12-17 14:29:24 -0800126 if (len(entries) > 0):
127 obj_data['tunnel_params']=entries
128 if (len(tunnelset_remove_tunnels) > 0):
129 obj_data['remove_tunnel_params']=tunnelset_remove_tunnels
Srikanth Vavilapallib5c3ca52014-12-15 15:59:33 -0800130 result = "fail"
131 try:
132 result = sdnsh.store.rest_post_request(url_str,obj_data)
133 except Exception, e:
134 errors = sdnsh.rest_error_to_dict(e)
135 print sdnsh.rest_error_dict_to_message(errors)
136 # LOOK! successful stuff should be returned in json too.
137 tunnelset_dict = []
Srikanth Vavilapalli40d79f82014-12-17 14:29:24 -0800138 tunnelset_remove_tunnels = []
Srikanth Vavilapallib5c3ca52014-12-15 15:59:33 -0800139 tunnelset_id = None
140 curr_tunnel_id = None
141 if result != "success":
142 print "command failed"
143 else:
144 print "empty command"
145 #Clear the transit information
146
147def tunnelset_remove(data=None):
148 if sdnsh.description: # description debugging
149 print "tunnelset_remove:" , data
150 tunnelset_id=data['tunnelset-id']
Srikanth Vavilapalli40d79f82014-12-17 14:29:24 -0800151 url_str = "http://%s/rest/v1/tunnelset/" % (sdnsh.controller)
Srikanth Vavilapallib5c3ca52014-12-15 15:59:33 -0800152 obj_data = {}
153 obj_data['tunnelset_id']=data['tunnelset-id']
154 result = "fail"
155 try:
156 result = sdnsh.store.rest_post_request(url_str,obj_data,'DELETE')
157 except Exception, e:
158 errors = sdnsh.rest_error_to_dict(e)
159 print sdnsh.rest_error_dict_to_message(errors)
160 if not result.startswith("SUCCESS"):
161 print result
162
Srikanth Vavilapalli1725e492014-12-01 17:50:52 -0800163tunnel_id=None
164tunnel_dict={}
165def tunnel_create(data=None):
166 global tunnel_id,tunnel_dict
167 if sdnsh.description: # description debugging
168 print "tunnel_create:" , data
169 if data.has_key('tunnel-id'):
170 if (tunnel_id != None):
171 if sdnsh.description: # description debugging
172 print "tunnel_create: previous data is not cleaned up"
173 tunnel_id=None
174 tunnel_dict={}
175 tunnel_id=data['tunnel-id']
176 tunnel_dict[tunnel_id]=[]
177 if data.has_key('node-label'):
178 tunnel_dict[tunnel_id].append(data['node-label'])
179 if data.has_key('adjacency-label'):
180 tunnel_dict[tunnel_id].append(data['adjacency-label'])
181 if sdnsh.description: # description debugging
182 print "tunnel_create:" , tunnel_id, tunnel_dict
183
184def tunnel_config_exit():
Srikanth Vavilapalli40d79f82014-12-17 14:29:24 -0800185 global tunnel_id,tunnel_dict,tunnelset_dict
Srikanth Vavilapalli1725e492014-12-01 17:50:52 -0800186 if sdnsh.description: # description debugging
187 print "tunnel_config_exit entered", tunnel_dict
Srikanth Vavilapallib5c3ca52014-12-15 15:59:33 -0800188
189 entries = tunnel_dict[tunnel_id]
190 obj_data = {}
191 obj_data['tunnel_id']=tunnel_id
192 obj_data['label_path']=entries
193 if tunnelset_id:
194 tunnelset_dict.append(obj_data)
195 tunnel_dict = {}
196 tunnel_id = None
197 elif tunnel_dict:
Srikanth Vavilapalli1725e492014-12-01 17:50:52 -0800198 url_str = ""
Srikanth Vavilapalli1725e492014-12-01 17:50:52 -0800199 url_str = "http://%s/rest/v1/tunnel/" % (sdnsh.controller)
Srikanth Vavilapalli1725e492014-12-01 17:50:52 -0800200 result = "fail"
201 try:
202 result = sdnsh.store.rest_post_request(url_str,obj_data)
203 except Exception, e:
204 errors = sdnsh.rest_error_to_dict(e)
205 print sdnsh.rest_error_dict_to_message(errors)
206 # LOOK! successful stuff should be returned in json too.
207 tunnel_dict = {}
208 tunnel_id = None
209 if result != "success":
210 print "command failed"
211 else:
212 print "empty command"
213 #Clear the transit information
214
215def tunnel_remove(data=None):
Srikanth Vavilapalli40d79f82014-12-17 14:29:24 -0800216 global tunnelset_remove_tunnels
Srikanth Vavilapalli1725e492014-12-01 17:50:52 -0800217 if sdnsh.description: # description debugging
218 print "tunnel_remove:" , data
219 tunnel_id=data['tunnel-id']
Srikanth Vavilapalli40d79f82014-12-17 14:29:24 -0800220 if tunnelset_id:
221 tunnelset_remove_tunnels.append(tunnel_id)
222 else:
223 url_str = "http://%s/rest/v1/tunnel/" % (sdnsh.controller)
224 obj_data = {}
225 obj_data['tunnel_id']=data['tunnel-id']
226 result = "fail"
227 try:
228 result = sdnsh.store.rest_post_request(url_str,obj_data,'DELETE')
229 except Exception, e:
230 errors = sdnsh.rest_error_to_dict(e)
231 print sdnsh.rest_error_dict_to_message(errors)
232 if not result.startswith("SUCCESS"):
233 print result
Srikanth Vavilapalli1725e492014-12-01 17:50:52 -0800234
Srikanth Vavilapallib5c3ca52014-12-15 15:59:33 -0800235
Srikanth Vavilapalli1725e492014-12-01 17:50:52 -0800236policy_obj_data = {}
237def policy_create(data=None):
238 global policy_obj_data
239 if sdnsh.description: # description debugging
240 print "policy_create:" , data
241 if data.has_key('policy-id'):
242 if policy_obj_data:
243 if sdnsh.description: # description debugging
244 print "policy_create: previous data is not cleaned up"
245 policy_obj_data = {}
246 policy_obj_data['policy_id'] = data['policy-id']
247 policy_obj_data['policy_type'] = data['policy-type']
248 if data.has_key('src_ip'):
249 for key in data:
250 policy_obj_data[key] = data[key]
251 if data.has_key('priority'):
252 policy_obj_data['priority'] = data['priority']
253 if data.has_key('tunnel-id'):
Srikanth Vavilapallib5c3ca52014-12-15 15:59:33 -0800254 if policy_obj_data.has_key('tunnelset_id'):
255 print "ERROR: Policy can not point to both tunnelset and tunnel"
256 return
Srikanth Vavilapalli1725e492014-12-01 17:50:52 -0800257 policy_obj_data['tunnel_id'] = data['tunnel-id']
Srikanth Vavilapallib5c3ca52014-12-15 15:59:33 -0800258 if data.has_key('tunnelset-id'):
259 if policy_obj_data.has_key('tunnel_id'):
260 print "ERROR: Policy can not point to both tunnelset and tunnel"
261 return
262 policy_obj_data['tunnelset_id'] = data['tunnelset-id']
Srikanth Vavilapalli1725e492014-12-01 17:50:52 -0800263
264 if sdnsh.description: # description debugging
265 print policy_obj_data
266
267def policy_config_exit():
268 global policy_obj_data
269 if sdnsh.description: # description debugging
270 print "policy_config_exit entered", policy_obj_data
271 if policy_obj_data:
272 url_str = "http://%s/rest/v1/policy/" % (sdnsh.controller)
273 result = "fail"
274 try:
275 result = sdnsh.store.rest_post_request(url_str,policy_obj_data)
276 except Exception, e:
277 errors = sdnsh.rest_error_to_dict(e)
278 print sdnsh.rest_error_dict_to_message(errors)
279 if result != "success":
280 print "command failed"
281 policy_obj_data = {}
282 else:
283 print "empty command"
284 #Clear the transit information
285
286def policy_remove(data=None):
287 if sdnsh.description: # description debugging
288 print "policy_remove:" , data
289 policy_id=data['policy-id']
290 url_str = "http://%s/rest/v1/policy/" % (sdnsh.controller)
291 obj_data = {}
292 obj_data['policy_id']=data['policy-id']
293 result = "fail"
294 try:
295 result = sdnsh.store.rest_post_request(url_str,obj_data,'DELETE')
296 except Exception, e:
297 errors = sdnsh.rest_error_to_dict(e)
298 print sdnsh.rest_error_dict_to_message(errors)
299 if result != "deleted":
300 print "command failed"
301
302
303
304def write_fields(obj_type, obj_id, data):
305 """
306 Typical action to update fields of a row in the model
307
308 @param obj_type a string, the name of the db table to update
309 @param obj_id a string, the value of the primary key in for the table
310 @param data a dict, the name:value pairs of data to update in the table
311 """
312 if sdnsh.description: # description debugging
313 print "write_fields:", obj_type, obj_id, data
314
315 pk_name = mi.pk(obj_type)
316 if not pk_name:
317 raise error.CommandDescriptionError("Can't find primary key name for type: %s" % obj_type)
318 if sdnsh.description: # description debugging
319 print "write_fields:", obj_type, pk_name, obj_id, data
320 for fk in mi.obj_type_foreign_keys(obj_type):
321 if fk in data and mi.is_null_allowed(obj_type, fk):
322 if data[fk] == 'default': # XXX much too magic, seems an option here would be good
323 data[fk] = None
324
325 result = sdnsh.rest_update_object(obj_type, pk_name, obj_id, data)
326 check_rest_result(result)
327
328
329def verify_row_includes(obj_type, pk_value, data, verify):
330 """
331 Intended to raise an exception when a user enters 'no field value',
332 and the field isn't currently set to value, for example:
333 'address-space as1 ; no address-space as2', should complain
334 that the 'address-space' field isn't currently set to 'as2'.
335
336 @param obj_type a string, identifies the db table
337 @param pk_value a string, identifies the value for the primary key
338 @param data is a dict, collecting the name:value pairs from the description
339 @verify the string or list of field names to be verified
340 """
341 if sdnsh.description: # description debugging
342 print "validate_row_includes:", obj_type, pk_value, data, verify
343
344 if type(verify) == str:
345 verify = [verify] # if not a list, make it a list
346
347 try:
348 row = sdnsh.get_object_from_store(obj_type, pk_value)
349 except Exception, e:
350 if sdnsh.debug or sdnsh.debug_backtrace:
351 print 'Failed lookup of %s:%s:%s', (obj_type, pk_value, e)
352 traceback.print_exc()
353 raise error.ArgumentValidationError("%s: '%s' doesn't exist" %
354 (obj_type, pk_value))
355 return
356
357 if sdnsh.description: # description debugging
358 print "validate_includes: ", row
359 for field in [x for x in verify if x in data and x in row]:
360 if row[field] != data[field]:
361 raise error.ArgumentValidationError("%s: %s found '%s' current value '%s'" %
362 (obj_type, field, data[field], row[field]))
363
364
365def reset_fields(obj_type, arg_data,
366 obj_id = None, fields = None, match_for_no = None):
367 """
368 For an obj_type, revert fields back to their default value.
369 This is the typical action for 'no' commands.
370
371 When verify is set, this is a string or list of fields who's values
372 must match in the table for the primary key associated with the reset.
373 This allows command descriptions to identify any fields which need to
374 be checked against, when they are explicidly named in the 'no' command,
375 so that 'no XXX value' will verify that 'value' matches the current
376 row's value before allowing the reset to continue
377
378 @param obj_type a string, identifies the db table
379 @param obj_id a string, identifies the value for the primary key of the row in the table,
380 possibly unset, the key is looked for in the arg_data in that case.
381 @param arg_data a dict, collection of name:value pairs from the description
382 @param fields a list, collection of fields to update in the table
383 @param match_for_no a string or list, list of fields to check for matched values in arg_data
384 """
385
386 if obj_type == None:
387 raise error.CommandDescriptionError("No object to reset (missing obj-type)")
388
389 pk_name = mi.pk(obj_type)
390 # If the fields aren't specified explicitly, then derive from the arg_data
391 if fields is None:
392 fields = []
393 for field in arg_data.keys():
394 # Only add arguments that correspond to valid fields in the object
395 if mi.obj_type_has_field(obj_type, field):
396 if field != pk_name: # don't reset primary keys
397 fields.append(field)
398
399 if len(fields) == 0:
400 raise error.CommandDescriptionError("No fields to reset: type: %s" % obj_type)
401
402 # Get the primary key name
403 if not pk_name:
404 raise error.CommandDescriptionError("Can't find primary key name for type: %s" % obj_type)
405 if obj_id == None:
406 if pk_name in arg_data:
407 obj_id = arg_data[pk_name]
408 elif mi.field_default_value(obj_type, pk_name):
409 # unusual, but not impossible for singletons
410 obj_id = mi.field_default_value(obj_type, pk_name)
411 else:
412 raise error.CommandDescriptionError("Can't find id value name for type: %s"
413 " field %s" % (obj_type, pk_name))
414
415 if match_for_no:
416 verify_row_includes(obj_type, obj_id, arg_data, match_for_no)
417
418 # Get the default values of the specified field from CLI model info
419 data = {}
420 for field in fields:
421 if field == pk_name:
422 continue
423 type_info = mi.cli_model_info.get_field_info(obj_type, field)
424 if type_info == None:
425 raise error.CommandDescriptionError("Can't find field details for "
426 "field %s in type %s" % (field, obj_type))
427 data[field] = type_info.get('default')
428 if data[field] == None and type_info.get('type') == 'BooleanField':
429 data[field] = False
430 # why does boolean not respect the default in the model?!?
431 # data[field] = type_info.get('default') if type_info.get('type') != 'BooleanField' else False
432
433 if sdnsh.description: # description debugging
434 print "reset_fields:", obj_type, pk_name, obj_id, data, match_for_no
435
436 # Invoke the REST API to set the default values
437 try:
438 result = sdnsh.rest_update_object(obj_type, pk_name, obj_id, data)
439 except Exception, e:
440 errors = sdnsh.rest_error_to_dict(e, obj_type)
441 raise error.CommandError('REST', sdnsh.rest_error_dict_to_message(errors))
442
443
444def obj_type_fields_have_default_value(obj_type, row, data):
445 """
446 Return True when all the fields have a default value,
447 row is the queried data from the store,
448 data is the data to be updated.
449
450 The goal is to determine whether to delete or update
451 the row in the store.
452
453 """
454
455 ckf = []
456 if mi.is_compound_key(obj_type, mi.pk(obj_type)):
457 # XXX primitive compound keys' too?
458 ckf = mi.compound_key_fields(obj_type, mi.pk(obj_type))
459
460 for field in mi.obj_type_fields(obj_type):
461 if mi.is_primary_key(obj_type, field):
462 continue
463 if mi.is_foreign_key(obj_type, field):
464 # perhaps only allow a single foreign key?
465 continue
466 # also any fields which are used to compound the ident.
467 if field in ckf:
468 continue
469 # Needs a better way to identify non-model-fields
470 if field == 'Idx':
471 continue
472 if mi.is_null_allowed(obj_type, field):
473 # does this need to be more complex?
474 if field in data and data[field] != None:
475 return False
476 continue # next field
477 default_value = mi.field_default_value(obj_type, field)
478 if default_value == None:
479 if sdnsh.description: # description debugging
480 print 'default_value: no default: %s %s' % (obj_type, field)
481 return False
482 # check to see if the updated value would be the default
483 if field in data and data[field] != default_value:
484 if sdnsh.description: # description debugging
485 print 'default_value: not default %s %s %s' % \
486 (field, data[field], default_value)
487 return False
488 elif row.get(field, default_value) != default_value:
489 if field in data and data[field] == default_value:
490 if sdnsh.description: # description debugging
491 print 'default_value: db not default %s %s %s' \
492 ' new value in data %s is default' % \
493 (field, row[field], default_value, data[field])
494 continue
495 if sdnsh.description: # description debugging
496 print 'default_value: db not default %s %s %s' % \
497 (field, row[field], default_value)
498 return False
499 return True
500
501
502def update_config(obj_type, obj_id, data, no_command):
503 """
504 update_config is intended to write a row when the described data
505 is different from the default values of the fields of the row.
506
507 When the data described in the call updates the field's values
508 to all default values, the row associated with the obj_id is
509 deleted.
510
511 This is intended to be used for models which contain configuration
512 row data, and that every field has a default value,
513 so that when the config data is transitioned to the default
514 state, the row is intended to be removed. For these sorts of
515 command descriptions, updating a field to some default value
516 may result in the row getting deleted.
517 """
518
519 c_data = dict(data) # make a local copy
520 if sdnsh.description: # description debugging
521 print "update_config: ", obj_type, obj_id, c_data, no_command
522
523 if not mi.obj_type_exists(obj_type):
524 raise error.CommandDescriptionError("Unknown obj-type: %s" % obj_type)
525
526 # collect any dict.key names which aren't fields in the object
527 for unknown_field in [x for x in c_data.keys() if not mi.obj_type_has_field(obj_type, x)]:
528 del c_data[unknown_field]
529
530 # if its a no command, set the value to 'None' if it's allowed,
531 # of to its default value otherwise
532 if no_command:
533 for field in c_data.keys():
534 if mi.is_null_allowed(obj_type, field):
535 c_data[field] = None
536 else:
537 # required to have a default value
538 c_data[field] = mi.field_default_value(obj_type, field)
539
540 # Get the primary key name
541 pk_name = mi.pk(obj_type)
542 if not pk_name:
543 raise error.CommandDescriptionError("Can't find primary key name for type: %s" % obj_type)
544 pk_value = obj_id
545 if pk_name in data:
546 pk_value = data[pk_name]
547 if pk_name in c_data:
548 del c_data[pk_name]
549
550 # Query for the row, if it doesn't exist, create the item if any item isn't default
551 if sdnsh.description: # description debugging
552 print "update_config: query:", obj_type, pk_value
553
554 result = sdnsh.rest_query_objects(obj_type, { pk_name : pk_value })
555 check_rest_result(result)
556 if len(result) == 0:
557 # result[0] -> dictionary of field:value pairs
558 # check to ensure c_data isn't just default row values
559 if not obj_type_fields_have_default_value(obj_type, {}, c_data):
560 if sdnsh.description: # description debugging
561 print "update_config: create:", obj_type, c_data
562 # populate the create dictionary
563 create_dict = dict(c_data)
564 create_dict[pk_name] = pk_value
565 result = sdnsh.rest_create_object(obj_type, create_dict)
566 check_rest_result(result)
567 else:
568 if sdnsh.description: # description debugging
569 print "update_config: no current row"
570 return
571 else:
572 if sdnsh.description: # description debugging
573 print "update_config: found row", result[0]
574
575 if len(result) > 1:
576 raise error.CommandInternalError("Multiple rows for obj-type: %s: pk %s" %
577 (obj_type, pk_value))
578
579 # See if the complete row needs to be deleted.
580 # For each of the current fields, if a field's default doesn't exist,
581 # skip the row delete, or if any field has a non-default value, update
582 # the requested fields instead of deleting the row.
583 if obj_type_fields_have_default_value(obj_type, result[0], c_data):
584 # if the table has foreign keys, check no children refer to this table.
585 no_foreign_keys_active = True
586 if obj_type in mi.foreign_key_xref:
587 for (fk_obj_type, fk_fn) in mi.foreign_key_xref[obj_type][mi.pk(obj_type)]:
588 try:
589 rows = sdnsh.get_table_from_store(fk_obj_type, fk_fn,
590 pk_value, "exact")
591 except Exception, e:
592 rows = []
593 if len(rows):
594 if sdnsh.description: # description debugging
595 print "update_config: foreign key active:", \
596 fk_obj_type, fk_fn, pk_value
597 no_foreign_keys_active = False
598 break
599
600 if no_foreign_keys_active:
601 if sdnsh.description: # description debugging
602 print "update_config: delete:", obj_type, pk_value
603 try:
604 delete_result = sdnsh.rest_delete_objects(obj_type, { pk_name : pk_value })
605 check_rest_result(delete_result)
606 except Exception, e:
607 errors = sdnsh.rest_error_to_dict(e)
608 raise error.CommandInvocationError(sdnsh.rest_error_dict_to_message(errors))
609 return
610 # XXX if a row from some table is removed, and that table is using
611 # foreign keys, then the table which is refered to ought to be
612 # reviewed, to see if all the entries of the row which this table
613 # refer's to are default, and if that parent table is a config-style
614 # table, with all default values for every field, there's a good
615 # argument that the row ought to be removed.
616
617 # See if any of the c_data items in the matching row are different
618 # (ie: is this update really necessary?)
619 update_necessary = False
620 for (name, value) in c_data.items():
621 if name in result[0]:
622 if value != result[0][name]:
623 update_necessary = True
624 if sdnsh.description: # description debugging
625 print "update_config: update necessary:", name, result[0][name], value
626 else:
627 update_necessary = True
628
629 if not update_necessary:
630 if sdnsh.description: # description debugging
631 print "update_config: no update needed", obj_type, pk_name, pk_value
632 return
633
634 if sdnsh.description: # description debugging
635 print "update_config: update:", obj_type, pk_name, pk_value, c_data
636 # Invoke the REST API to set the default values
637 result = sdnsh.rest_update_object(obj_type, pk_name, pk_value, c_data)
638 check_rest_result(result)
639
640
641def delete_objects(obj_type, data, parent_field=None, parent_id=None):
642 """
643 Delete a row in the table.
644
645 @param obj_type a string, the name of the table to update
646 @param data a dictionary, name:value pairs to describe the delete
647 @param parent_field a string, the name of a field in the obj_type,
648 identifying a relationship between this table, and another table
649 @param parent_id a string, the value of the parent_field, to identify
650 another row in the other table identified by a field in this table
651 """
652
653 pk_name = mi.pk(obj_type)
654 if not pk_name:
655 raise error.CommandDescriptionError("Can't find primary key name for type: %s" % obj_type)
656
657 query_data = dict(data)
658 if parent_field:
659 query_data[parent_field] = parent_id
660
661 # case conversion
662 for field in data:
663 if mi.obj_type_has_field(obj_type, field):
664 case = mi.get_obj_type_field_case_sensitive(obj_type, field)
665 if case:
666 if sdnsh.description: # description debugging
667 print 'delete_objects: case convert %s:%s to %s' % \
668 (obj_type, field, case)
669 data[field] = utif.convert_case(case, data[field])
670
671 query_result = sdnsh.rest_query_objects(obj_type, query_data)
672 check_rest_result(query_result)
673 #
674 # if there were no results, try to delete by removing any
675 # items which have "None" values
676 if len(query_result) == 0:
677 for key in query_data.keys():
678 if query_data[key] == None:
679 del query_data[key]
680 query_result = sdnsh.rest_query_objects(obj_type, query_data)
681 check_rest_result(query_result)
682
683 if sdnsh.description: # description debugging
684 print "delete_objects:", obj_type, query_data
685 delete_result = sdnsh.rest_delete_objects(obj_type, query_data)
686 check_rest_result(delete_result)
687
688 for item in query_result:
689 key = item[pk_name]
690 sdnsh.cascade_delete(obj_type, key)
691
692
693def set_data(data, key, value):
694 """
695 Action to associate a new name:value pair with 'data', the dictionary used
696 to pass to REST API's. Allows the action to describe a value for a field
697 which wasn't directly named in the description.
698
699 """
700 if sdnsh.description: # description debugging
701 print "set_data:", data, key, value
702 data[key] = value
703
704
705def write_object(obj_type, data, parent_field=None, parent_id=None):
706 """
707 Write a new row into a specific table.
708
709 """
710 # If we're pushing a config submode with an object, then we need to extend the
711 # argument data that was entered explicitly in the command with the information
712 # about the parent object (by default obtained by looking at the obj info on
713 # the mode stack -- see default arguments for this action when it is added).
714
715 if sdnsh.description: # description debugging
716 print 'write_object: params ', obj_type, data, parent_field, parent_id
717 data = dict(data) # data is overwriten in various situations below
718 if parent_field:
719 if not parent_id:
720 raise error.CommandDescriptionError('Invalid command description;'
721 'improperly configured parent info for create-object')
722 data[parent_field] = parent_id
723
724 pk_name = mi.pk(obj_type)
725 if not pk_name:
726 raise error.CommandDescriptionError("Can't find primary key name for type: %s" % obj_type)
727
728 # look for unpopulated foreign keys.
729 fks = mi.obj_type_foreign_keys(obj_type)
730 if fks:
731 for fk in fks:
732 (fk_obj, fk_nm) = mi.foreign_key_references(obj_type, fk)
733
734 if not fk in data or \
735 (mi.is_compound_key(fk_obj, fk_nm) and data[fk].find('|') == -1):
736 # use various techniques to populate the foreign key
737 # - if the foreign key is for class which has a compound key, see if all the
738 # parts of the compound key are present
739
740 if mi.is_compound_key(fk_obj, fk_nm):
741 kfs = mi.deep_compound_key_fields(fk_obj, fk_nm)
742 missing = [x for x in kfs if not x in data]
743 if len(missing) == 0:
744 # remove the entries, build the compound key for the foreign key reference
745 new_value = mi.compound_key_separator(fk_obj, fk_nm).\
746 join([data[x] for x in kfs])
747 # verify the foreign key exists, if not complain and return,
748 # preventing a error during the create request
749 query_result = sdnsh.rest_query_objects( fk_obj, { fk_nm : new_value })
750 check_rest_result(query_result)
751 if len(query_result) == 0:
752 joinable_name = ["%s: %s" % (x, data[x]) for x in kfs]
753 raise error.CommandSemanticError("Reference to non-existant object: %s " %
754 ', '.join(joinable_name))
755 for rfn in kfs: # remove field name
756 del data[rfn]
757 data[fk] = new_value
758 else:
759 qr = sdnsh.rest_query_objects(fk_obj, data)
760 if len(qr) == 1:
761 data[fk] = qr[0][mi.pk(fk_obj)]
762
763 if pk_name in data:
764 if sdnsh.description: # description debugging
765 print command._line(), 'write_object: query pk_name ', obj_type, pk_name, data
766 case = mi.get_obj_type_field_case_sensitive(obj_type, pk_name)
767 if case:
768 data[pk_name] = utif.convert_case(case, data[pk_name])
769 query_result = sdnsh.rest_query_objects(obj_type, { pk_name : data[pk_name]})
770 else:
771 query_data = dict([[n,v] for (n,v) in data.items() if v != None])
772 if sdnsh.description: # description debugging
773 print command._line(), 'write_object: query ', obj_type, query_data
774 query_result = sdnsh.rest_query_objects(obj_type, query_data)
775 check_rest_result(query_result)
776
777 # Consider checking to see if all the fields listed here
778 # already match a queried result, if so, no write is needed
779
780 if (len(query_result) > 0) and (pk_name in data):
781 if sdnsh.description: # description debugging
782 print "write_object: update object", obj_type, pk_name, data
783 result = sdnsh.rest_update_object(obj_type, pk_name, data[pk_name], data)
784 else:
785 if sdnsh.description: # description debugging
786 print "write_object: create_object", obj_type, data
787 result = sdnsh.rest_create_object(obj_type, data)
788
789 check_rest_result(result)
790
791 for item in query_result:
792 key = item[pk_name]
793 sdnsh.cascade_delete(obj_type, key)
794
795
796def delete_object(obj_type, data, parent_field=None, parent_id=None):
797 global sdnsh
798
799 data = dict(data)
800 if parent_field:
801 if not parent_id:
802 raise error.CommandDescriptionError('Invalid command description;'
803 'improperly configured parent info for delete-object')
804 data[parent_field] = parent_id
805
806 # case conversion
807 for field in data:
808 if mi.obj_type_has_field(obj_type, field):
809 case = mi.get_obj_type_field_case_sensitive(obj_type, field)
810 if case:
811 if sdnsh.description: # description debugging
812 print 'delete_object: case convert %s:%s to %s' % \
813 (obj_type, field, case)
814 data[field] = utif.convert_case(case, data[field])
815
816 if sdnsh.description: # description debugging
817 print "delete_object: ", obj_type, data
818 result = sdnsh.rest_delete_objects(obj_type, data)
819 check_rest_result(result)
820
821
822def push_mode_stack(mode_name, obj_type, data, parent_field = None, parent_id = None, create=True):
823 """
824 Push a submode on the config stack.
825 """
826 global sdnsh, modi
827
828 # Some few minor validations: enable only in login, config only in enable,
829 # and additional config modes must also have the same prefix as the
830 # current mode.
831 current_mode = sdnsh.current_mode()
Srikanth Vavilapallib5c3ca52014-12-15 15:59:33 -0800832 if (mode_name == 'config-tunnel'):
833 if (current_mode == 'config-tunnelset'):
834 mode_name = 'config-tunnelset-tunnel'
Srikanth Vavilapalli40d79f82014-12-17 14:29:24 -0800835 if (mode_name == 'config-policy'):
836 if (data.has_key('policy-type')):
837 if (data['policy-type'] == 'tunnel-flow'):
838 mode_name = 'config-policy-tunnel'
839 if (data['policy-type'] == 'loadbalance'):
840 mode_name = 'config-policy-loadbalance'
841 if (data['policy-type'] == 'avoid'):
842 mode_name = 'config-policy-avoid'
843 if sdnsh.description: # description debugging
844 print "Changing config-policy sub mode to ", mode_name
Srikanth Vavilapalli1725e492014-12-01 17:50:52 -0800845
846 if sdnsh.description: # description debugging
847 print "push_mode: ", mode_name, obj_type, data, parent_field, parent_id
848
849 # See if this is a nested submode, or whether some current modes
850 # need to be popped.
851 if (mode_name.startswith('config-') and
852 (not mode_name.startswith(current_mode) or (mode_name == current_mode))):
853
854 sdnsh.pop_mode()
855 current_mode = sdnsh.current_mode()
856 # pop until it it matches
857 while not mode_name.startswith(current_mode):
858 if len(sdnsh.mode_stack) == 0:
859 raise error.CommandSemanticError('%s not valid within %s mode' %
860 (mode_name, current_mode))
861 sdnsh.pop_mode()
862 current_mode = sdnsh.current_mode()
863
864 # if there's a parent id, it is typically the parent, and audit
865 # ought to be done to verify this
866 if parent_field:
867 data = dict(data)
868 data[parent_field] = sdnsh.get_current_mode_obj()
869
870 elif mode_name in ['config', 'enable', 'login']:
871 # see if the mode is in the stack
872 if mode_name in [x['mode_name'] for x in sdnsh.mode_stack]:
873 if sdnsh.description: # description debugging
874 print 'push_mode: popping stack for', mode_name
875 current_mode = sdnsh.current_mode()
876 while current_mode != mode_name:
877 sdnsh.pop_mode()
878 current_mode = sdnsh.current_mode()
879 return
880
881
882 # If we're pushing a config submode with an object, then we need to extend the
883 # argument data that was entered explicitly in the command with the information
884 # about the parent object (by default obtained by looking at the obj info on
885 # the mode stack -- see default arguments for this action when it is added).
886 elif parent_field:
887 if not parent_id:
888 raise error.CommandDescriptionError('Invalid command description; '
889 'improperly configured parent info for push-mode-stack')
890 data = dict(data)
891 data[parent_field] = parent_id
892
893 key = None
894 if obj_type:
895 for field in data:
896 if mi.obj_type_has_field(obj_type, field):
897 case = mi.get_obj_type_field_case_sensitive(obj_type, field)
898 if case:
899 if sdnsh.description: # description debugging
900 print 'push_mode: case convert %s:%s to %s' % \
901 (obj_type, field, case)
902 data[field] = utif.convert_case(case, data[field])
903
904
905 # Query for the object both to see if it exists and also to determine
906 # the pk value we're going to push on the stack. We need to do
907 # the query in the case where the model uses compound keys and we're
908 # specifying the individual fields that compose the compound key.
909 result = sdnsh.rest_query_objects(obj_type, data)
910 check_rest_result(result)
911 if len(result) == 0 and create:
912 #
913 # For vns-interface, the association of 'rule' with the data dict
914 # is difficult to explain via the command description. This is
915 # obviously a poor method of dealing with the issue, but until
916 # a better one arises (possibly REST api create? possibly
917 # model validation code?), this solution works.
918 if obj_type == 'vns-interface':
919 data = associate_foreign_key_for_vns_interface(data)
920
921 # Create the object and re-query to get the id/pk value
922 # FIXME: Could probably optimize here if the data already
923 # contains the pk value.
924 if sdnsh.description: # description debugging
925 print "push_mode: create ", obj_type, data
926 result = sdnsh.rest_create_object(obj_type, data)
927 check_rest_result(result)
928 result = sdnsh.rest_query_objects(obj_type, data)
929 check_rest_result(result)
930 else:
931 if sdnsh.description: # description debugging
932 print "push_mode: object found", obj_type, result
933
934 # Check (again) to make sure that we have an object
935 if len(result) == 0:
936 raise error.CommandSemanticError('Object not found; type = %s' % obj_type)
937
938 # Check to make sure there aren't multiple matching objects. If there
939 # are that would indicate a problem in the command description.
940 if len(result) > 1:
941 raise error.CommandDescriptionError('Push mode info must identify a single object;'
942 'type = %s; data = %s' %
943 (obj_type, str(data)))
944
945 # Get the id/pk value from the object info
946 pk_name = mi.pk(obj_type)
947 if not pk_name:
948 raise error.CommandDescriptionError("Can't find primary key name for type: %s" % obj_type)
949 key = result[0][pk_name]
950 else:
951 pk_name = '<none>'
952
953 if sdnsh.description: # description debugging
954 print "push_mode: ", mode_name, obj_type, pk_name, key
955 exitCallback = None
Srikanth Vavilapallib5c3ca52014-12-15 15:59:33 -0800956 if (mode_name == 'config-tunnelset'):
957 exitCallback = tunnelset_config_exit
958 if ((mode_name == 'config-tunnel') or (mode_name == 'config-tunnelset-tunnel')):
Srikanth Vavilapalli1725e492014-12-01 17:50:52 -0800959 exitCallback = tunnel_config_exit
Srikanth Vavilapalli40d79f82014-12-17 14:29:24 -0800960 if (mode_name.startswith('config-policy')):
Srikanth Vavilapalli1725e492014-12-01 17:50:52 -0800961 exitCallback = policy_config_exit
962 sdnsh.push_mode(mode_name, obj_type, key, exitCallback)
963
964
965def pop_mode_stack():
966 global sdnsh
967
968 if sdnsh.description: # description debugging
969 print "pop_mode: "
970 sdnsh.pop_mode()
971
972def confirm_request(prompt):
973 global sdnsh
974
975 if sdnsh.batch:
976 return
977 result = raw_input(prompt)
978 if result.lower() == 'y' or result.lower() == 'yes':
979 return
980 raise error.ArgumentValidationError("Expected y or yes, command: ")
981
982import c_data_handlers
983
984def convert_vns_access_list(obj_type, key, data):
985 """
986 For vns-access-group's, the access list which is the first parameter
987 needs to be converted into a vns-access-list foreign key. This is
988 possible since the vns name is part of the current object id.
989 """
990 global sdnsh, modi
991
992 key_parts = key.split('|')
993 if len(key_parts) != 3:
994 raise error.ArgumentValidationError("invalid id")
995 if not 'vns-access-list' in data:
996 raise error.ArgumentValidationError("missing vns-access-list")
997 try:
998 key_parts.pop()
999 vnskey='|'.join(key_parts)
1000 entry = sdnsh.rest_query_objects('vns-access-list',
1001 { 'vns' : vnskey,
1002 'name' : data['vns-access-list']
1003 })
1004 except Exception, _e:
1005 entry = []
1006
1007 if len(entry) != 1:
1008 raise error.ArgumentValidationError("unknown acl %s" % data['vns-access-list'])
1009 data['vns-access-list'] = entry[0]['id']
1010
1011def command_query_object(obj_type, data, scoped, sort):
1012 """
1013 Return model entries (db rows) via the REST API. Try to be
1014 very smart about using parameters and the model definition to
1015 figure out how to query for the entries.
1016 """
1017
1018 if sdnsh.description:
1019 print 'command_query_object: ', obj_type, data, scoped, sort
1020
1021 skipforeignsearch=False
1022 if (obj_type=='virtualrouter-routingrule' or obj_type=='virtualrouter-interface'):
1023 skipforeignsearch=True
1024 # big_search describes a related search which must be done to
1025 # satisfy this request, see the relationship of tag-mapping to tag
1026 # as an example.
1027 big_search = []
1028
1029 key = mi.pk(obj_type)
1030 #
1031 if mi.is_compound_key(obj_type, key):
1032 if sdnsh.description: # description debugging
1033 print "command_query_object: %s compound %s" % (obj_type, key)
1034 #
1035 # collect compound key names, look for these in the data,
1036 # if any of the values are 'all', remove the item from
1037 # the group of data.
1038 #
1039 # XXX needs work: we ought to check to see if the
1040 # compound key is part of some other key.
1041 #
1042 if scoped:
1043 obj_d = { key : sdnsh.get_current_mode_obj() }
1044 mi.split_compound_into_dict(obj_type, key, obj_d, is_prefix = True)
1045 for (k,v) in obj_d.items():
1046 if k != key and not k in data:
1047 data[k] = v
1048
1049 new_data = {}
1050 dckfs = mi.deep_compound_key_fields(obj_type, key)
1051 if key in data:
1052 mi.split_compound_into_dict(obj_type, key, data, is_prefix = True)
1053 foreign_obj_type_search = {}
1054
1055 for kf in dckfs:
1056 if mi.obj_type_has_field(obj_type, kf) and kf in data and data[kf] != 'all':
1057 new_data[kf] = data[kf]
1058 elif not mi.obj_type_has_field(obj_type, kf):
1059 # deep_compound_keys returns references via foreign keys.
1060 # if the field is missing in obj_type, its likely from
1061 # some related fk.
1062 for fk in mi.obj_type_foreign_keys(obj_type):
1063 (_fk_obj_type, fk_name) = mi.foreign_key_references(obj_type,
1064 fk)
1065 if kf == fk_name:
1066 # print "FOUND MATCH ", kf, _fk_obj_type, fk_name
1067 continue
1068 elif not mi.is_compound_key( _fk_obj_type, fk_name):
1069 continue
1070 for fkcf in mi.compound_key_fields(_fk_obj_type, fk_name):
1071 if fkcf in data and data[fkcf] != 'all':
1072 # assume all models use COMPOUND_KEY_FIELDS
1073 if _fk_obj_type not in foreign_obj_type_search:
1074 foreign_obj_type_search[_fk_obj_type] = {}
1075 foreign_obj_type_search[_fk_obj_type][fkcf] = data[fkcf]
1076 pass
1077 # see if foreign key fields are indirectly named
1078 elif mi.is_foreign_key(obj_type, kf):
1079 (_fk_obj_type, fk_name) = mi.foreign_key_references(obj_type,
1080 kf)
1081 if fk_name in data and data[fk_name] != 'all':
1082 new_data[kf] = data[fk_name]
1083 if (not skipforeignsearch): #skip foreign key search for routingrule type
1084 if len(foreign_obj_type_search):
1085 # This means to collect the entries, a search though a
1086 # related obj_type (through foreign key) will need to be done
1087 # a single query isn't enough, unless all entries are collected
1088 # consider the relationship between tag-mapping and tags
1089 #
1090 # This code seems to handle single indirected foreign key
1091 # lookup, but if deep_compound_key_fields() found more than
1092 # three layers deep (the obj-type has a fk reference to a
1093 # table, which had a fk reference to another table, which
1094 # had a value to search with), this won't do the trick.
1095 # at that point some sort of recursive building of the
1096 # foreign keys would be needed to collect up the required
1097 # final seraches
1098 for (_fk_obj_type, search) in foreign_obj_type_search.items():
1099 fk_entries = sdnsh.rest_query_objects(_fk_obj_type, search)
1100 # need to identify the name associated foreign key in this model
1101 for fk in mi.obj_type_foreign_keys(obj_type):
1102 (fk_obj, fk_name) = mi.foreign_key_references(obj_type, fk)
1103 if fk_obj == _fk_obj_type:
1104 obj_type_field = fk
1105 break
1106 else:
1107 raise error.CommandSemanticError("bigsearch: can't find fk reference"
1108 "for %s for obj-type %s" %
1109 (fk, obj_type))
1110 big_search += [{obj_type_field:
1111 x[mi.pk(_fk_obj_type)]} for x in fk_entries]
1112 # big_search would return id's for the _fk_obj_type,
1113 # which can be used to search this obj_type
1114 # look for fields which are set in new_data, which aren't in data.
1115 for (field, value) in data.items():
1116 if field not in new_data:
1117 if mi.is_marked_searchable(obj_type, field) and value!='all':
1118 new_data[field] = value
1119
1120 data = new_data
1121 else:
1122 # Only allow fields which are searchable (XXX need a prediate)
1123 # only save primary key's and foreigh keys.
1124 new_data = {}
1125 if key in data and mi.is_primary_key(obj_type, key):
1126 new_data[key] = data[key]
1127 for fk in mi.obj_type_foreign_keys(obj_type):
1128 if fk in data:
1129 new_data[fk] = data[fk]
1130 (_fk_obj, fk_fn) = mi.foreign_key_references(obj_type, fk)
1131 if fk_fn in data:
1132 new_data[fk_fn] = data[fk_fn]
1133 for f in mi.obj_type_fields(obj_type):
1134 if f in data and f not in new_data:
1135 new_data[f] = data[f]
1136
1137 data = new_data
1138
1139 if scoped:
1140 data[key] = sdnsh.get_current_mode_obj()
1141
1142 if key in data and (data[key]=='all' or data[key]==None):
1143 del data[key]
1144 #
1145 # Now that the fields have been disassembled as much as possible, see
1146 # if some of the entries need to be cobbled back together.
1147 fks = mi.obj_type_foreign_keys(obj_type)
1148 if sdnsh.description: # description debugging
1149 print "command_query_object: %s foreign-key %s" % (obj_type, fks)
1150 if fks:
1151 for fk in fks:
1152 (fk_obj, fk_nm) = mi.foreign_key_references(obj_type, fk)
1153
1154 if not fk in data or \
1155 (mi.is_compound_key(fk_obj, fk_nm) and data[fk].find('|') == -1):
1156
1157 # use various techniques to populate the foreign key
1158 # - if the foreign key is for class which has a compound key, see if all the
1159 # parts of the compound key are present
1160 if mi.is_compound_key(fk_obj, fk_nm):
1161 kfs = mi.deep_compound_key_fields(fk_obj, fk_nm)
1162 missing = [x for x in kfs if not x in data]
1163 if len(missing) == 0:
1164 # remove the entries, build the compound key for the foreign key reference
1165 new_value = mi.compound_key_separator(fk_obj, fk_nm).\
1166 join([data[x] for x in kfs])
1167 # verify the foreign key exists, if not complain and return,
1168 # preventing a error during the create request
1169 query_result = sdnsh.rest_query_objects( fk_obj, { fk_nm : new_value })
1170 check_rest_result(query_result)
1171 if len(query_result) == 0:
1172 joinable_name = ["%s: %s" % (x, data[x]) for x in kfs]
1173 raise error.CommandSemanticError("Reference to non-existant object: %s " %
1174 ', '.join(joinable_name))
1175 for rfn in kfs: # remove field name
1176 del data[rfn]
1177 data[fk] = new_value
1178 if sdnsh.description: # description debugging
1179 print "command_query_object: %s foreign key construction " % obj_type, data
1180 #
1181 # Do something for alias displays, for obj_types which sdnsh says
1182 # are aliases, find the foreign reference in the alias obj_type,
1183 # and use that to determine the field name (fk_fn) in the parent.
1184 # Do lookups based on either the alias field name, or the parent's
1185 # fk_fn when set in data{}
1186 if obj_type in mi.alias_obj_types:
1187 field = mi.alias_obj_type_field(obj_type)
1188 (_fk_obj, fk_fn) = mi.foreign_key_references(obj_type, field)
1189 new_data = {}
1190 if fk_fn in data and data[fk_fn] != 'all':
1191 new_data[field] = data[fk_fn]
1192 elif field in data and data[field] != 'all':
1193 new_data[field] = data[field]
1194 data = new_data
1195
1196 #
1197 # The sort value ought to be a command separated list of fields within the model
1198 #
1199 if sort:
1200 data['orderby'] = sort
1201
1202 if not mi.obj_type_has_model(obj_type):
1203 return rest_to_model.get_model_from_url(obj_type, data)
1204
1205 if sdnsh.description: # description debugging
1206 print "command_query_object: ", obj_type, data
1207
1208 if len(big_search):
1209 entries = []
1210 if sdnsh.description: # description debugging
1211 print "command_query_object: big search", big_search
1212 for bs in big_search:
1213 search = dict(list(bs.items()) + list(data.items()))
1214 entries += sdnsh.rest_query_objects(obj_type, search)
1215 # XXX needs to be re-sorted
1216 return entries
1217
1218 return sdnsh.rest_query_objects(obj_type, data)
1219
1220
1221def command_display_table_join_entries(obj_type, data, entries, detail):
1222 """
1223 """
1224 if obj_type == 'tag-mapping':
1225 # lift persist from the parent tag
1226 if len(entries) == 1:
1227 entry = entries[0]
1228 tag = sdnsh.rest_query_objects('tag', { mi.pk('tag') : entry['tag']})
1229 entry['persist'] = tag[0]['persist']
1230 else:
1231 # key? value? for the _dict?
1232 tags = create_obj_type_dict('tag', mi.pk('tag'))
1233 for entry in entries:
1234 entry['persist'] = tags[entry['tag']][0]['persist']
1235
1236 if obj_type == 'controller-node':
1237 # This is a big odd, since the current node needs to be asked
1238 # which controller node it is
1239 url = "http://%s/rest/v1/system/controller" % sdnsh.controller
1240
1241 result = sdnsh.store.rest_simple_request(url)
1242 check_rest_result(result)
1243 iam = json.loads(result)
1244
1245 cluster_url = ("http://%s/rest/v1/system/ha/clustername"
1246 % sdnsh.controller)
1247 result = sdnsh.store.rest_simple_request(cluster_url)
1248 check_rest_result(result)
1249 # perhaps ought to assert on lenresult) == 1
1250 clustername = json.loads(result)[0]['clustername']
1251
1252 for entry in entries:
1253 controller = None
1254 if entry['id'] == iam['id']:
1255 controller = sdnsh.controller
1256 else:
1257 # find interfaces which hacve a firewall rule open for
1258 # tcp/80. ie: ip for the interface with rest-api role
1259 ips = local_interfaces_firewall_open("tcp", 80, entry)
1260
1261 # controller-interfaces needs to be examined to determine
1262 # if there's an ip address to use to discover the ha-role
1263 if len(ips) == 1:
1264 # Not even certain if this is reachable
1265 if ips[0]['discovered-ip'] != '':
1266 controller = ips[0]['discovered-ip']
1267 elif ips[0]['ip'] != '':
1268 controller = ips[0]['ip']
1269 else:
1270 entry['ha-role'] = 'no-ip'
1271 entry['errors'] = 'No IP Address'
1272 else:
1273 entry['errors'] = 'No IP Address'
1274
1275 if controller == None:
1276 entry['errors'] = 'No ip address configured'
1277 entry['ha-role'] = 'unknown'
1278 continue
1279
1280 try:
1281 url = "http://%s/rest/v1/system/ha/role" % controller
1282 result = sdnsh.store.rest_simple_request(url, timeout = 2)
1283 check_rest_result(result)
1284 ha_role = json.loads(result)
1285 entry['ha-role'] = ha_role['role']
1286 if not 'clustername' in ha_role:
1287 entry['errors'] = 'no clustername in ha-role rest api'
1288 entry['ha-role'] = 'Untrusted: %s' % ha_role['role']
1289 elif ha_role['clustername'] != clustername:
1290 entry['errors'] = 'Not in HA Cluster, requires decomission'
1291 entry['ha-role'] = 'External Cluster: %s' % ha_role['role']
1292 if 'change-date-time' in ha_role:
1293 entry['change-date-time'] = ha_role['change-date-time']
1294 if 'change-description' in ha_role:
1295 entry['change-description'] = ha_role['change-description']
1296 except urllib2.HTTPError, e: # timeout?
1297 entry['errors'] = e.reason
1298 entry['ha-role'] = 'unknown'
1299 continue
1300 except urllib2.URLError, e: # timeout?
1301 entry['errors'] = '%s: %s' % (controller, e.reason)
1302 entry['ha-role'] = 'unknown'
1303 continue # dontt try the uptime, it will fail too
1304 except Exception, e:
1305 entry['errors'] = str(e)
1306 entry['ha-role'] = 'unknown'
1307
1308 url = "http://%s/rest/v1/system/uptime" % controller
1309 try:
1310 result = sdnsh.store.rest_simple_request(url)
1311 check_rest_result(result)
1312 uptime = json.loads(result)
1313 entry['uptime'] = uptime['systemUptimeMsec']
1314
1315 except Exception, e:
1316 pass
1317
1318 return detail
1319
1320
1321def command_display_table(obj_type, data, detail = 'default',
1322 table_format = None, title = None, scoped = None, sort = None):
1323
1324 """
1325 Display entries from a obj_type, with some filtering done via data,
1326 and the output format described by table_format, with the devel of detail in detail
1327
1328 @param obj_type string name of the object type
1329 @param data dictionary of configured data items from the description
1330 @param table_format string describing table format to use for output
1331 @param detail string describing the detail-flavor for format
1332 @param scoped string, when not null, indicates the submode level is used to filter query request
1333 @param sort string, describes sort to append to the query request
1334 """
1335
1336 if not mi.obj_type_exists(obj_type):
1337 raise error.CommandDescriptionError("Unknown obj-type: %s" % obj_type)
1338
1339 if sdnsh.description: # description debugging
1340 print "command_display_table:", obj_type, data, table_format, detail, scoped, sort
1341
1342 if 'detail' in data:
1343 detail = data['detail']
1344
1345 if not table_format:
1346 if 'format' in data:
1347 table_format = data['format']
1348 else:
1349 table_format = obj_type
1350 if 'scoped' in data:
1351 scoped=data['scoped']
1352 del data['scoped']
1353 entries = command_query_object(obj_type, data, scoped, sort)
1354 if sdnsh.description: # description debugging
1355 print "command_display_table: %d entries found, using %s" % (len(entries), data)
1356
1357 # update any of the pretty-printer tables based on the obj_type
1358 obj_type_show_alias_update(obj_type)
1359
1360 # with_key manages whether a 'detail' or table is displayed.
1361 with_key = '<with_key>' if detail == 'details' and len(entries) > 0 else '<no_key>'
1362
1363 # pick foreign keys which are compound keys, explode these into fields
1364 fks = [x for x in mi.obj_type_foreign_keys(obj_type) if mi.is_compound_key(obj_type,x)]
1365 for entry in entries:
1366 for fk in fks:
1367 if fk in entry: # fk may be null-able
1368 mi.split_compound_into_dict(obj_type, fk, entry, True)
1369 #
1370 detail = command_display_table_join_entries(obj_type, data, entries, detail)
1371
1372 # use display_obj_type_rows since it (currently) joins fields for obj_types.
1373 display = sdnsh.display_obj_type_rows(table_format, entries, with_key, detail)
1374 if title:
1375 return title + display
1376 return display
1377
1378
1379def command_display_rest_join_entries(table_format, data, entries, detail):
1380 """
1381 @param table_format string, identifying the final table output
1382 @param data dict, used to query the rest api output
1383 @param entries list of dicts, ready to be displayed
1384 @return string replacing detail
1385
1386 """
1387
1388 if sdnsh.description: # description debugging
1389 print "command_display_rest_join_entries: ", table_format, data, detail
1390
1391 if table_format == 'controller-interface':
1392 # join firewall rules for these interfaces
1393 for intf in entries:
1394 rules = [x['rule'] for x in sdnsh.get_firewall_rules(intf['id'])]
1395 intf['firewall'] = ', '.join(rules)
1396
1397 if table_format == 'system-clock':
1398 # join the 'time' string, possibly remove 'tz' from entries[0]
1399 entries[0]['time'] = sdnsh.get_clock_string(entries[0], data.get('detail'))
1400 return 'details' # force table format
1401
1402 return detail
1403
1404
1405def command_display_rest_type_converter(table_format, rest_type, data, entries):
1406 """
1407 the expected display table_format is a list of dictionaries
1408 each dictionary has the field : value pairs. Many rest api's
1409 return a dictionary of different layers, the description
1410 provides a rest-type, which is used to describe the form
1411 of the value returned from the rest api.
1412 """
1413
1414 if sdnsh.description: # description debugging
1415 print "command_display_rest_type_converter: ", table_format, rest_type
1416
1417 if rest_type.startswith('dict-of-list-of-'):
1418 # entries look like { row_name : [value, ...], ... more-row-value-pairs }
1419 #
1420 # dict-of-list-of: a dict with key's which are given
1421 # the name of the first token, then the dict's value is
1422 # a list which can be given an associated name.
1423 # for example 'dict-of-list-of-cluster-id|[switches]'
1424 #
1425 # 'dict-of-list-of-switch' is a dict with key : value's
1426 # where the value is a list. The member's of the list
1427 # are dictionaries. the key of the outer dict is added to
1428 # each of the dicts, and this interior dict is added to
1429 # the final output list.
1430
1431 # identify the added key from the rest_type
1432 key = rest_type.replace('dict-of-list-of-','')
1433 parts = key.split('|')
1434 names = None
1435 build_list = False
1436 if len(parts) > 0:
1437 key = parts[0]
1438 names = parts[1:] # should only be one name
1439 if len(names) > 0 and names[0][0] == '[':
1440 build_list = True
1441 formatted_list = []
1442 for (row_name, rows) in entries.items():
1443 if not rows:
1444 continue
1445 # use the names as ways of describing each of the list's items
1446 if type(rows) == list and build_list:
1447 # name[0] looks like '[switches]', requesting that this
1448 # list become switches : [rows]
1449 formatted_list.append({key : row_name, names[0][1:-1] : rows})
1450 elif type(rows) == list:
1451 for row in rows:
1452 add_dict = {key : row_name}
1453 if type(row) == str or type(row) == unicode:
1454 add_dict[names[0]] = row
1455 elif type(row) == dict:
1456 # addition names make no difference
1457 add_dict.update(row)
1458 formatted_list.append(add_dict)
1459 elif type(rows) == dict:
1460 do_append = True
1461 new_row = { key : row_name }
1462 for name in [x for x in names.keys() if x in row]:
1463 item = row[name]
1464 if type(item) == str or type(item) == unicode:
1465 new_row[name] = item
1466 if type(item) == dict:
1467 new_row[name].update(item)
1468 if type(item) == list:
1469 do_append = False
1470 for i_row in item:
1471 new_row.update(i_row)
1472 formatted_list.append(new_row)
1473 new_row = { key : row_name }
1474 if do_append:
1475 formatted_list.append(new_row)
1476
1477 entries = formatted_list
1478 elif rest_type.startswith('dict-of-dict-of-'):
1479 # entries looks like { row_name : { [ { }, ... ] } }
1480 # ^
1481 # want this |
1482 # ie: dict with a value which is a dict, whose
1483 # 'dict-of-dict-of-switch|ports' The dict has key : values
1484 # where the value is a dict. That dict has the 'switch' : key
1485 # added, and it becomes the final output dict.
1486 #
1487 # if a second name is included, then the outer dict is
1488 # examined to find these values (ie: values[names]), and these
1489 # get added to the final output dict.
1490 #
1491 # identify the added key from the rest_type
1492 key = rest_type.replace('dict-of-dict-of-','')
1493 parts = key.split('|')
1494 name = None
1495 if len(parts) > 0:
1496 names = parts[1:]
1497 key = parts[0]
1498 formatted_list = []
1499 for (row_name, row) in entries.items():
1500 row[key] = row_name
1501 do_append = False
1502 if names:
1503 new_row = {}
1504 for name in names:
1505 if name in row:
1506 item = row[name]
1507 if type(item) == str or type(item) == unicode:
1508 new_row[name] = item
1509 do_append = True
1510 elif type(item) == dict:
1511 if name == row_name:
1512 do_append = True
1513 elif type(item) == list:
1514 for i_row in item:
1515 row_items = {}
1516 row_items[key] = row_name
1517 row_items.update(i_row)
1518 formatted_list.append(row_items)
1519 if do_append:
1520 formatted_list.append(row)
1521
1522 else:
1523 formatted_list.append(row)
1524
1525 entries = formatted_list
1526 elif rest_type.startswith('dict-with-'):
1527 # rest result looks like: { k : v, k : { } }
1528 # ^
1529 # want this |
1530 # dict-with: typically used for dict returns which have
1531 # nested dict's who's values are promoted to a single
1532 # list with a dict with these values.
1533 #
1534 # identify the added key from the rest_type
1535 key = rest_type.replace('dict-with-','')
1536 names = key.split('|')
1537 collect_row = {}
1538 formatted_list = []
1539 for name in names:
1540 if name in entries:
1541 item = entries[name]
1542 if type(item) == str or type(item) == unicode or \
1543 type(item) == int or type(item) == long: # XXX float?
1544 collect_row[name] = item
1545 elif type(item) == list:
1546 for i_row in item:
1547 row_items = {}
1548 formatted_list.append(i_row)
1549 elif type(item) == dict:
1550 collect_row.update(item)
1551
1552 if len(collect_row) == 0:
1553 entries = formatted_list
1554 else:
1555 entries = [collect_row] + formatted_list
1556
1557 elif rest_type == 'dict':
1558 entries = [entries]
1559 else:
1560 raise error.CommandDescriptionError("Unknown rest-type: %s" % rest_type)
1561 return entries
1562
1563
1564def missing_part(key_parts, entry, key_case = False):
1565 """
1566 Return the name of the missing field of one of the strings
1567 in the key_parts list when it doesn't appear in the 'entry' dictionary.
1568
1569 Return None otherwise.
1570
1571 This is used to identify rows which don't have all the
1572 parts needed to constrcut a join key, or a db-table or
1573 query "key" to support addition of two different tables.
1574
1575 @key_parts list of strings,
1576 @entry dictionary, needs to contains each string in key_parts
1577 @key_case True when all key_parts may contain a leading '~' to
1578 denote the field needs to be lower cased for joining
1579 """
1580 for kn in key_parts:
1581 if not kn in entry:
1582 if key_case == False:
1583 return kn
1584 if kn[0] != '~':
1585 return kn
1586 if kn[1:] not in entry:
1587 return kn[1:]
1588
1589 return None
1590
1591
1592def case_cvt(fn, f_dict):
1593 """
1594 For join operations, the fields in the partial result can no longer
1595 be associated with any obj-type, which means its impossible to determine
1596 whether the associated field is case sensitive.
1597
1598 One approach to this problem is to case-normalize the obj-type's
1599 field values when they're first added to the row. That doesn't
1600 help for rest-api's, which means it can only be a partial solution.
1601 In addition, it makes sense to preserve these values when possible,
1602 but still join based on case-normalization.
1603 """
1604 if fn[0] == '~':
1605 return str(f_dict.get(fn[1:], '').lower())
1606 return str(f_dict.get(fn, ''))
1607
1608
1609def obj_type_field_case(data, obj_type, field):
1610 """
1611 For objects where the case-normalization is identifed,
1612 manage conversion of the value associated with the field
1613 """
1614 case = mi.get_obj_type_field_case_sensitive(obj_type, field)
1615 return str(data[field]) if not case else str(utif.convert_case(case, data[field]))
1616
1617
1618def add_fields(dest, src):
1619 """
1620 These should both be dictionaries, leave the original entries in place
1621 when the 'dest' entries are populated from 'src'. This operation is
1622 handy since the original 'dest' entries may differ from the 'src' due
1623 to case normalization. Since having consistent names is a plus, by
1624 not updating the value with the 'src' entries, 'dest' retains its original
1625 values.
1626 """
1627 for (n,v) in src.items():
1628 if n not in dest:
1629 dest[n] = v
1630 elif str(dest[n]).lower() == str(v).lower:
1631 # should have better controls for when the case matters
1632 if sdnsh.description:
1633 print 'ADD %s skipping updating %s <-> %s' % (n, dest[n], v)
1634 else:
1635 dest[n] = v
1636
1637
1638def command_query_table(obj_type, data,
1639 clear = True,
1640 key = None, append = None, scoped = None, sort = None, crack = None):
1641 """
1642 Leave the result in command's global query_result, which can
1643 be used by other c_action steps
1644
1645 'key' is one or more fields which are concatenated together to form
1646 the display-pipeline's version of a primary key. It could be the
1647 actual primary key of the table, or it could be some fields which
1648 appear in all the rows. Once the 'key' is constructed, it used to
1649 determine how results are added to the command.query_result.
1650
1651 If the existing entries are to be 'cleared', then te primary key's
1652 are simply added to the table. When the entries aren't cleared, then
1653 the computed primary key is used to join against existing items.
1654
1655 Finally, the dict field name for the primary key is a single character: '@'
1656 This name was picked since its not possible for the database to ever
1657 use that name.
1658 """
1659
1660 if not mi.obj_type_exists(obj_type):
1661 raise error.CommandDescriptionError("Unknown obj-type: %s" % obj_type)
1662
1663 if sdnsh.description: # description debugging
1664 print "command_query_table:", obj_type, data, clear, key, append, scoped, sort, crack
1665
1666 if 'scoped' in data:
1667 scoped=data['scoped']
1668 del data['scoped']
1669
1670 result = command_query_object(obj_type, data, scoped, sort)
1671 if sdnsh.description: # description debugging
1672 print "command_query_table: %d entries found, using %s" % \
1673 (len(result), data)
1674
1675 if crack:
1676 if crack == True:
1677 crack = mi.pk(obj_type)
1678 for entry in result:
1679 mi.split_compound_into_dict(obj_type, crack, entry, True)
1680
1681 if append:
1682 for entry in result:
1683 if type(append) == dict:
1684 entry.update(append)
1685 elif type(append) == list:
1686 entry.update(dict(append))
1687 else:
1688 entry[append] = True
1689
1690 # all the field from all the rows need to be present.
1691 if key:
1692 fields = key.split('|')
1693
1694 if clear:
1695 command.query_result = result
1696 if key:
1697 for r in result:
1698 missing = missing_part(fields, r)
1699 if missing:
1700 if sdnsh.description:
1701 print "command_query_table: ' \
1702 ' missing field in row %s (%s) " % (missing, obj_type)
1703 continue
1704 r['@'] = '|'.join([obj_type_field_case(r, obj_type, f) for f in fields])
1705 else:
1706 if key == None:
1707 if command.query_resuls != None:
1708 command.query_result += result
1709 else:
1710 command.query_result = result
1711 else:
1712 r_dict = {}
1713 for r in result:
1714 missing = missing_part(fields, r)
1715 if missing:
1716 if sdnsh.description:
1717 print "command_query_table: ' \
1718 ' missing field in row %s (%s) " % (missing, obj_type)
1719 continue
1720 pk = '|'.join([r[f] for f in fields])
1721 r_dict[pk] = r
1722 if hasattr(command, 'query_result') and command.query_result:
1723 for qr in command.query_result:
1724 if '@' in qr and qr['@'] in r_dict:
1725 add_fields(qr, r_dict[qr['@']])
1726 del r_dict[qr['@']]
1727 command.query_result += r_dict.values()
1728 else:
1729 for (r, value) in r_dict.items():
1730 value['@'] = '|'.join([value[f] for f in fields])
1731 command.query_result = r_dict.values()
1732
1733
1734def command_query_rest(data,
1735 url = None, path = None, clear = True,
1736 key = None, rest_type = None, scoped = None, sort = None, append = None):
1737 """
1738 Leave the result in command's global query_result, which can
1739 be used by other c_action steps (query-table, join-table, join-rest, display)
1740
1741 'key' is one or more fields which are concatenated together to form
1742 the display-pipeline's version of a primary key. It could be the
1743 actual primary key of the table, or it could be some fields which
1744 appear in all the rows. Once the 'key' is constructed, it used to
1745 determine how results are added to the command.query_result.
1746
1747 If the existing entries are to be 'cleared', then te primary key's
1748 are simply added to the table. When the entries aren't cleared, then
1749 the computed primary key is used to join against existing items.
1750
1751 Finally, the dict field name for the primary key is a single character: '@'
1752 This name was picked since its not possible for the database to ever
1753 use that name.
1754
1755 """
1756
1757 if sdnsh.description: # description debugging
1758 print "command_query_rest:", url, path, rest_type, data, scoped, sort, append
1759
1760 if url == None and path == None:
1761 raise error.CommandDescriptionError("missing url or path")
1762
1763 if path:
1764 schema = sdnsh.sdndb.schema_detail(path)
1765 if schema:
1766 result = sdnsh.sdndb.data_rest_request(path)
1767 if key:
1768 # create a key dictionary, with the key values, pointing to
1769 # a psth in the schema.
1770 pass
1771 print 'PATH', path, result
1772 else:
1773 # if url is a list, pick the first one which can be build from the data
1774 if type(url) == list:
1775 select_url = url
1776 else:
1777 select_url = [url]
1778
1779 use_url = None
1780 for u in select_url:
1781 try:
1782 use_url = (u % data)
1783 break
1784 except:
1785 pass
1786
1787 if use_url == None:
1788 if sdnsh.description: # description debugging
1789 print "command_query_rest: no url found"
1790 return
1791
1792 query_url = "http://%s/rest/v1/" % sdnsh.controller + use_url
1793
1794 if sdnsh.description: # description debugging
1795 print "command_query_rest: query ", query_url
1796 try:
1797 result = sdnsh.store.rest_simple_request(query_url)
1798 check_rest_result(result)
1799 entries = json.loads(result)
1800 except Exception, e:
1801 if sdnsh.description or sdnsh.debug:
1802 print 'command_query_rest: ERROR url %s %s' % (url, e)
1803 entries = []
1804
1805 if entries == None or len(entries) == 0:
1806 if sdnsh.description: # description debugging
1807 print "command_query_rest: no new entries ", query_url
1808 if clear:
1809 command.query_result = None
1810 return
1811
1812 # It certainly seems possible to map from url's to the type associated,
1813 # with the result, but it also makes sense to encode that type information
1814 # into the description
1815 if rest_type:
1816 result = command_display_rest_type_converter(None,
1817 rest_type,
1818 data,
1819 entries)
1820 if sdnsh.description: # description debugging
1821 print "command_query_rest: %s #entries %d " % (url, len(entries))
1822 print result
1823 else:
1824 result = []
1825 import fmtcnv
1826 if (onos == 1) and (url == 'links'):
1827 for entry in entries:
1828 src = entry.get('src')
1829 dst = entry.get('dst')
1830 for tempEntry in entries:
1831 if cmp(src, tempEntry.get('dst')) == 0:
1832 if cmp(dst, tempEntry.get('src')) == 0:
1833 entries.remove(tempEntry)
1834 result.append({
1835 'src-switch' : fmtcnv.print_switch_and_alias(entry['src']['dpid']),
1836 'src-port' : entry['src']['portNumber'],
1837 'src-port-state' : 0,
1838 'dst-switch' : fmtcnv.print_switch_and_alias(entry['dst']['dpid']),
1839 'dst-port' : entry['dst']['portNumber'],
1840 'dst-port-state' : 0,
1841 'type' : entry['type'],
1842 })
1843 else:
1844 result = entries
1845
1846 if append:
1847 for entry in result:
1848 if type(append) == dict:
1849 entry.update(append)
1850 elif type(append) == list:
1851 entry.update(dict(append))
1852 else:
1853 entry[append] = True
1854
1855 if key:
1856 fields = key.split('|')
1857
1858 if clear:
1859 command.query_result = result
1860 if key:
1861 for r in result:
1862 r['@'] = '|'.join([r[f] for f in fields])
1863 else:
1864 if key == None:
1865 if command.query_result != None:
1866 command.query_result += result
1867 else:
1868 command.query_result = result
1869 else:
1870 r_dict = {}
1871 for r in result:
1872 missing = missing_part(fields, r, key_case = True)
1873 if missing:
1874 if sdnsh.description:
1875 print "command_query_rest: missing field %s in row %s" % (missing, r)
1876 continue
1877 pk = '|'.join([case_cvt(f, r) for f in fields])
1878 r_dict[pk] = r
1879 for qr in command.query_result:
1880 if '@' in qr and qr['@'] in r_dict:
1881 add_fields(qr, r_dict[qr['@']])
1882
1883
1884def command_join_rest(url, data, key, join_field,
1885 add_field = None, rest_type = None, crack = None, url_key = None):
1886
1887 """
1888 url-key allows single row results to have a name:value added to the
1889 entry in situations where a single dictionary is computed after the
1890 rest-type conversion. this allows simple results from the url to
1891 have a keyword added to allow joins.
1892 """
1893 if not hasattr(command, 'query_result'):
1894 if sdnsh.description: # description debugging
1895 print "command_join_rest: no entries found"
1896 return
1897
1898 if command.query_result == None:
1899 if sdnsh.description: # description debugging
1900 print "command_join_rest: query_result: None"
1901 return
1902
1903 if sdnsh.description: # description debugging
1904 print "command_join_rest: %d entries found, using %s, url %s" % \
1905 (len(command.query_result), data, url)
1906 print "command_join_rest:", data, key, join_field
1907
1908 if url == None:
1909 return
1910 if join_field == None:
1911 return
1912 if key == None:
1913 return
1914
1915
1916 # Collect all the queries, removing any duplicates
1917 queries = {}
1918 for entry in command.query_result:
1919 # if url is a list, pick the first one which can be build from the data
1920 if type(url) == list:
1921 select_url = url
1922 else:
1923 select_url = [url]
1924
1925 use_url = None
1926 for u in select_url:
1927 try:
1928 use_url = (u % entry)
1929 break
1930 except:
1931 pass
1932
1933 if use_url == None:
1934 if sdnsh.description: # description debugging
1935 print "command_join_rest: no url found", url
1936 continue
1937 query_url = "http://%s/rest/v1/" % sdnsh.controller + use_url
1938
1939 if sdnsh.description: # description debugging
1940 print "command_join_rest: query ", query_url, entry
1941 if query_url in queries:
1942 continue
1943
1944 try:
1945 result = sdnsh.store.rest_simple_request(query_url)
1946 check_rest_result(result)
1947 entries = json.loads(result)
1948 except Exception, e:
1949 entries = []
1950
1951 if entries == None or len(entries) == 0:
1952 continue
1953
1954 # It certainly seems possible to map from url's to the type associated,
1955 # with the result, but it also makes sense to encode that type information
1956 # into the description
1957 if rest_type:
1958 queries[query_url] = command_display_rest_type_converter(None,
1959 rest_type,
1960 data,
1961 entries)
1962 #
1963 # url_key allows the addition of a key for joining for single results
1964 if url_key and len(queries[query_url]) == 1:
1965 queries[query_url][0][url_key] = entry.get(url_key)
1966
1967 if sdnsh.description: # description debugging
1968 print "command_join_rest: %s #entries %d #result %s" % \
1969 (url, len(entries), len(queries[query_url]))
1970 else:
1971 queries[query_url] = entries
1972
1973 # From the query results, generate the dictionary to join through
1974
1975 key_parts = key.split('|') # all the fields needed to make a key
1976 key_dict = {} # resulting key dictionary
1977 for (url, value) in queries.items():
1978 for entry in value:
1979 # see if all the key parts are in the entry
1980 missing = missing_part(key_parts, entry)
1981 if missing:
1982 if sdnsh.description:
1983 print 'command_join_rest: missing field %s in %s' % (missing, entry)
1984 continue
1985 new_key = '|'.join([str(entry[kn]) for kn in key_parts])
1986 if sdnsh.description: # description debugging
1987 print 'command_join_rest: new-key', new_key
1988 key_dict[new_key] = entry
1989
1990 # Using the key-dictinoary, look for matches from the original entries
1991
1992 if add_field:
1993 parts = add_field.split('|')
1994 from_fields = None
1995 if len(parts):
1996 add_field = parts[0]
1997 from_fields = parts[1:]
1998
1999 join_parts = join_field.split('|')
2000 for entry in command.query_result:
2001 if len(join_parts):
2002 missing = missing_part(join_parts, entry, key_case = True)
2003 if missing:
2004 if sdnsh.description: # description debugging
2005 print "command_join_rest: missing field %s in %s" % (missing, entry)
2006 continue
2007
2008 joiner = '|'.join([case_cvt(kn, entry) for kn in join_parts])
2009 else:
2010 if sdnsh.description: # description debugging
2011 print "command_join_rest: joining ", entry, join_field, entry.get(join_field)
2012 if not join_field in entry:
2013 continue
2014 joiner = case_cvt(join_field, entry)
2015
2016 if sdnsh.description: # description debugging
2017 print "command_join_rest: joining ", entry, joiner, key_dict.get(joiner)
2018
2019 if joiner in key_dict:
2020 # add all the entries from the key_dict
2021 if sdnsh.description: # description debugging
2022 print 'command_join_rest: ADD', key_dict[joiner]
2023 if add_field == None:
2024 add_fields(entry, key_dict[joiner])
2025 elif from_fields:
2026 if len(from_fields) == 1:
2027 # add a single field
2028 if from_fields[0] in key_dict[joiner]:
2029 entry[add_field] = key_dict[joiner][from_fields[0]]
2030 else:
2031 # add a dictionary
2032 entry[add_field] = dict([[ff, key_dict[joiner][ff]]
2033 for ff in from_fields])
2034 else:
2035 entry[add_field] = key_dict[joiner]
2036
2037 if sdnsh.description: # description debugging
2038 print "command_join_rest: ", command.query_result
2039
2040
2041def command_join_table(obj_type, data, key, join_field,
2042 key_value = None, add_field = None, crack = None):
2043 """
2044 Add fieds to the current command.query_result by looking up the entry in
2045 the db/store. key represents the value of the index to use from
2046 the entries read from the database. The key can be composed of
2047 multiple fields within the entry. The join_field is the name
2048 of the field within the command.query_result to use as the value to match
2049 against the key field.
2050
2051 When key_value is None, the matched entry from the join_field's is
2052 treated as a dictionary, and all the pair of name:values are added
2053 directly to the new entry.
2054
2055 When key_value is a field name, the joined entries are collected
2056 as a list, and added to the new entry a the key_value name.
2057 (see the use of tag-mapping as an example)
2058 """
2059 if not hasattr(command, 'query_result'):
2060 if sdnsh.description: # description debugging
2061 print "command_join_table: no entries found"
2062 return
2063
2064 if command.query_result == None:
2065 if sdnsh.description: # description debugging
2066 print "command_join_table: query_result: None"
2067 return
2068
2069 if sdnsh.description: # description debugging
2070 print "command_join_table: %d entries found, using %s, obj_type %s %s %s" % \
2071 (len(command.query_result), data, obj_type, key, join_field)
2072 print "command_join_table:", data, key, join_field
2073
2074 if join_field == None:
2075 return
2076 if key == None:
2077 return
2078
2079 if not mi.obj_type_exists(obj_type):
2080 raise error.CommandDescriptionError("Unknown obj-type: %s" % obj_type)
2081
2082 # build the join_dict, which will have keys for the items to
2083 # add into the entries
2084 if not mi.obj_type_has_model(obj_type):
2085 entries = rest_to_model.get_model_from_url(obj_type, data)
2086 else:
2087 entries = sdnsh.get_table_from_store(obj_type)
2088
2089 # determine whether specific field names are added
2090 if add_field:
2091 parts = add_field.split('|')
2092 from_fields = None
2093 if len(parts):
2094 add_field = parts[0]
2095 from_fields = parts[1:]
2096
2097 # constuct the join key for each row from the db table
2098 key_parts = key.split('|') # all the fields needed to make a key
2099 key_dict = {} # resulting key dictionary
2100 for entry in entries:
2101 # see if all the key parts are in the entry
2102 missing = missing_part(key_parts, entry)
2103 if missing:
2104 if sdnsh.description: # description debugging
2105 print "command_join_table: missing field %s in %s" % (missing, entry)
2106 continue
2107
2108 new_key = '|'.join([obj_type_field_case(entry, obj_type, kn) for kn in key_parts])
2109 if sdnsh.description: # description debugging
2110 print 'command_join_table: new-key', new_key, key_value
2111 if key_value:
2112 if not new_key in key_dict:
2113 key_dict[new_key] = [entry]
2114 else:
2115 key_dict[new_key].append(entry)
2116 else:
2117 key_dict[new_key] = entry
2118
2119
2120 # let 'crack' contain the field's name, not a boolean.
2121 if crack and crack == True:
2122 crack = mi.pk(obj_type)
2123
2124 # Using the key-dictinoary, look for matches from the original entries
2125
2126 join_parts = join_field.split('|')
2127 for entry in command.query_result:
2128 if len(join_parts):
2129 missing = missing_part(join_parts, entry, key_case = True)
2130 if missing:
2131 if sdnsh.description: # description debugging
2132 print "command_join_table: missing field %s in %s" % (missing, entry)
2133 continue
2134
2135 joiner = '|'.join([case_cvt(kn, entry) for kn in join_parts])
2136 else:
2137 if sdnsh.description: # description debugging
2138 print "command_join_table: joining ", entry, join_field, entry.get(join_field)
2139 if not join_field in entry:
2140 continue
2141 joiner = case_cvt(join_field, entry)
2142
2143 if joiner in key_dict:
2144 if crack:
2145 if not crack in key_dict[entry[joiner]]:
2146 if sdnsh.description: # description debugging
2147 print "command_join_table: field %s not in entry" % crack, key_dict[joiner]
2148 else:
2149 mi.split_compound_into_dict(obj_type, crack, key_dict[joiner], True)
2150
2151 # add all the entries from the key_dict
2152 if sdnsh.description: # description debugging
2153 print 'command_join_table: ADD %s as %s ' % (key_dict[joiner], add_field)
2154 if add_field == None:
2155 if key_value:
2156 entry[key_value] = key_dict[joiner]
2157 else:
2158 add_fields(entry, key_dict[joiner])
2159 elif from_fields:
2160 if len(from_fields) == 1:
2161 # add a single field
2162 if type(key_dict[joiner]) == list:
2163 entry[add_field] = [x[from_fields[0]] for x in key_dict[joiner]]
2164 else:
2165 entry[add_field] = key_dict[joiner][from_fields[0]]
2166 else:
2167 # add a dictionary with named fields
2168 if type(key_dict[joiner]) == list:
2169 for item in key_dict[joiner]:
2170 entry[add_field] = dict([[ff, item[ff]]
2171 for ff in from_fields])
2172 else:
2173 entry[add_field] = dict([[ff, key_dict[joiner][ff]]
2174 for ff in from_fields])
2175
2176 else:
2177 entry[add_field] = key_dict[joiner]
2178
2179 if sdnsh.description: # description debugging
2180 print "command_join_table: ", command.query_result
2181
2182
2183def command_display_rest(data, url = None, sort = None, rest_type = None,
2184 table_format = None, title = None, detail = None):
2185 """
2186 Perform a call to the rest api, and format the result.
2187
2188 When sort isn't None, it names a field whose's value are sorted on.
2189 """
2190 #just a hack check to implement decending sorting
2191 descending = False
2192 #raise error.ArgumentValidationError('\n\n\n %s' % (descending))
2193 if sdnsh.description: # description debugging
2194 print "command_display_rest: ", data, url, rest_type, table_format, detail
2195
2196 if not url:
2197 url = data.get('url')
2198 if not table_format:
2199 table_format = data.get('format')
2200
2201 check_single_entry = True
2202
2203 # if url is a list, pick the first one which can be build from the data
2204 select_url = url
2205 if url and type(url) == list:
2206 for u in url:
2207 try:
2208 select_url = (u % data)
2209 select_url = u # select this url from the list
2210 break
2211 except:
2212 pass
2213
2214 if not detail:
2215 detail = data.get('detail', 'default')
2216 url = "http://%s/rest/v1/" % sdnsh.controller + (select_url % data)
2217
2218 result = sdnsh.store.rest_simple_request(url)
2219 check_rest_result(result)
2220 if sdnsh.description: # description debugging
2221 print "command_display_rest: result ", result
2222 entries = json.loads(result)
2223 #rest_type = None
2224 #raise error.ArgumentValidationError('\n\n\n %s' % (attributes))
2225 #if 'realtimestats' in data and data['realtimestats'] == 'group':
2226
2227 entries2 = None
2228
2229
2230 if 'realtimestats' in data and data['realtimestats'] == 'group':
2231 url2 = "http://%s/rest/v1/" % sdnsh.controller + ("realtimestats/groupdesc/%(dpid)s/" % data)
2232 result2 = sdnsh.store.rest_simple_request(url2)
2233 check_rest_result(result2)
2234 if sdnsh.description: # description debugging
2235 print "command_display_rest: groupdesc result ", result2
2236 entries2 = json.loads(result2)
2237
2238 # It certainly seems possible to map from url's to the type associated,
2239 # with the result, but it also makes sense to encode that type information
2240 # into the description
2241 if 'routerrealtimestats' in data and data['routerrealtimestats'] == 'adjacency':
2242 rest_type =False
2243 if rest_type:
2244 entries = command_display_rest_type_converter(table_format,
2245 rest_type,
2246 data,
2247 entries)
2248 if 'realtimestats' in data and data['realtimestats'] == 'group':
2249 if entries2 is not None:
2250 entries2 = command_display_rest_type_converter(table_format,
2251 rest_type,
2252 data,
2253 entries2)
2254
2255 if 'router' in data and data['router'] == 'router':
2256 combResult = []
2257 for entry in entries:
2258 attributes = entry.get('stringAttributes')
2259 #raise error.ArgumentValidationError('\n\n\n %s' % (attributes))
2260 combResult.append({
2261 'dpid' : entry.get('dpid'),
2262 'routerIP' : attributes['routerIp'],
2263 'name' : attributes['name'],
2264 'isEdgeRouter' : attributes['isEdgeRouter'],
2265 'routerMac' : attributes['routerMac'],
2266 'nodeSId' : attributes['nodeSid'],
2267 },)
2268 entries = combResult
2269 #raise error.ArgumentValidationError('\n\n\n %s' % (entries))
2270 if 'routerrealtimestats' in data and data['routerrealtimestats'] == 'port':
2271 #raise error.ArgumentValidationError('\n\n\n %s' % (data))
2272 combResult = []
2273 portList = entries
2274 for port in portList:
2275 portData = port.get("port")
2276 name = portData.get("stringAttributes").get('name')
2277 portNo = portData.get("portNumber") & 0xFFFF # converting to unsigned16int
2278 subnetIp = port.get("subnetIp")
2279 adjacency = str(port.get('adjacency'))
2280 combResult.append({
2281 'name' :name,
2282 'portNo' : portNo,
2283 'subnetIp' : subnetIp,
2284 'adjacency' : adjacency,
2285 })
2286 entries = combResult
2287 if 'routerrealtimestats' in data and data['routerrealtimestats'] == 'adjacency':
2288 #raise error.ArgumentValidationError('\n\n\n %s' % (entries))
2289 #raise error.ArgumentValidationError('\n\n\n %s' % (entries))
2290 combResult = []
2291 adjacencyPairList = entries
2292 for adjacencyPair in adjacencyPairList:
2293 adjacencySid = adjacencyPair.get("adjacencySid")
2294 ports = adjacencyPair.get("ports")
2295 combResult.append({
2296 'adjacencySid' : adjacencySid,
2297 'ports' : ports,
2298 })
2299 entries = combResult
2300 #raise error.ArgumentValidationError('\n\n\n %s' % (data))
2301
2302 if 'showtunnel' in data and (data['showtunnel'] == 'tunnel' or data['detail'] == 'details'):
2303 #eraise error.ArgumentValidationError('\n\n\n %s' % (entries))
2304 combResult = []
2305 tunnelList = entries
2306 for tunnel in tunnelList:
2307 labelStackList = (tunnel.get('labelStack'))
2308 labelStackString = str(labelStackList)
2309 labelStackString = remove_unicodes(labelStackString)
2310 #labelStackList = (tunnel.get('labelStack'))
2311 #labelStackString ='['
2312 #for labelSack in labelStackList:
2313 # for label in labelSack:
2314 # labelStackString += (label + ',')
2315 #if labelStackString == '[':
2316 # labelStackString = ''
2317 #else:
2318 # labelStackString = labelStackString[:-1]
2319 # labelStackString += ']'
2320 tunnelId = tunnel.get('tunnelId')
Srikanth Vavilapallib5c3ca52014-12-15 15:59:33 -08002321 tunnelsetId = tunnel.get('tunnelsetId')
Srikanth Vavilapalli1725e492014-12-01 17:50:52 -08002322 tunnelPath = tunnel.get('tunnelPath')
2323 dpidGroup = str(tunnel.get('dpidGroup'))
2324 dpidGroup= remove_unicodes(dpidGroup)
2325 policies = tunnel.get('policies')
2326 combResult.append({
2327 'tunnelId' : tunnelId,
2328 'labelStack' : labelStackString,
2329 'dpidGroup' : dpidGroup,
2330 'tunnelPath' : tunnelPath,
2331 'policies' : policies,
Srikanth Vavilapallib5c3ca52014-12-15 15:59:33 -08002332 'tunnelset' : tunnelsetId,
Srikanth Vavilapalli1725e492014-12-01 17:50:52 -08002333 })
2334 entries = combResult
2335
Srikanth Vavilapallib5c3ca52014-12-15 15:59:33 -08002336 if 'showtunnelset' in data and (data['showtunnelset'] == 'tunnelset' or data['detail'] == 'details'):
2337 #eraise error.ArgumentValidationError('\n\n\n %s' % (entries))
2338 combResult = []
2339 tunnelsetList = entries
2340 for tunnelset in tunnelsetList:
2341 tunnelsetId = tunnelset.get('tunnelsetId')
2342 policies = tunnelset.get('policies')
2343 tunnelList = tunnelset.get('constituentTunnels')
2344 for tunnel in tunnelList:
2345 labelStackList = (tunnel.get('labelStack'))
2346 labelStackString = str(labelStackList)
2347 labelStackString = remove_unicodes(labelStackString)
2348 tunnelId = tunnel.get('tunnelId')
2349 tunnelPath = tunnel.get('tunnelPath')
2350 dpidGroup = str(tunnel.get('dpidGroup'))
2351 dpidGroup= remove_unicodes(dpidGroup)
2352 combResult.append({
2353 'tunnelsetId' : tunnelsetId,
2354 'policies' : policies,
2355 'tunnelId' : tunnelId,
2356 'labelStack' : labelStackString,
2357 'dpidGroup' : dpidGroup,
2358 'tunnelPath' : tunnelPath,
2359 'tunnelset' : tunnelsetId,
2360 })
2361 entries = combResult
2362
Srikanth Vavilapalli1725e492014-12-01 17:50:52 -08002363 if 'showpolicy' in data and data['showpolicy'] == 'policy':
2364 #raise error.ArgumentValidationError('\n\n\n %s' % (data))
2365 combResult = []
2366 portList = entries
2367 for policy in portList:
2368 policyId = policy.get("policyId")
2369 policyType = policy.get("policyType")
2370 priority = policy.get("priority")
2371 tunnelId = policy.get('tunnelId')
2372 match = policy.get("match")
2373 dstIpAddress = match.get('dstIpAddress')['value'] if match.get('dstIpAddress') else '*'
2374 dstMacAddress = match.get('dstMacAddress')['value'] if match.get('dstMacAddress') else '*'
2375 dstTcpPortNumber = match.get('dstTcpPortNumber') if match.get('dstTcpPortNumber') else '*'
2376 etherType = ('0x'+ str(match.get('etherType'))) if match.get('etherType') else '*'
2377 ipProtocolNumber = match.get('ipProtocolNumber') if match.get('ipProtocolNumber') else '*'
2378 srcIpAddress = match.get('srcIpAddress')['value'] if match.get('srcIpAddress') else '*'
2379 srcMacAddress = match.get('srcMacAddress')['value'] if match.get('srcMacAddress') else '*'
2380 srcTcpPortNumber = match.get('srcTcpPortNumber') if match.get('srcTcpPortNumber') else '*'
2381 combResult.append({
2382 'policyId' : policyId,
2383 'policyType' : policyType,
2384 'tunnelId' : tunnelId,
2385 'priority' : priority,
2386 'dstIpAddress' : dstIpAddress,
2387 'dstMacAddress' : dstMacAddress,
2388 'dstTcpPortNumber': dstTcpPortNumber,
2389 'etherType' : etherType,
2390 'ipProtocolNumber': ipProtocolNumber,
2391 'srcIpAddress' : srcIpAddress,
2392 'srcMacAddress' : srcMacAddress,
2393 'srcTcpPortNumber': srcTcpPortNumber,
2394
2395 })
2396 entries = combResult
2397
2398 if 'realtimestats' in data and 'tabletype' in data and data['realtimestats'] == 'table':
2399 combResult = []
2400 if data['tabletype'] == 'ip':
2401 #for decending sorting
2402 descending = True
2403 for ipTableEntry in entries:
2404 match = ipTableEntry['match']
2405 networkDestination = '*'
2406 if match :
2407 networkDestination = match.get('networkDestination') if match.get('networkDestination') else '*'
2408 #raise error.ArgumentValidationError('\n\n\n %s' % json.tool(entries))
2409 instructions = ipTableEntry['instructions']
2410 actions = str(instructions[0]) if instructions[0] else None
2411 if actions != None:
2412 actions = remove_unicodes(actions)
2413 actions = renameActions(actions)
2414 actions = actions.lower()
2415 else:
2416 actions =''
2417 combResult.append({
2418 'switch' : ipTableEntry['switch'],
2419 'byteCount' : ipTableEntry['byteCount'],
2420 'packetCount' : ipTableEntry['packetCount'],
2421 'priority' : ipTableEntry['priority'],
2422 'cookie' : ipTableEntry['cookie'],
2423 'durationSeconds' : ipTableEntry['durationSec'],
2424 'networkDestination' : networkDestination,
2425 'actions' : actions,
2426 })
2427 elif data['tabletype'] == 'mpls':
2428 for ipTableEntry in entries:
2429 match = ipTableEntry['match']
2430 mplsTc = '*'
2431 mplsLabel = '*'
2432 mplsBos = '*'
2433 if match :
2434 mplsTc = match.get('mplsTc') if match.get('mplsTc') else '*'
2435 mplsLabel = match.get('mplsLabel') if match.get('mplsLabel') else '*'
2436 mplsBos = match.get('mplsBos') if match.get('mplsBos') else '*'
2437 instructions = ipTableEntry['instructions']
2438 #raise error.ArgumentValidationError('\n\n\n %s' %len(actions))
2439 actions = str(instructions[0])if instructions[0] else None
2440 if actions != None:
2441 actions = remove_unicodes(actions)
2442 actions = renameActions(actions)
2443 actions = actions.lower()
2444 else:
2445 actions =''
2446 combResult.append({
2447 'switch' : ipTableEntry['switch'],
2448 'byteCount' : ipTableEntry['byteCount'],
2449 'packetCount' : ipTableEntry['packetCount'],
2450 'cookie' : ipTableEntry['cookie'],
2451 'priority' : ipTableEntry['priority'],
2452 'mplsTc' : mplsTc,
2453 'mplsLabel' : mplsLabel,
2454 'mplsBos' : mplsBos,
2455 'durationSeconds' : ipTableEntry['durationSec'],
2456 'actions' : actions
2457 })
2458 elif data['tabletype'] == 'acl':
2459 descending = True
2460 for ipTableEntry in entries:
2461 match = ipTableEntry['match']
2462 networkDestination ='*'
2463 networkProtocol = '*'
2464 networkSource = '*'
2465 mplsTc = '*'
2466 mplsLabel = '*'
2467 mplsBos = '*'
2468 transportDestination = '*'
2469 inputPort = '*'
2470 transportSource = '*'
2471 dataLayerSource = '*'
2472 dataLayerDestination = '*'
2473 dataLayerType = '*'
2474 if match :
2475 networkDestination = match.get('networkDestination') if match.get('networkDestination') else '*'
2476 networkProtocol = match.get('networkProtocol') if match.get('networkProtocol') else '*'
2477 networkSource = match.get('networkSource') if match.get('networkSource') else '*'
2478 mplsTc = match.get('mplsTc') if match.get('mplsTc') else '*'
2479 mplsLabel = match.get('mplsLabel')if match.get('mplsLabel') else '*'
2480 transportDestination = match.get('transportDestination') if match.get('transportDestination') else '*'
2481 transportSource = match.get('transportSource') if match.get('transportSource') else '*'
2482 inputPort = match.get('inputPort') if match.get('inputPort') else '*'
2483 dataLayerSource = match.get('dataLayerSource') if match.get('dataLayerSource') else '*'
2484 dataLayerDestination = match.get('dataLayerDestination') if match.get('dataLayerDestination') else '*'
2485 dataLayerType= match.get('dataLayerType') if match.get('dataLayerType') else '*'
2486 mplsBos = match.get('mplsBos') if match.get('mplsBos') else '*'
2487 instructions = ipTableEntry['instructions']
2488 actions = str(instructions[0])if instructions[0] else None
2489 if actions != None:
2490 actions = remove_unicodes(actions)
2491 actions = renameActions(actions)
2492 actions = actions.lower()
2493 else:
2494 actions = ''
2495 combResult.append({
2496 'switch' : ipTableEntry['switch'],
2497 'byteCount' : ipTableEntry['byteCount'],
2498 'packetCount' : ipTableEntry['packetCount'],
2499 'cookie' : ipTableEntry['cookie'],
2500 'priority' : ipTableEntry['priority'],
2501 'inputPort' : inputPort,
2502 'durationSeconds' : ipTableEntry['durationSec'],
2503 'networkSource' : networkSource,
2504 'networkDestination' : networkDestination,
2505 'networkProtocol' : networkProtocol,
2506 'dataLayerType' : dataLayerType,
2507 'dataLayerSource' : dataLayerSource,
2508 'dataLayerDestination' : dataLayerDestination,
2509 'mplsTc' : mplsTc,
2510 'mplsLabel' : mplsLabel,
2511 'mplsBos' : mplsBos,
2512 'transportDestination' : transportDestination,
2513 'transportSource' : transportSource,
2514 'actions' : actions
2515 })
2516 entries = combResult
2517
2518 if 'realtimestats' in data and data['realtimestats'] == 'group':
2519 combResult = []
2520 for groupStatEntry in entries:
2521 groupId = groupStatEntry["groupId"]
2522 groupDescEntry = None
2523 for entry in entries2:
2524 if groupId == entry["groupId"]:
2525 groupDescEntry = entry
2526 break
2527 if groupDescEntry is '':
2528 print "command_display_rest: missing group desc for group id %s" % (groupId)
2529 continue
2530
2531 if (len(groupStatEntry['bucketStats']) > 0):
2532 for bucketId in range(len(groupStatEntry['bucketStats'])):
2533 setsrcmac = ''
2534 if 'SET_DL_SRC' in groupDescEntry['bucketsActions'][bucketId]:
2535 setsrcmac = groupDescEntry['bucketsActions'][bucketId]['SET_DL_SRC']
2536 setdstmac = ''
2537 if 'SET_DL_DST' in groupDescEntry['bucketsActions'][bucketId]:
2538 setdstmac = groupDescEntry['bucketsActions'][bucketId]['SET_DL_DST']
2539 pushmpls = ''
2540 if 'PUSH_MPLS_LABEL' in groupDescEntry['bucketsActions'][bucketId]:
2541 pushmpls = groupDescEntry['bucketsActions'][bucketId]['PUSH_MPLS_LABEL']
2542 popmpls = ''
2543 if 'POP_MPLS' in groupDescEntry['bucketsActions'][bucketId]:
2544 popmpls = groupDescEntry['bucketsActions'][bucketId]['POP_MPLS']
2545 outport = ''
2546 if 'OUTPUT' in groupDescEntry['bucketsActions'][bucketId]:
2547 outport = groupDescEntry['bucketsActions'][bucketId]['OUTPUT']
2548 goToGroup = ''
2549 if 'goToGroup' in groupDescEntry['bucketsActions'][bucketId]:
2550 goToGroup = groupDescEntry['bucketsActions'][bucketId]['goToGroup']
2551 setBos= ''
2552 if 'PUSH_MPLS_BOS' in groupDescEntry['bucketsActions'][bucketId]:
2553 setBos = groupDescEntry['bucketsActions'][bucketId]['PUSH_MPLS_BOS']
2554 COPY_TTL_IN= ''
2555 if 'COPY_TTL_IN' in groupDescEntry['bucketsActions'][bucketId]:
2556 COPY_TTL_IN = groupDescEntry['bucketsActions'][bucketId]['COPY_TTL_IN']
2557 COPY_TTL_OUT= ''
2558 if 'COPY_TTL_OUT' in groupDescEntry['bucketsActions'][bucketId]:
2559 COPY_TTL_OUT = groupDescEntry['bucketsActions'][bucketId]['COPY_TTL_OUT']
2560 DEC_MPLS_TTL= ''
2561 if 'DEC_MPLS_TTL' in groupDescEntry['bucketsActions'][bucketId]:
2562 DEC_MPLS_TTL = groupDescEntry['bucketsActions'][bucketId]['DEC_MPLS_TTL']
2563 DEC_NW_TTL= ''
2564 if 'DEC_NW_TTL' in groupDescEntry['bucketsActions'][bucketId]:
2565 DEC_NW_TTL = groupDescEntry['bucketsActions'][bucketId]['DEC_NW_TTL']
2566
2567 combResult.append({
2568 'groupid' : groupId,
2569 'grouptype' : groupDescEntry['groupType'],
2570 'totalpktcnt' : groupStatEntry['packetCount'],
2571 'totalbytecnt' : groupStatEntry['byteCount'],
2572 'bucketpktcnt' : groupStatEntry['bucketStats'][bucketId]['pktCount'],
2573 'bucketbytecnt' : groupStatEntry['bucketStats'][bucketId]['byteCount'],
2574 'setsrcmac' : setsrcmac,
2575 'setdstmac' : setdstmac,
2576 'pushMplsLabel' : pushmpls,
2577 'popmpls' : popmpls,
2578 'outport' : outport,
2579 'goToGroup' : goToGroup,
2580 'setBos' : setBos,
2581 'COPY_TTL_IN' : COPY_TTL_IN,
2582 'COPY_TTL_OUT' : COPY_TTL_OUT,
2583 'DEC_MPLS_TTL' : DEC_MPLS_TTL,
2584 'DEC_NW_TTL' : DEC_NW_TTL,
2585 })
2586 else:
2587 combResult.append({
2588 'groupid' : groupId,
2589 'grouptype' : groupDescEntry['groupType'],
2590 'totalpktcnt' : groupStatEntry['packetCount'],
2591 'totalbytecnt' : groupStatEntry['byteCount'],
2592 'bucketpktcnt' : '',
2593 'bucketbytecnt' : '',
2594 'setsrcmac' : '',
2595 'setdstmac' : '',
2596 'pushMplsLabel' : '',
2597 'popmpls' : '',
2598 'outport' : '',
2599 'goToGroup' : '',
2600 'setBos' : '',
2601 'COPY_TTL_IN' : '',
2602 'COPY_TTL_OUT' : '',
2603 'DEC_MPLS_TTL' : '',
2604 'DEC_NW_TTL' : '',
2605 })
2606 entries = combResult
2607 #
2608 if format:
2609 #
2610 detail = command_display_rest_join_entries(table_format, data, entries, detail)
2611 #if 'realtimestats' in data and data['realtimestats'] == 'flow':
2612 # entries = sdnsh.fix_realtime_flows(entries)
2613 # check_single_entry = False
2614
2615 if 'realtimestats' in data and data['realtimestats'] == 'features':
2616 for entry in entries:
2617 entry['stp-state'] = entry['state']
2618
2619 # update any of the pretty-printer tables based on the table_format (obj_type)
2620 obj_type_show_alias_update(table_format % data)
2621
2622 if check_single_entry and entries and len(entries) == 1 and detail == 'details':
2623 return sdnsh.pp.format_entry(entries[0],
2624 table_format % data,
2625 detail,
2626 sdnsh.debug)
2627 if sort:
2628 if descending:
2629 reverse = True
2630 else:
2631 reverse = False
2632 def sort_cmp(x,y):
2633 for f in sort:
2634 if f in x:
2635 c = cmp(x.get(f), y.get(f))
2636 if c != 0:
2637 return c
2638 return 0
2639 entries = sorted(entries, cmp=sort_cmp, reverse=reverse )
2640 if 'realtimestats' in data and data['realtimestats'] == 'group':
2641 repeatGroupId = -1
2642 length = len(entries)
2643 for i in range(0, length):
2644 entry = entries[i]
2645 groupId = entry.get('groupid')
2646 if groupId == repeatGroupId:
2647 entries[i]['groupid'] = ''
2648 else:
2649 repeatGroupId = groupId
2650
2651 display = sdnsh.pp.format_table(entries, table_format % data, detail)
2652 else:
2653 display = entries
2654
2655 if title:
2656 return title + display
2657 return display
2658
2659
2660def command_crack(field):
2661 """
2662 Part of the show pipeline, split is typically used with rest api's
2663 not associated with the model (database), since the cli has enough
2664 details of the relationships between model fields to understand
2665 which of the fields has a compound key value, and has options to
2666 crack those into component parts.
2667
2668 The operation is called 'crack' (not split), since the other
2669 options for some of the actions is called 'crack'
2670
2671 The field identifies the name of the field in the entry to
2672 split into parts, and the remaining '|' separated fields list
2673 the labels to associate in the result from each of the
2674 split components. Currently, the 'crack' character is '|',
2675 although this could be parameterized.
2676 """
2677 if sdnsh.description: # description debugging
2678 print "command_split: ", field
2679
2680 if hasattr(command, 'query_result'):
2681 entries = command.query_result
2682 if command.query_result == None:
2683 entries = []
2684 else:
2685 if sdnsh.description: # description debugging
2686 print "command_join_table: no entries found"
2687 entries = []
2688
2689 parts = field.split('|')
2690 if len(parts) == 0:
2691 if sdnsh.description: # description debugging
2692 print "command_join_table: field doesn't contain labels" \
2693 " use field|label1|label2|..."
2694 return
2695
2696 field = parts[0]
2697 label = parts[1:]
2698 many = len(label)
2699
2700 for entry in entries:
2701 if field in entry:
2702 parts = entry[field].split('|')
2703 if len(parts) and many >= len(parts) :
2704 # use enumerate to create a tuple for each item in parts,
2705 # assocaiting an index, which can be used to identify the
2706 # label to use for each of the elements; from that create
2707 # a dictionay, which is then used to update the entry
2708 entry.update(dict([[label[n],p] for (n,p) in enumerate(parts)]))
2709
2710
2711def command_display(data, table_format, detail = 'default', sort = None, title = None):
2712
2713 if sdnsh.description: # description debugging
2714 print "command_display: ", data, table_format, detail
2715
2716 if 'detail' in data:
2717 detail = data['detail']
2718
2719 if hasattr(command, 'query_result'):
2720 entries = command.query_result
2721 if command.query_result == None:
2722 entries = []
2723 else:
2724 if sdnsh.description: # description debugging
2725 print "command_join_table: no entries found"
2726 entries = []
2727
2728 if sdnsh.description: # description debugging
2729 print "command_display: #entries ", len(entries)
2730
2731 # XXX controller-node has an odd url, join-rest needs to be able to
2732 # be handed a complete url, and replace the ip address with the controller's
2733 # ip address.
2734 detail = command_display_table_join_entries(table_format, data, entries, detail)
2735
2736 # update any of the pretty-printer tables based on the table_format (obj_type)
2737 obj_type_show_alias_update(table_format)
2738
2739 # with_key manages whether a 'detail' or table is displayed.
2740 with_key = '<with_key>' if detail == 'details' and len(entries) > 0 else '<no_key>'
2741
2742 #
2743 if sort:
2744 def sort_cmp(x,y):
2745 for f in sort:
2746 if f in x:
2747 c = utif.trailing_integer_cmp(x.get(f),y.get(f))
2748 if c:
2749 return c
2750 return 0
2751 entries = sorted(entries, cmp=sort_cmp)
2752
2753 # use display_obj_type_rows since it (currently) joins fields for obj_types.
2754 display = sdnsh.display_obj_type_rows(table_format, entries, with_key, detail)
2755
2756 if title:
2757 return title + display
2758 return display
2759
2760
2761def command_legacy_cli(obj_type, data, detail = 'default', scoped = None, sort = None):
2762 """
2763 Unfortunatly, the command descriptions don't have enough different
2764 detail to describe how to join specific distinct fields. In the future,
2765 there will be rest api's for each of the cli requests; that should cause
2766 this trampoline code to become obsolete.
2767 """
2768
2769 if sdnsh.description: # description debugging
2770 print "command_legacy_cli: ", obj_type, data, detail, scoped, sort
2771
2772 # update any of the pretty-printer tables based on the obj_type
2773 obj_type_show_alias_update(obj_type)
2774
2775 #
2776 #
2777 # Various show command 'join' data to create a table not
2778 # directly available in the REST API, someday in the future,
2779 # these joins will be directly implemented in the REST API,
2780 # but these special cases still exist:
2781 #
2782 if 'running-config' in data:
2783 result = sdnsh.error_msg("No running-config choice")
2784 words = []
2785 if 'word' in data and data['word'] != 'all':
2786 words = [data['word']]
2787
2788 if data['running-config'] == 'running-config':
2789 # 'show vns XXX running-config'
2790 if 'vnsname' in data and data['vnsname'] != 'all':
2791 return sdnsh.show_vns_running_config(data['vnsname'],data['tenant'])
2792 elif 'vns' in data and data['vns']=='all':
2793 data['running-config'] = 'vns'
2794 elif 'tenant' in data:
2795 data['running-config']='tenant'
2796 words=[data['tenant']]
2797 if data['running-config'] in run_config.registry_items_enabled():
2798 result = run_config.perform_running_config(data['running-config'], sdnsh, config, words)
2799
2800 if result:
2801 return result
2802 return ''.join(config)
2803
2804 if obj_type == 'running-config':
2805 return run_config.implement_show_running_config([])
2806
2807 if obj_type == 'vns-interface':
2808 if scoped:
2809 # should check for missing 'id' in data
2810 data['vns'] = sdnsh.get_current_mode_obj()
2811
2812 if 'vns' in data:
2813 if data['vns'] == 'all':
2814 return sdnsh.display_vns_interface(None, {}, '<no_key>')
2815 vns_name=data['vns']
2816 return sdnsh.display_vns_interface(vns_name, {'vns': vns_name },
2817 '<no_key>', detail = 'scoped')
2818
2819 if obj_type == 'vns-switch-ports':
2820 if 'vns' in data:
2821 return sdnsh.show_vns_switch_ports([data['vns']])
2822 return sdnsh.show_vns_switch_ports([])
2823
2824 if obj_type == 'switch-ports-vns':
2825 if 'dpid' in data:
2826 return sdnsh.show_switch_ports_vns([data['dpid']])
2827 return sdnsh.show_switch_ports_vns([])
2828
2829 if obj_type == 'switch-interfaces':
2830 key = mi.pk(obj_type)
2831 if scoped:
2832 data['dpid'] = sdnsh.get_current_mode_obj()
2833
2834 # in legacy_cli to join the switch-interfaces with port stats
2835 port_obj = 'port'
2836 entries = sdnsh.show_sort_obj_type(obj_type,
2837 command_query_object(port_obj, data, scoped, sort))
2838
2839 # switch-interfaces is really class Port, and the primary key
2840 # is '#|switch|number, not name.
2841
2842 entries_dict = dict([['%s|%s' % (x['switch'], x['name']), x] for x in entries])
2843 # collect switch-interface-config
2844 sic = 'switch-interface-config'
2845 if 'dpid' in data and data['dpid'] != 'all':
2846 sic_dict = create_obj_type_dict(sic, mi.pk(sic), mi.pk(sic), data['dpid'])
2847 else:
2848 sic_dict = create_obj_type_dict(sic, mi.pk(sic))
2849
2850 # add switch-interface-config names when missing
2851 for (sic_id, sic_value) in sic_dict.items():
2852 if not sic_id in entries_dict:
2853 # add 'state' to this item for prettyprinting column width computation
2854 for sv in sic_value:
2855 sv['state'] = ''
2856 entries += sic_value
2857
2858 # collect the stats for the interfaces
2859 stats_url = 'realtimestats/port/%(dpid)s/' % data
2860 url = "http://%s/rest/v1/" % sdnsh.controller + stats_url
2861 try:
2862 result = sdnsh.store.rest_simple_request(url)
2863 check_rest_result(result)
2864 stats = json.loads(result)
2865
2866 except Exception, e:
2867 stats = {}
2868
2869 # join realtimestats
2870 for entry in entries:
2871 if 'state' in entry:
2872 entry['stp-state'] = entry['state']
2873 stats_list = stats.get(entry['switch'])
2874 # Note, 'number' may be missing from entry if the switch
2875 # matches for switch-interface-config but the interface name
2876 # doesn't show up.
2877 if stats_list and 'number' in entry:
2878 ifn = entry['number']
2879 # Notice that the realtime stat's use a int for the 2^16 value here
2880 # The & 0xffff converts the "-x" to a positive 2^16 value
2881 item = [x for x in stats_list if (x['portNumber'] & 0xffff) == ifn]
2882 if len(item) == 1:
2883 entry.update(item[0])
2884 if entry['id'] in sic_dict:
2885 entry.update(sic_dict[entry['id']][0])
2886
2887 # Update the alias mappings for display
2888 obj_type_show_alias_update(obj_type)
2889
2890 return sdnsh.pp.format_table(entries, obj_type, detail)
2891
2892 if obj_type == 'tunnel-interfaces':
2893 # Use the active tunnels to identify the interfaces on the
2894 # switches which are the tunneling interfaces, with that
2895 # collect to port -> if_name mappings from 'port', then
2896 # find all the switches interfaces, convert those port numbers to
2897 # if names, to collect only tunneling interfaces. Now collect
2898 # realtimestats for the switch's ports, and associate those
2899 # stats with any filtered interfaces, finally display the result
2900 tunnel_url = "tunnel-manager/%(dpid)s" % data
2901 url = "http://%s/rest/v1/" % sdnsh.controller + tunnel_url
2902 result = sdnsh.store.rest_simple_request(url)
2903 check_rest_result(result)
2904 tunnels = json.loads(result)
2905
2906 # use the active tunnels to
2907 # collect dpid's, convert the remote ip's to interface names.
2908 tunnel_ports = {}
2909 for t in tunnels:
2910 quad = t['tunnelPorts'].split('.')
2911 if_name = "vta%03d%03d%03d%03d" % (int(quad[0]), int(quad[1]),
2912 int(quad[2]), int(quad[3]))
2913 key = "%s|%s" % (t['dpid'], if_name)
2914 if not key in tunnel_ports:
2915 tunnel_ports[key] = {t['dpid']: t['tunnelPorts']}
2916
2917 # Collect interfaces on associated switch
2918 port_obj = 'port'
2919 entries = sdnsh.show_sort_obj_type(port_obj,
2920 command_query_object(port_obj, data, scoped, sort))
2921 # Associate port names with interface names
2922 port_to_if_name = {}
2923
2924 try:
2925 ports = sdnsh.get_table_from_store("port")
2926 except Exception, e:
2927 port = []
2928
2929 for port in ports:
2930 key_string = '%s|%s' % (port['switch'], port['number'])
2931 port_to_if_name[key_string] = port['name']
2932
2933 # Filter elements, 'filtered' only contains active tunnel interfaces
2934 filtered = []
2935 for e in entries:
2936 e['ifname'] = port_to_if_name[e['id']]
2937 key = '%s|%s' % (e['switch'], e['ifname'])
2938 if sdnsh.description: # description debugging
2939 print command._line(), key
2940 if key in tunnel_ports:
2941 if sdnsh.description: # description debugging
2942 print command._line(), "Found ", e['id']
2943 filtered.append(e)
2944 entries = filtered
2945
2946 # collect switch-interface-config
2947 sic = 'switch-interface-config'
2948 if 'dpid' in data:
2949 sic_dict = create_obj_type_dict(sic, mi.pk(sic), mi.pk(sic), data['dpid'])
2950 else:
2951 sic_dict = create_obj_type_dict(sic, mi.pk(sic))
2952
2953 # collect the stats for the interfaces
2954 stats_url = 'realtimestats/port/%(dpid)s/' % data
2955 url = "http://%s/rest/v1/" % sdnsh.controller + stats_url
2956 try:
2957 result = sdnsh.store.rest_simple_request(url)
2958 check_rest_result(result)
2959 stats = json.loads(result)
2960 except Exception, e:
2961 stats = {}
2962
2963 # join realtimestats
2964 for entry in entries:
2965 if 'state' in entry:
2966 entry['stp-state'] = entry['state']
2967 stats_list = stats.get(entry['switch'])
2968 if stats_list and 'number' in entry:
2969 ifn = entry['number']
2970 # Notice that the realtime stat's use a int for the 2^16 value here
2971 # The & 0xffff converts the "-x" to a positive 2^16 value
2972 item = [x for x in stats_list if (x['portNumber'] & 0xffff) == ifn]
2973 if len(item) == 1:
2974 entry.update(item[0])
2975 if entry['id'] in sic_dict:
2976 entry.update(sic_dict[entry['id']][0])
2977
2978 obj_type_show_alias_update('switch-interfaces')
2979
2980 return sdnsh.pp.format_table(entries, 'switch-interfaces', detail)
2981
2982 if obj_type == 'host-vns-interface-vns':
2983 words = []
2984 for w in []: # list of options to display_vns_mac_address_table
2985 if w in data:
2986 words[w] = data[w]
2987
2988 return sdnsh.display_vns_mac_address_table(data['vns'], words)
2989
2990 if obj_type == 'config':
2991 if 'config' in data:
2992 if 'version' in data:
2993 return sdnsh.implement_show_config([data['config'],data['version']])
2994 return sdnsh.implement_show_config([data['config']])
2995
2996 if 'config-diff' in data:
2997 if 'version' in data:
2998 return sdnsh.implement_show_config([ data['first'],
2999 'diff',
3000 data['second'],
3001 data['version']])
3002 return sdnsh.implement_show_config([data['first'],
3003 'diff',
3004 data['second'], ])
3005 return sdnsh.implement_show_config([])
3006
3007 if obj_type == 'vns-flow':
3008 if 'detail' in data:
3009 return sdnsh.show_vns_flow_annotated([data['vns'],
3010 'flow',
3011 data['detail']])
3012 return sdnsh.show_vns_flow_annotated([data['vns'], 'flow'])
3013
3014 if obj_type == 'tech-support':
3015 return sdnsh.do_show_tech_support([])
3016
3017 if obj_type == 'config-file':
3018 if 'file' in data:
3019 return sdnsh.implement_show_config_file(['config-file', data['config']])
3020 return sdnsh.implement_show_config_file(['config-file', ])
3021
3022 if obj_type == 'logging':
3023 if 'log-name' in data:
3024 return sdnsh.implement_show_logging([data['log-name']])
3025 return sdnsh.implement_show_logging([])
3026
3027 if obj_type == 'event-history':
3028 if 'count' in data:
3029 return sdnsh.do_show_event_history([data['event'],
3030 'last',
3031 str(data['count'])])
3032 return sdnsh.do_show_event_history([data['event']])
3033
3034 if obj_type == 'flow-cache':
3035 words = []
3036 if 'counters' in data:
3037 words.append('counters')
3038 elif 'application' in data:
3039 words.append('app')
3040 words.append(data['application'])
3041 words.append('app-instance')
3042 words.append(data['instance'])
3043
3044 return sdnsh.do_show_flow_cache(words)
3045
3046 if obj_type in ['controller-stats', 'switch-stats']:
3047 #
3048 # data['id'] is the name of the controller
3049 helper_item = obj_type.replace('-stats','')
3050 if helper_item == 'controller':
3051 helper_item = 'controller-node'
3052 key = mi.pk(helper_item)
3053 words = [helper_item, data[key], 'stats']
3054 if 'stats-type' in data:
3055 words.append(data['stats-type'])
3056 for (n,v) in data.items():
3057 if not n in [key, 'stats', 'stats-type']:
3058 words.append(n)
3059 words.append(v)
3060 return sdnsh.helper_show_object_stats(words)
3061
3062 if obj_type == 'switch-tcpdump':
3063 words = ['trace', data['dpid']]
3064 for (n,v) in data.items():
3065 if not n in ['tcpdump', 'dpid']:
3066 words.append(n)
3067 return sdnsh.do_trace(words)
3068
3069 if obj_type == 'copy':
3070 words = [data['source']]
3071 if 'dest' in data:
3072 words.append(data['dest'])
3073 return sdnsh.implement_copy(words)
3074
3075 if obj_type == 'write':
3076 return sdnsh.implement_write([data['target']])
3077
3078 if obj_type == 'this':
3079 obj_type = sdnsh.get_current_mode_obj_type()
3080 show_this = mi.obj_type_show_this(obj_type)
3081 if not show_this:
3082 return sdnsh.do_show_object(['this'])
3083 result = []
3084 for show in show_this:
3085 if type(show) is list and len(show) >= 3:
3086 # [ object, format, detail ]
3087 if len(result) > 0:
3088 result.append(mi.obj_type_show_title(show[0]))
3089 sort = None
3090 if len(show) > 3:
3091 sort = show[3]
3092 result.append(command_display_table(show[0], {},
3093 table_format = show[1],
3094 detail = show[2],
3095 sort = sort,
3096 scoped = True))
3097 elif type(show) is list and len(show) == 2:
3098 # [ object, detail ]
3099 if len(result) > 0:
3100 result.append(mi.obj_type_show_title(show[0]))
3101 result.append(command_display_table(show[0], {}, detail = show[1], scoped = True))
3102 else:
3103 result.append(sdnsh.do_show_object([show]))
3104 return '\n'.join(result)
3105
3106 if obj_type == 'version':
3107 return sdnsh.do_show_version([])
3108
3109 if obj_type == 'reload':
3110 return sdnsh.implement_reload()
3111
3112 if obj_type == 'test-command':
3113 if data['test-type'] == 'packet-in':
3114 return sdnsh.implement_test_packet_in(data)
3115 if data['test-type'] == 'path':
3116 return sdnsh.implement_test_path(data)
3117
3118 print 'command_legacy_cli: obj-type unknown: ', obj_type
3119
3120
3121def command_legacy_cli_no(obj_type, data, detail = 'default', scoped = None, sort = None):
3122 """
3123 Implement no command for trampoline code back to the original code
3124 """
3125 if obj_type == 'tag-mapping':
3126 return sdnsh.implement_no_tag(['tag', data['tag']])
3127
3128
3129def command_version(data):
3130 """
3131 The version command will later manage changing the syntax to match
3132 the requested version.
3133 """
3134 new_version = data.get('version')
3135 if new_version == None:
3136 return
3137
3138 version = new_version # save for error message
3139 new_version = sdnsh.desc_version_to_path_elem(new_version)
3140
3141 # skip version change is this is the current version.
3142 if sdnsh.desc_version == new_version:
3143 return
3144
3145 # see if the requested version exists
3146 if not sdnsh.command_packages_exists(new_version):
3147 print 'No command description group for version %s' % version
3148 return
3149
3150 # run 'env [envriron_vars] ... cli.py'
3151 command = ['env']
3152 command.append('CLI_COMMAND_VERSION=%s' % version)
3153 command.append('CLI_STARTING_MODE=config')
3154 if os.path.exists('/opt/sdnplatform/cli/bin/cli'):
3155 # controller VM
3156 command.append('/opt/sdnplatform/cli/bin/cli --init')
3157 else:
3158 # developer setup
3159 base = os.path.dirname(__file__)
3160 command.append(os.path.join(base, 'cli.py'))
3161 command.append('--init')
3162
3163 # dump the command descriptions, and read a new set.
3164 # open a subshell with a new command version
3165 subprocess.call(command, cwd=os.environ.get("HOME"))
3166
3167 return
3168
3169
3170def command_clearterm():
3171 """
3172 Print reset characters to the screen to clear the console
3173 """
3174 subprocess.call("reset")
3175
3176def command_display_cli(data):
3177 """
3178 Display various cli details
3179 (this may need to be re-factored into some general "internal" state show
3180 """
3181 debug = []
3182 if sdnsh.debug:
3183 debug.append('debug')
3184 if sdnsh.debug_backtrace:
3185 debug.append('backtrace')
3186
3187 modes = sdnsh.command_dict.keys() + sdnsh.command_nested_dict.keys()
3188
3189 entry = {
3190 'version' : ', '.join(command.command_syntax_version.keys()),
3191 'desc' : ', '.join(sorted(command.command_added_modules.keys())),
3192 'format' : ', '.join(sorted(sdnsh.pp.format_added_modules.keys())),
3193 'modes' : ', '.join(sorted(utif.unique_list_from_list(modes))),
3194 'debug' : ', '.join(debug),
3195 }
3196 basic = sdnsh.pp.format_entry(entry, 'cli')
3197
3198 mode_entries = command.command_submode_dictionary(modes)
3199 mode_table = sdnsh.pp.format_table(mode_entries, 'cli-modes')
3200
3201 return basic + '\n\nCommand Submode Transition\n' + mode_table
3202
3203 return
3204
3205
3206def delete_alias_by_id(alias_obj_type, alias_value):
3207 """
3208 Common delete operation for alias, based on primary key
3209
3210 @param alias_obj_type string, name of table where single entry is removed
3211 @param alias_value string, value of primary key to delete
3212 """
3213 xref = mi.foreign_key_xref.get(alias_obj_type)
3214 if xref:
3215 # look for any referecnes to this alias_value. Since this
3216 # is an alias table, only the pk ought to exist in the xref.
3217 # When the alias is getting removed, any references to it
3218 # via foreign keys must also get removed.
3219 if len(xref) > 1 or not mi.pk(alias_obj_type) in xref:
3220 print 'Internal Inconsistency'
3221 else:
3222 for (fk_obj_type, fk_field) in xref[mi.pk(alias_obj_type)]:
3223 rows = sdnsh.get_table_from_store(fk_obj_type,
3224 fk_field,
3225 alias_value,
3226 'exact')
3227 for row in rows:
3228 sdnsh.rest_delete_object(fk_obj_type, row[mi.pk(fk_obj_type)])
3229 sdnsh.rest_delete_object(alias_obj_type, alias_value)
3230
3231
3232def delete_alias_by_fk(alias_obj_type, foreign_key):
3233 """
3234 Common delete operation for alias, by foreign key
3235
3236 @param alias_obj_type string, name of table where single entry is removed
3237 @param alias_value string, value of primary key to delete
3238 """
3239 # find all the id's based on the foreign key, then delete them all.
3240 # note: see similar midw alias_lookup_with_foreign_key()
3241
3242 foreign_field = mi.alias_obj_type_field(alias_obj_type)
3243 try:
3244 rows = sdnsh.get_table_from_store(alias_obj_type,
3245 foreign_field,
3246 foreign_key,
3247 "exact")
3248 except Exception, e:
3249 raise error.CommandInternalError("Can't fetch %s:%s" %
3250 (foreign_field, foreign_key))
3251 pk = mi.pk(alias_obj_type)
3252 for row in rows:
3253 delete_alias_by_id(alias_obj_type, row[pk])
3254
3255
3256def command_delete_alias(obj_type, data):
3257 """
3258 Action for delete-alias
3259
3260 A single row is deleted from an alias table.
3261 Current alias tables include host-alias, switch-alias, port-alias
3262
3263 @param obj_type string, name of alias table to manage
3264 @param data dict, collection of field:value's from command description
3265 """
3266 if sdnsh.description: # description debugging
3267 print "command_delete_alias: ", obj_type, data
3268
3269 parent_id = sdnsh.get_current_mode_obj()
3270
3271 key = mi.pk(obj_type)
3272 if key not in data:
3273 delete_alias_by_fk(obj_type, parent_id)
3274 else:
3275 delete_alias_by_id(obj_type, data[key])
3276
3277
3278def command_create_alias(obj_type, data, reserved = None, fail_if_exists = False):
3279 """
3280 Action for create-alias
3281
3282 Current alias tables include host-alias, switch-alias, port-alias
3283
3284 @param obj_type string, name of alias table to manage
3285 @param data dict, collection of field:value's from the command description
3286 """
3287 if sdnsh.description: # description debugging
3288 print "command_create_alias: ", obj_type, data, reserved, fail_if_exists
3289
3290 parent_obj_type = sdnsh.get_current_mode_obj_type()
3291 parent_id = sdnsh.get_current_mode_obj()
3292
3293 key = mi.pk(obj_type)
3294 if key not in data:
3295 raise error.CommandInternalError("Alias table '%s': description "
3296 "doesn't populate correct '%s' field as data" %
3297 (obj_type, key))
3298 alias_value = data[key]
3299 #
3300 # Determine if the alias name is allowed.
3301 if alias_value in sdnsh.reserved_words:
3302 raise error.ArgumentValidationError('reserved name "%s" in "%s"'
3303 % (alias_value, ','.join(sdnsh.reserved_words)))
3304 if reserved and type(reserved) != list:
3305 reserved = [reserved]
3306
3307 if reserved and alias_value in reserved:
3308 raise error.ArgumentValidationError('reserved name "%s" in "%s"'
3309 % (alias_value, ','.join(reserved)))
3310
3311 # Walk the foreign key's in the (alias) obj-type, looking
3312 # for the parent reference.
3313
3314 alias_fk = None
3315 obj_type_foreign_keys = mi.obj_type_foreign_keys(obj_type)
3316 if len(obj_type_foreign_keys) == 1:
3317 alias_fk = obj_type_foreign_keys[0]
3318 else:
3319 for alias_fn in obj_type_foreign_keys:
3320 (fk_ot, fk_fn) = mi.foreign_key_references(obj_type, alias_fn)
3321 if fk_ot == parent_obj_type:
3322 alias_fk = alias_fn
3323
3324 if not alias_fk:
3325 raise error.CommandInternalError("Alias table '%s' has no foreign key to '%s'" %
3326 (obj_type, parent_obj_type))
3327
3328 try:
3329 sdnsh.get_object_from_store(obj_type, alias_value)
3330 if sdnsh.description: # description debugging
3331 print "command_create_alias: delete ", obj_type, alias_value
3332 if fail_if_exists:
3333 raise error.ArgumentValidationError("Interface name '%s' already in use - cannot reassign" %(alias_value))
3334 delete_alias_by_id(obj_type, alias_value)
3335 except:
3336 pass
3337
3338 # Remove other existing alias for the same foreign key
3339 # (ie: only one alias per each item, this could be relaxed)
3340 # XXX improve method of managing errors here
3341 try:
3342 rows = sdnsh.get_table_from_store(obj_type,
3343 alias_fk,
3344 parent_id,
3345 "exact")
3346 except Exception, e:
3347 errors = sdnsh.rest_error_to_dict(e)
3348 print sdnsh.rest_error_dict_to_message(errors)
3349 rows = []
3350
3351 for row in rows:
3352 try:
3353 delete_alias_by_id(obj_type, row[key])
3354 if row[alias_fk] != parent_id:
3355 sdnsh.warning("Removed additional alias '%s'"
3356 ", also refers to %s '%s'" %
3357 (row[key], parent_obj_type, parent_id))
3358 except:
3359 if sdnsh.debug or sdnsh.debug_backtrace:
3360 traceback.print_exc()
3361
3362 # This set's the foreign key to allow the create to succeed
3363 c_dict = {
3364 key : alias_value,
3365 alias_fk : parent_id,
3366 }
3367
3368 if sdnsh.description: # description debugging
3369 print "command_create_alias: create ", obj_type, c_dict
3370 result = sdnsh.rest_create_object(obj_type, c_dict)
3371 check_rest_result(result)
3372 result = sdnsh.rest_query_objects(obj_type, c_dict)
3373 check_rest_result(result)
3374
3375 return None
3376
3377
3378def command_create_tag(obj_type, data):
3379 """
3380 obj_type needs to be one of the objects which implements
3381 a relationship to 'tag', for example: tag-mac-mapping
3382 """
3383
3384 item = sdnsh.get_current_mode_obj_type()
3385 fks = mi.obj_type_foreign_keys(obj_type)
3386 for fk in fks:
3387 (fk_obj, fk_name) = mi.foreign_key_references(obj_type, fk)
3388 if fk_obj == item:
3389 break
3390 else:
3391 raise error.CommandSemanticError( "type mapping %s doesn't have "
3392 "relationship to the current object %s" %
3393 (obj_type, item))
3394
3395 if sdnsh.description: # description debugging
3396 print "command_create_tag: create ", obj_type, data
3397
3398 tag_and_value = data['tag'].split('=')
3399 if len(tag_and_value) != 2:
3400 # deal with tag_and_value's 'va=vb=vc...'
3401 raise error.CommandSemanticError("tag <[tag-namespace.]name>=<value> "
3402 ": associate tag with host")
3403
3404 tag_parts = tag_and_value[0].split('.')
3405 if len(tag_parts) == 0:
3406 raise error.CommandSemanticError("tag <[tag-namespace.]name>"
3407 ": must have a name")
3408 elif len(tag_parts) == 1:
3409 tag_namespace = "default"
3410 tag_name = tag_parts[0]
3411 elif len(tag_parts) >= 2:
3412 # the tag_name is not allowed to have '.'
3413 # collect all the '.'s together into the namespace
3414 tag_namespace = '.'.join(tag_parts[:-1])
3415 tag_name = tag_parts[-1]
3416
3417 tag_value = tag_and_value[1]
3418
3419 # first manage the tag ...
3420 tag_dict = {
3421 'namespace' : tag_namespace,
3422 'name' : tag_name,
3423 'value' : tag_value,
3424 }
3425
3426 query = sdnsh.rest_query_objects('tag', tag_dict)
3427 sdnsh.check_rest_result(query)
3428 tag_dict['persist'] = True
3429 if len(query) == 0:
3430 result = sdnsh.rest_create_object('tag', tag_dict)
3431 sdnsh.check_rest_result(result)
3432 elif len(query) == 1:
3433 update = sdnsh.rest_update_object('tag',
3434 mi.pk('tag'),
3435 query[0][mi.pk('tag')],
3436 tag_dict)
3437 sdnsh.check_rest_result(update)
3438
3439 del tag_dict['persist']
3440 query = sdnsh.rest_query_objects('tag', tag_dict)
3441 sdnsh.check_rest_result(query)
3442 tag_id = query[0][mi.pk('tag')]
3443
3444 # now create the tag-mapping
3445 tag_dict = {
3446 fk : sdnsh.get_current_mode_obj(), # fk from early for loop
3447 'tag' : tag_id,
3448 }
3449
3450 query = sdnsh.rest_query_objects(obj_type, tag_dict)
3451 sdnsh.check_rest_result(query)
3452 if len(query) == 0:
3453 result = sdnsh.rest_create_object(obj_type, tag_dict)
3454 sdnsh.check_rest_result(result)
3455
3456
3457def command_delete_tag(obj_type, data):
3458 """
3459 obj_type describes the tag-XXX-mapping which is getting
3460 managed, data has the tag 'string' to delete.
3461 """
3462 item = sdnsh.get_current_mode_obj_type()
3463 fks = mi.obj_type_foreign_keys(obj_type)
3464 for fk in fks:
3465 (fk_obj, fk_name) = mi.foreign_key_references(obj_type, fk)
3466 if fk_obj == item:
3467 break
3468 else:
3469 raise error.CommandSemanticError( "type mapping %s doesn't have "
3470 "relationship to the current object %s" %
3471 (obj_type, item))
3472
3473 if 'tag' not in data:
3474 raise error.CommandSemanticError('Tag value missing')
3475
3476 tag = data['tag']
3477 name_and_value = tag.split('=')
3478
3479 name_part = name_and_value[0].split('.')
3480 if len(name_part) == 1:
3481 namespace = 'default'
3482 name = name_part[0]
3483 elif len(name_part) >= 2:
3484 namespace = '.'.join(name_part[:-1])
3485 name = name_part[-1]
3486
3487 value = name_and_value[1]
3488 pk_value = sdnsh.unique_key_from_non_unique([namespace,
3489 name,
3490 value,
3491 sdnsh.get_current_mode_obj()])
3492 try:
3493 sdnsh.get_object_from_store(obj_type, pk_value)
3494 except Exception:
3495 raise error.CommandSemanticError('%s No such tag %s' % (obj_type, tag))
3496
3497 sdnsh.rest_delete_object(obj_type, pk_value)
3498
3499 # with that entry removed, check to see if any other
3500 # foreign keys assocaited with class Tag exist.
3501
3502 fk_value = sdnsh.unique_key_from_non_unique([namespace,
3503 name,
3504 value])
3505
3506 for tag_fields in mi.foreign_key_xref['tag']:
3507 for (fk_obj_type, fk_name) in mi.foreign_key_xref['tag'][tag_fields]:
3508 try:
3509 sdnsh.get_table_from_store(fk_obj_type, fk_name, fk_value)
3510 break
3511 except Exception, e:
3512 pass
3513 else:
3514 continue
3515 break
3516 else:
3517 try:
3518 sdnsh.rest_delete_object('tag', fk_value)
3519 except Exception, e:
3520 raise error.CommandSemanticError('base tag missing' % fk_value)
3521
3522
3523def command_rest_post_data(path, data=None, verb='PUT'):
3524 """
3525 """
3526 url = 'http://%s/rest/v1/%s' % (sdnsh.controller, path)
3527 result = sdnsh.rest_post_request(url, data, verb)
3528 check_rest_result(result)
3529 return None
3530
3531
3532def command_cli_variables_set(variable, value, data):
3533 global sdnsh
3534
3535 if variable == 'debug':
3536 print '***** %s cli debug *****' % \
3537 ('Enabled' if value else 'Disabled')
3538 sdnsh.debug = value
3539 elif variable == 'cli-backtrace':
3540 print '***** %s cli debug backtrace *****' % \
3541 ('Enabled' if value else 'Disabled')
3542 sdnsh.debug_backtrace = value
3543 elif variable == 'cli-batch':
3544 print '***** %s cli batch mode *****' % \
3545 ('Enabled' if value else 'Disabled')
3546 sdnsh.batch = value
3547 elif variable == 'description':
3548 print '***** %s command description mode *****' % \
3549 ('Enabled' if value else 'Disabled')
3550 sdnsh.description = value
3551 elif variable == 'rest':
3552 if 'record' in data and value:
3553 print '***** Eanbled rest record mode %s *****' % \
3554 (data['record'])
3555 url_cache.record(data['record'])
3556 return
3557 print '***** %s display rest mode *****' % \
3558 ('Enabled' if value else 'Disabled')
3559 if 'detail' in data and data['detail'] == 'details':
3560 if value == True:
3561 sdnsh.disply_rest_detail = value
3562 sdnsh.store.display_reply_mode(value)
3563 sdnsh.display_rest = value
3564 sdnsh.store.display_mode(value)
3565 if value == False:
3566 sdnsh.disply_rest_detail = value
3567 sdnsh.store.display_reply_mode(value)
3568 url_cache.record(None)
3569 elif variable == 'set':
3570 if 'length' in data:
3571 sdnsh.length = utif.try_int(data['length'])
3572
3573
3574def command_cli_set(variable, data):
3575 command_cli_variables_set(variable, True, data)
3576
3577def command_cli_unset(variable, data):
3578 command_cli_variables_set(variable, False, data)
3579
3580
3581def command_shell_command(script):
3582
3583 def shell(args):
3584 subprocess.call(["env", "SHELL=/bin/bash", "/bin/bash"] + list(args),
3585 cwd=os.environ.get("HOME"))
3586 print
3587
3588 print "\n***** Warning: this is a debug command - use caution! *****"
3589 if script == 'bash':
3590 print '***** Type "exit" or Ctrl-D to return to the CLI *****\n'
3591 shell(["-l", "-i"])
3592 elif script == 'python':
3593 print '***** Type "exit()" or Ctrl-D to return to the CLI *****\n'
3594 shell(["-l", "-c", "python"])
3595 elif script == 'cassandra-cli':
3596 print '***** Type "exit" or Ctrl-D to return to the CLI *****\n'
3597 shell(["-l", "-c", "/opt/sdnplatform/db/bin/cassandra-cli --host localhost"])
3598 elif script == 'netconfig':
3599 if not re.match("/dev/ttyS?[\d]+$", os.ttyname(0)):
3600 print '***** You seem to be connected via SSH or another remote protocol;'
3601 print '***** reconfiguring the network interface may disrupt the connection!'
3602 print '\n(Press Control-C now to leave the network configuration unchanged)\n'
3603 subprocess.call(["sudo",
3604 "env",
3605 "SHELL=/bin/bash",
3606 "/opt/sdnplatform/sys/bin/bscnetconfig",
3607 "eth0"],
3608 cwd=os.environ.get("HOME"))
3609 else:
3610 # XXX possibly run the script directly?
3611 print "Unknown debug choice %s" % script
3612
3613
3614def command_prompt_update():
3615 """
3616 Action to recompute the prompt, used when there's some possibility
3617 the prompt has changes after some other action (hostname update, for example)
3618 """
3619 sdnsh.set_controller_for_prompt()
3620 sdnsh.update_prompt()
3621
3622def command_controller_decommission(data):
3623 """
3624 Decommission the controller using the REST API
3625 """
3626 id = data.get('id')
3627 confirm_request("Decommission controller '%s'?\n(yes to continue) " % id)
3628
3629 while True:
3630 url = 'http://%s/rest/v1/system/ha/decommission' % (sdnsh.controller)
3631 result = sdnsh.rest_post_request(url, {"id": id}, 'PUT')
3632 status = json.loads(result)
3633
3634 if (status['status'] == 'OK') and status['description'].endswith('is already decommissioned') == True:
3635 print 'Decommission finished'
3636 print
3637 break
3638 else:
3639 print 'Decommission in progress'
3640
3641 time.sleep(10)
3642
3643def command_controller_upgrade(data = None):
3644 """
3645 Upgrade the controller using the REST API
3646 """
3647
3648 force = 'force' in data
3649 details = 'details' in data
3650
3651 if force:
3652 print "WARNING: Ignoring any validation errors during upgrade"
3653 url = "http://%s/rest/v1/system/upgrade/image-name" % sdnsh.controller
3654 result = sdnsh.store.rest_simple_request(url)
3655 check_rest_result(result)
3656 iname = json.loads(result)
3657 if (iname['file'] is None or iname['file'] == ""):
3658 print "Error: No upgrade image present."
3659 print ""
3660 print """To perform upgrade, an upgrade image package needs to be uploaded (with scp) to the controller's \"images\" user."""
3661 print """Upgrade image package is a file with name of format \"upgrade-YYYY.MM.DD.XXXX.pkg\"."""
3662 print ""
3663 print "Following is an example to prepare upgrade for controller with IP address 192.168.67.141:"
3664 print "scp $path/upgrade-2013.02.13.0921.pkg images@192.168.67.141:"
3665 print ""
3666 return
3667
3668 confirm_request("Upgrade controller from image '%s'?\n(yes to continue) "
3669 % iname['file'])
3670
3671 url = "http://%s/rest/v1/system/upgrade/extract-image-manifest" % sdnsh.controller
3672 result = sdnsh.store.rest_simple_request(url)
3673 check_rest_result(result)
3674 manifest = json.loads(result)
3675
3676 print "Executing upgrade..."
3677 for step in manifest:
3678 print "%s - %s" % (step['step'], step['description'])
3679 url = 'http://%s/rest/v1/system/upgrade/execute-upgrade-step' % \
3680 (sdnsh.controller)
3681 result = sdnsh.rest_post_request(url, {"step": step['step'],
3682 "imageName": iname['file'],
3683 "force": force},
3684 'PUT')
3685 check_rest_result(result)
3686 status = json.loads(result)
3687
3688 if (status['status'] == "OK"):
3689 print " Succeeded"
3690 if details:
3691 print "\nDetailed output:"
3692 print status['description']
3693 print
3694 else:
3695 print " Failed to execute upgrade step %d" % step['step']
3696 print "\nDetailed output:"
3697 print status['description']
3698 print
3699 return
3700
3701 print """Controller node upgrade complete.
3702Upgrade will not take effect until system is rebooted. Use 'reload' to
3703reboot this controller node. To revert, select the appropriate image
3704from the boot menu"""
3705
3706def command_cluster_config_rollback(data):
3707 path = ''
3708 if data.get('dir') == 'images://':
3709 path += '/home/images/'
3710 elif data.get('dir') == 'saved-configs://':
3711 path += '/opt/sdnplatform/run/saved-configs/'
3712 path += data.get('file')
3713
3714 url = "http://%s/rest/v1/system/ha/role" % sdnsh.controller
3715 result = sdnsh.store.rest_simple_request(url, use_cache = False)
3716 ha_role = json.loads(result)
3717 if ha_role['role'] != 'MASTER':
3718 print "Command can only be run on Master"
3719 return
3720
3721 command_legacy_cli('copy', {'dest': 'file://running-config-copy', 'source': 'running-config'})
3722 print "INFO: Checking config '%s'" % path
3723 url = "http://%s/rest/v1/system/rollback/diffconfig" % sdnsh.controller
3724 result = sdnsh.rest_post_request(url, {"config-1": "/opt/sdnplatform/run/saved-configs/running-config-copy", "config-2": path}, 'PUT')
3725 check_rest_result(result)
3726 if json.loads(result)['out'].startswith('Found differences'):
3727 print json.loads(result)['out']
3728 print "Rollback aborted"
3729 return
3730
3731 url = "http://%s/rest/v1/system/controller" % sdnsh.controller
3732 result = sdnsh.store.rest_simple_request(url, use_cache = False)
3733 controller_id = json.loads(result)['id']
3734
3735 url = "http://%s/rest/v1/model/controller-interface?controller=%s" % (sdnsh.controller, controller_id)
3736 result = sdnsh.store.rest_simple_request(url)
3737 local_iface = json.loads(result)[0]['discovered-ip']
3738
3739 url = "http://%s/rest/v1/model/controller-interface" % sdnsh.controller
3740 result = sdnsh.store.rest_simple_request(url)
3741 check_rest_result(result)
3742 ifaces = json.loads(result)
3743
3744 nodeCount = len(ifaces)
3745 cutover = nodeCount/2
3746 if nodeCount%2 == 1:
3747 cutover = cutover + 1
3748
3749 rollbackedNodes = []
3750
3751 # remove and add object for local node at the end of the list
3752 for index, iface in enumerate(ifaces):
3753 if iface['discovered-ip'] == local_iface:
3754 break
3755 del ifaces[index]
3756 ifaces.append(iface)
3757
3758 config=open(path, 'r').read()
3759 url = 'http://%s/rest/v1/system/upload-data' % ifaces[0]['discovered-ip']
3760 result = sdnsh.rest_post_request(url, {"data": config, "dst" : "/tmp/rollback.conf"}, 'PUT')
3761 check_rest_result(result)
3762
3763 while len(ifaces) > 0:
3764 if sdnsh.batch == False:
3765 while True:
3766 confirm = raw_input("Rollback controller at '%s'. [yes/no] ?" % ifaces[0]['discovered-ip'])
3767 if confirm.lower() == 'n' or confirm.lower() == 'no':
3768 if len(rollbackedNodes) == 0:
3769 print "INFO: Rollback aborted"
3770 return
3771
3772 print "INFO: Undoing Rollback on previously rollbacked nodes"
3773 for node in rollbackedNodes:
3774 print "INFO: Resetting database on '%s'" % node['discovered-ip']
3775 url = 'http://%s/rest/v1/system/resetbsc' % (node['discovered-ip'])
3776 result = sdnsh.rest_post_request(url, {}, 'PUT')
3777 check_rest_result(result)
3778 print "INFO: Rebooting '%s'" % node['discovered-ip']
3779 url = 'http://%s/rest/v1/system/reload' % (node['discovered-ip'])
3780 result = sdnsh.rest_post_request(url, {}, 'GET')
3781 check_rest_result(result)
3782
3783 if len(rollbackedNodes) >= cutover:
3784 # delete the REJECT rules
3785 url="http://localhost/rest/v1/model/firewall-rule?port=6633"
3786 result = sdnsh.rest_post_request(url, {}, 'DELETE')
3787 # enable allow openflow on all controllers not rollbacked.
3788 url="http://localhost/rest/v1/model/firewall-rule"
3789 for iface in ifaces:
3790 pk_id = '%s|Ethernet|0' % iface['controller']
3791 data = {
3792 'action': 'allow',
3793 'interface': pk_id,
3794 'src-ip': '',
3795 'port': '6633',
3796 'proto': 'tcp',
3797 'vrrp-ip': '',
3798 }
3799 print "INFO: re-allow openflow on %s" % iface['discovered-ip']
3800 result = sdnsh.rest_post_request(url, data, 'PUT')
3801 check_rest_result(result)
3802
3803 print "Rollback aborted"
3804 return
3805 elif confirm.lower() == 'y' or confirm.lower() == 'yes':
3806 break
3807
3808 url = 'http://%s/rest/v1/system/rollback/config' % (ifaces[0]['discovered-ip'])
3809 result = sdnsh.rest_post_request(url, {"path": "/tmp/rollback.conf"}, 'PUT')
3810 check_rest_result(result)
3811 time.sleep(10)
3812
3813 print "INFO: Rebooting ", ifaces[0]['discovered-ip']
3814 url = "http://%s/rest/v1/system/reload" % ifaces[0]['discovered-ip']
3815 result = sdnsh.store.rest_simple_request(url)
3816
3817 if ifaces[0]['discovered-ip'] == local_iface:
3818 break
3819
3820 print "INFO: Waiting for %s to come back up" % ifaces[0]['discovered-ip']
3821 url = "http://%s/rest/v1/system/ha/role" % ifaces[0]['discovered-ip']
3822 while True:
3823 time.sleep(30)
3824 try:
3825 result = sdnsh.store.rest_simple_request(url, use_cache = False)
3826 status = json.loads(result)
3827 if status['role'] == 'SLAVE' or status['role'] == 'MASTER':
3828 print "INFO: Rollback complete on '%s'" % ifaces[0]['discovered-ip']
3829 break
3830 print "INFO: Waiting for 30 seconds"
3831 except:
3832 print "INFO: Waiting for 30 seconds"
3833
3834
3835 iface = ifaces.pop(0)
3836 rollbackedNodes.append(iface)
3837
3838 print "Rollback completed"
3839
3840def command_wait_for_controller(delay = None, sdnplatform_check = False,
3841 within_command = False):
3842 """
3843 For various commands, it makes sense for the command to verify that
3844 the controller restart has been completed. In the situation where
3845 a single controller is configured, it also makes sense to verify the
3846 controller is now configured as MASTER.
3847
3848 This is especially true for command which are known to cause the
3849 controller to restart, for exampe the 'feature' command.
3850
3851 The procedure is also used during CLI startup (see cli.py)
3852 to verify that the controller is in MASTER mode. Its normal
3853 for the HA role to transition from SLAVE to master during
3854 system startup.
3855 """
3856
3857 # if the CLI was started with --init, skip the wait, the
3858 # controller isn't running.
3859 if sdnsh.options.init:
3860 return
3861
3862 def is_ready(sdnsh, verbose, duration):
3863 """
3864 Be loud-as-_ean when the duration is greater then 15 seconds.
3865 Display the gory details for all to know.
3866 """
3867 too_long = 90
3868 try:
3869 url = "http://%s/rest/v1/system/ha/role" % sdnsh.controller
3870 result = sdnsh.store.rest_simple_request(url, use_cache = False)
3871 ha_role = json.loads(result)
3872 if duration > too_long:
3873 print 'Long delay: reason', \
3874 ', '.join(['%s: %s' % (n,v) for (n,v) in ha_role.items()
3875 if v != ''])
3876 if (ha_role['role'] == 'MASTER' or
3877 sdnsh.find_master()['master'] is not None):
3878 if verbose:
3879 print 'Current role is MASTER'
3880 return True
3881 return False
3882 except error.CommandRestError,e:
3883 print "REST error whileUnable to determine controller HA role."
3884 errors = self.rest_error_to_dict(e, obj_type)
3885 print self.rest_error_dict_to_message(errors)
3886 return True
3887 except Exception, e:
3888 if duration > too_long:
3889 print 'MASTER Transition Failure: ', e
3890 traceback.print_exc()
3891 return True
3892 return False
3893
3894 # if this isn't a typical environment (ie: running remotely)
3895 # don't bother trying to determine the role
3896 if not os.path.exists('/opt/sdnplatform/current_role'):
3897 return
3898
3899 # now vadalidate the rest api port is working
3900 ip_and_port = sdnsh.controller.split(':')
3901 if len(ip_and_port) == 2:
3902 # first ensure the REST API is answering
3903 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
3904 try:
3905 s.connect((ip_and_port[0], int(ip_and_port[1])))
3906 s.close()
3907 except Exception, e:
3908 print 'REST API not running, emergency CLI access'
3909 if sdnsh.debug: # enable debug to see messages
3910 print 'Exception:', e
3911 return
3912
3913 # issue a REST API request directed at the model.
3914 try:
3915 entry = sdnsh.get_table_from_store('feature')
3916 except Exception, e:
3917 print 'REST API/Database not responding, emergency CLI access'
3918 if sdnsh.debug: # enable debug to see messages
3919 print 'Exception:', e
3920 return
3921
3922 if sdnplatform_check:
3923 # the REST API request for ha-role will return UNAVAILABLE
3924 # when sdnplatform isn't running.
3925 url = "http://%s/rest/v1/system/ha/role" % sdnsh.controller
3926 result = sdnsh.store.rest_simple_request(url, use_cache = False)
3927 ha_role = json.loads(result)
3928 if ha_role['role'] == 'UNAVAILABLE':
3929 print 'REST API/SDN platform not responding, emergency CLI access'
3930 return
3931
3932
3933 if delay == None:
3934 delay = 1
3935 delay_str = 'a sec' if delay == 1 else '%d seconds' % delay
3936
3937 duration = 0
3938 while True:
3939 try:
3940 verbose = False
3941 while not is_ready(sdnsh, verbose, duration):
3942 if within_command:
3943 print 'Waiting %s to complete command execution, ' \
3944 'Hit Ctrl-C to exit early' % delay_str
3945 verbose = False
3946 else:
3947 print 'Waiting %s while current role is SLAVE mode, ' \
3948 'Hit Ctrl-C to exit early' % delay_str
3949 verbose = True
3950 time.sleep(delay)
3951 duration += delay
3952 return
3953 except:
3954 if is_ready(sdnsh, True, duration):
3955 if duration > 15:
3956 print 'MASTER Transition: %s sec' % duration
3957 return
3958 try:
3959 resp = raw_input('Controller is not yet ready.'
3960 'Do you still want to continue to the CLI? [n]')
3961 if resp and "yes".startswith(resp.lower()):
3962 print 'Continuing with CLI despite initialization error ...'
3963 return
3964 except KeyboardInterrupt:
3965 return
3966
3967
3968def command_factory_default():
3969 print "Re-setting controller to factory defaults ..."
3970 os.system("sudo /opt/sdnplatform/sys/bin/resetbsc")
3971 return
3972
3973
3974def command_dump_log(data):
3975 controller = data.get('controller-node') # can be None.
3976 controller_dict = { 'id' : controller }
3977 for ip_port in controller_ip_and_port(controller_dict):
3978 log_name = data['log-name']
3979 if log_name == 'all':
3980 url = log_url(ip_and_port = ip_port)
3981 log_names = command.sdnsh.rest_simple_request_to_dict(url)
3982 for log in log_names:
3983 yield '*' * 40 + ip_port + ' ' + log['log'] + '\n'
3984 for item in command_dump_log({ 'log-name' : log['log'] }):
3985 yield item
3986 return
3987
3988 # use a streaming method so the complete log is not in memory
3989 url = log_url(ip_and_port = ip_port, log = log_name)
3990 request = urllib2.urlopen(url)
3991 for line in request:
3992 yield line
3993 request.close()
3994
3995
3996#
3997# Initialize action functions
3998#
3999#
4000
4001def init_actions(bs, modi):
4002 global sdnsh, mi
4003 sdnsh = bs
4004 mi = modi
4005
4006 command.add_action('create-tunnel',
4007 tunnel_create,
4008 {'kwargs': {'data' : '$data',}})
4009
4010 command.add_action('remove-tunnel',
4011 tunnel_remove,
4012 {'kwargs': {'data' : '$data',}})
4013
Srikanth Vavilapallib5c3ca52014-12-15 15:59:33 -08004014 command.add_action('create-tunnelset',
4015 tunnelset_create,
4016 {'kwargs': {'data' : '$data',}})
4017
4018 command.add_action('remove-tunnelset',
4019 tunnelset_remove,
4020 {'kwargs': {'data' : '$data',}})
4021
Srikanth Vavilapalli1725e492014-12-01 17:50:52 -08004022 command.add_action('create-policy',
4023 policy_create,
4024 {'kwargs': {'data' : '$data',}})
4025
4026 command.add_action('remove-policy',
4027 policy_remove,
4028 {'kwargs': {'data' : '$data',}})
4029
4030 command.add_action('write-fields', write_fields,
4031 {'kwargs': {'obj_type': '$current-mode-obj-type',
4032 'obj_id': '$current-mode-obj-id',
4033 'data': '$data'}})
4034
4035 command.add_action('reset-fields', reset_fields,
4036 {'kwargs': {'obj_type' : '$current-mode-obj-type',
4037 'obj_id' : '$current-mode-obj-id',
4038 'arg_data' : '$data',
4039 'match_for_no' : '$match-for-no',
4040 'fields' : '$fields'}})
4041
4042 command.add_action('write-fields-explicit', write_fields,
4043 {'kwargs': {'obj_type' : '$obj-type',
4044 'obj_id' : '$obj-id',
4045 'data' : '$data'}})
4046
4047 command.add_action('reset-fields-explicit', reset_fields,
4048 {'kwargs': {'obj_type' : '$obj-type',
4049 'obj_id' : '$obj-id',
4050 'arg_data' : '$data',
4051 'match_for_no' : '$match-for-no',
4052 'fields' : '$fields'}})
4053
4054 command.add_action('update-config', update_config,
4055 {'kwargs': {'obj_type' : '$obj-type',
4056 'obj_id' : '$current-mode-obj-id',
4057 'data' : '$data',
4058 'no_command' : '$is-no-command', }})
4059
4060 command.add_action('delete-objects', delete_objects,
4061 {'kwargs': {'obj_type': '$obj-type',
4062 'data': '$data',
4063 'parent_field': '$parent-field',
4064 'parent_id': '$current-mode-obj-id'}})
4065
4066 command.add_action('write-object', write_object,
4067 {'kwargs': {'obj_type': '$obj-type',
4068 'data': '$data',
4069 'parent_field': '$parent-field',
4070 'parent_id': '$current-mode-obj-id'}})
4071
4072 command.add_action('set-data', set_data,
4073 {'kwargs': {'data': '$data',
4074 'key': '$key',
4075 'value': '$value'}})
4076
4077 command.add_action('push-mode-stack', push_mode_stack,
4078 {'kwargs': {'mode_name': '$submode-name',
4079 'obj_type': '$obj-type',
4080 'parent_field': '$parent-field',
4081 'parent_id': '$current-mode-obj-id',
4082 'data': '$data',
4083 'create': '$create'}})
4084
4085 command.add_action('pop-mode-stack', pop_mode_stack)
4086
4087 command.add_action('confirm', confirm_request,
4088 {'kwargs': {'prompt': '$prompt'}})
4089
4090 command.add_action('convert-vns-access-list', convert_vns_access_list,
4091 {'kwargs': {'obj_type': '$obj-type',
4092 'key' : '$current-mode-obj-id',
4093 'data' : '$data'}})
4094 command.add_action('display-table', command_display_table,
4095 {'kwargs': {'obj_type' : '$obj-type',
4096 'data' : '$data',
4097 'table_format' : '$format',
4098 'title' : '$title',
4099 'detail' : '$detail',
4100 'scoped' : '$scoped',
4101 'sort' : '$sort',
4102 }})
4103
4104 command.add_action('display-rest', command_display_rest,
4105 {'kwargs': { 'data' : '$data',
4106 'url' : '$url',
4107 'path' : '$path',
4108 'rest_type' : '$rest-type',
4109 'sort' : '$sort',
4110 'title' : '$title',
4111 'table_format' : '$format',
4112 'detail' : '$detail',
4113 }})
4114
4115 command.add_action('query-table', command_query_table,
4116 {'kwargs': {'obj_type' : '$obj-type',
4117 'data' : '$data',
4118 'key' : '$key',
4119 'scoped' : '$scoped',
4120 'sort' : '$sort',
4121 'crack' : '$crack',
4122 'append' : '$append',
4123 'clear' : True,
4124 }})
4125
4126 command.add_action('query-table-append', command_query_table,
4127 {'kwargs': {'obj_type' : '$obj-type',
4128 'data' : '$data',
4129 'key' : '$key',
4130 'scoped' : '$scoped',
4131 'sort' : '$sort',
4132 'crack' : '$crack',
4133 'append' : '$append',
4134 'clear' : False,
4135 }})
4136
4137
4138 command.add_action('query-rest', command_query_rest,
4139 {'kwargs': {'url' : '$url',
4140 'path' : '$path',
4141 'rest_type' : '$rest-type',
4142 'data' : '$data',
4143 'key' : '$key',
4144 'scoped' : '$scoped',
4145 'sort' : '$sort',
4146 'append' : '$append',
4147 'clear' : True,
4148 }})
4149
4150 command.add_action('query-rest-append', command_query_rest,
4151 {'kwargs': {'url' : '$url',
4152 'path' : '$path',
4153 'rest_type' : '$rest-type',
4154 'data' : '$data',
4155 'key' : '$key',
4156 'scoped' : '$scoped',
4157 'sort' : '$sort',
4158 'crack' : '$crack',
4159 'append' : '$append',
4160 'clear' : False,
4161 }})
4162
4163 command.add_action('join-rest', command_join_rest,
4164 {'kwargs': {'url' : '$url',
4165 'key' : '$key',
4166 'join_field' : '$join-field',
4167 'rest_type' : '$rest-type',
4168 'add_field' : '$add-field',
4169 'data' : '$data',
4170 'crack' : '$crack',
4171 'url_key' : '$url-key',
4172 }})
4173
4174 command.add_action('join-table', command_join_table,
4175 {'kwargs': {'obj_type' : '$obj-type',
4176 'data' : '$data',
4177 'key' : '$key',
4178 'key_value' : '$key-value',
4179 'add_field' : '$add-field',
4180 'join_field' : '$join-field',
4181 'crack' : '$crack',
4182 }})
4183
4184 command.add_action('crack', command_crack,
4185 {'kwargs': {
4186 'field' : '$field',
4187 }})
4188
4189 command.add_action('display', command_display,
4190 {'kwargs': {'data' : '$data',
4191 'table_format' : '$format',
4192 'sort' : '$sort',
4193 'detail' : '$detail',
4194 'title' : '$title',
4195 }})
4196
4197 command.add_action('legacy-cli', command_legacy_cli,
4198 {'kwargs': {'obj_type' : '$obj-type',
4199 'data' : '$data',
4200 'detail' : '$detail',
4201 'sort' : '$sort',
4202 'scoped' : '$scoped',
4203 }})
4204
4205 command.add_action('legacy-cli-no', command_legacy_cli_no,
4206 {'kwargs': {'obj_type' : '$obj-type',
4207 'data' : '$data',
4208 'detail' : '$detail',
4209 'sort' : '$sort',
4210 'scoped' : '$scoped',
4211 }})
4212
4213 command.add_action('version', command_version,
4214 {'kwargs': {'data' : '$data',
4215 }})
4216
4217 command.add_action('clearterm', command_clearterm)
4218
4219 command.add_action('display-cli', command_display_cli,
4220 {'kwargs': {'data' : '$data',
4221 'detail' : '$detail',
4222 }})
4223
4224 command.add_action('create-alias', command_create_alias,
4225 {'kwargs': {'obj_type' : '$obj-type',
4226 'data' : '$data',
4227 'reserved' : '$reserved',
4228 'fail_if_exists' : '$fail-if-exists',
4229 }})
4230
4231 command.add_action('delete-alias', command_delete_alias,
4232 {'kwargs': {'obj_type' : '$obj-type',
4233 'data' : '$data',
4234 }})
4235
4236 command.add_action('create-tag', command_create_tag,
4237 {'kwargs': {'obj_type' : '$obj-type',
4238 'data' : '$data',
4239 }})
4240
4241 command.add_action('delete-tag', command_delete_tag,
4242 {'kwargs': {'obj_type' : '$obj-type',
4243 'data' : '$data',
4244 }})
4245
4246 command.add_action('cli-set', command_cli_set,
4247 {'kwargs': {'variable' : '$variable',
4248 'data' : '$data',
4249 }})
4250
4251 command.add_action('cli-unset', command_cli_unset,
4252 {'kwargs': {'variable' : '$variable',
4253 'data' : '$data',
4254 }})
4255
4256 command.add_action('shell-command', command_shell_command,
4257 {'kwargs': {'script' : '$command',
4258 }})
4259
4260 command.add_action('rest-post-data', command_rest_post_data,
4261 {'kwargs': {'path': '$path',
4262 'data': '$data',
4263 'verb': '$verb'
4264 }})
4265
4266 command.add_action('prompt-update', command_prompt_update,)
4267
4268 command.add_action('controller-upgrade', command_controller_upgrade,
4269 {'kwargs': {'data': '$data'}})
4270
4271 command.add_action('controller-config-rollback', command_cluster_config_rollback,
4272 {'kwargs': {'data': '$data'}})
4273
4274 command.add_action('controller-decommission', command_controller_decommission,
4275 {'kwargs': {'data': '$data'}})
4276
4277 command.add_action('wait-for-controller', command_wait_for_controller,
4278 {'kwargs': {'within_command': True}})
4279
4280 command.add_action('factory-default', command_factory_default)
4281
4282 command.add_action('dump-log', command_dump_log,
4283 {'kwargs' : { 'data' : '$data', }})