blob: e6987af1c90abc52db602a9b5e741f43021c45a6 [file] [log] [blame]
Srikanth Vavilapalli1725e492014-12-01 17:50:52 -08001#!/usr/bin/python
2#
3# Copyright (c) 2010,2011,2012,2013 Big Switch Networks, Inc.
4#
5# Licensed under the Eclipse Public License, Version 1.0 (the
6# "License"); you may not use this file except in compliance with the
7# License. You may obtain a copy of the License at
8#
9# http://www.eclipse.org/legal/epl-v10.html
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14# implied. See the License for the specific language governing
15# permissions and limitations under the License.
16#
17
18# sdnsh - The Controller Shell
19# --------------------------------------------------------------------------------
20#
21# LOOK! TODO --
22#
23# create tokens for modify actions in StaticFlowEntryPusher and here
24#
25# extend completion to show help text
26#
27# need to verify we can talk to the controller
28#
29# flow-entry naming - unique across all switches?
30#
31# joins on show commands (port names, ids, etc.)
32#
33# accept enums in addition to numbers - "ether-type arp" or "ether-type ip"
34#
35# cli commands not mapping to database model (this is an -undo-)
36#
37# simple options/singletons in the CLI ("logging level 5" "twitter username blah")
38#
39# csv, left/right justification in columns, sorts
40#
41# completion with multi command lines
42#
43# disallow 'switch' field and name as options flow-entry ('key' fields in general)
44#
45# delayed commit semantics (depends on 1?)
46#
47# delete config files?
48#
49# --------------------------------------------------------------------------------
50#
51# Notes:
52#
53# support functions for a command, consider, for example, 'vns-definition'
54#
55# For a new command, for example 'vns-definition', there will be a variety
56# of different support functions to manage different required functionaly.
57# do_vns_definition() describes what to do to execute the actual command.
58# cp_vns_definition() describes how to perform command completion for the same.
59# do_show_vns_definition() would describe how to show the vns-definitions
60# (this procedure doesn't exist, but is implemented by 'show vns' via
61# do_show_vns), while
62# cp_show_vns_definition() would describe how to complete the show command for
63# this particular command. Other functions may also be needed, for example
64# specialized changes to the 'no' command for the same command.
65#
66# command completion
67#
68# currently in many cases, there's a specialized completion handler
69# it would make sense to manage 'no' completion by overloading the
70# same completion handler.
71#
72#
73# error processing:
74#
75# Keep in mond that al errors ought to use a similar prefix so that
76# all the error conditions acan be identifyed by the automated test
77# procedures. these prefixes are regular expressions in bigtest/cli.py
78# The prefixed currently recognized as errors are 'Error: Not Found",
79# 'Error running command ', 'Syntax error:', and 'Syntax: ' the last
80# of which is used to display both usage for some commands, and
81# various specific syntax errors
82#
83# alias management:
84#
85# Aliases are managed by having a separate table/model for each
86# of the aliases, for example the host table has an associated alias
87# table called 'host-alias'. These alias models are nothing more
88# than an id, which is the alias name and a foreign key for the other
89# table, in the 'host-alias' model, the associated foreign key would
90# point to the host.
91#
92# Alias tables or models are not directly configured as alias tables,
93# the cli can find these tables by noticing the only fields within
94# the model is a foreign key for another table. The cli builds the
95# alias_obj_type_xref dictionary during initialization, and later uses
96# that association to manage aliases. Knowing which of the tables
97# are alias tables also allows the cli to exclude these tables from
98# modification in typical config mode.
99#
100# Alias are created when within the submode for a particular obj_type,
101# for example, the host-alias table is available within the host
102# config submode, and it appears as the 'host-alias' table there.
103# However no further submode for the host-alias is managed; it is
104# treated as if it were a field in the submode config object.
105# So if 'host-alias xxx' were entered while in host config submode,
106# the cli converts the request into a host-alias table entry.
107#
108# Feature management. The cli is intended to manage the availability
109# of specific feature groups. This isn't meant to prevent a user from
110# getting at specific sku features (ie: licensing), but instead meant
111# to allow a user to disable specific sub-groups of commands to
112# prevent unintended enabling of features. For example,
113# to prevent a 'static-flow' cli configured controller from enabling
114# vns features likely resulting in a misconfigured environment.
115#
116# changes:
117#
118# -- new source field for obj_types in models, obj_types which are
119# written based on discovered details are typically not editable.
120# don't allow a config submode entry. and don't
121# advertise such an entry is possible
122#
123# -- integrated alias support, no special cases through the use
124# of a separated alias tables. this allows more integrated
125# cli support for tables specifically constructed to manage
126# alias names for other fields. Currently code in the cli
127# prevents multiple alias for a single foreign key, but this
128# behavior can be relaxed (perhaps it ought to be configurable?)
129#
130# -- perform alias substitution for 'no' commands obj_type's
131#
132# -- support for primary key/id changes within a row.
133# if an update occurs which modifies the primary key of a row,
134# the cli now identifies whether any foreign keys point at
135# the existing entry, and updates the foreign key to reference
136# the updated primary key.
137#
138# -- support for cascaded delete.
139# when a row is deleted, if there are foreign keys associated
140# with the row, the cli will remove them. the climodelinfo
141# configures which obj_types are included in the cascade deletes.
142#
143# -- vns interface creation.
144# vns interfaces are required to have a vns-interface-rule associated.
145# sdnplatform writes both the interface name and the rule, but the cli needed
146# to have code added to determine and validate the associated rule.
147# The validation attempts to ensure the correct rule is associated with
148# the use of the interface name (id).
149#
150
151import subprocess
152import os, atexit, stat
153import sys, traceback # traceback.print_exc()
154from optparse import OptionParser
155from types import StringType
156import collections
157import datetime
158import json
159import re
160import time
161import urllib2
162import httplib # provides error processing for isinstance
163import socket
164import select
165import fcntl
166import posixpath
167import random
168import copy
169import utif
170import fmtcnv
171import run_config
172import imp
173import locale
174
175from pkg_resources import resource_filename
176from modi import Modi
177from midw import *
178from vnsw import *
179from prettyprint import PrettyPrinter
180from storeclient import StoreClient
181from climodelinfo import CliModelInfo
182from vendor import VendorDB
183import error
184import command
185import rest_to_model
186import tech_support
187import url_cache
188import doc
189import sdndb
190
191#
192# --------------------------------------------------------------------------------
193#
194
195class ParamException(Exception):
196 def __init__(self, value):
197 self.value = value
198 def __str__(self):
199 return repr(self.value)
200
201class TooManyVNSException(Exception):
202 def __init__(self, value):
203 self.value = value
204 def __str__(self):
205 return repr(self.value)
206
207TIMESTAMP_HELP = \
208 ('Enter an integer timestamp in milliseconds since the epoch or formatted\n' +
209 'as one of the following options in your local timezone:\n'
210 '"YYYY-MM-DD HH:MM:SS" YYYY-MM-DDTHH:MM:SS\n' +
211 '"YYYY-MM-DD HH:MM:SS+TTT" YYYY-MM-DDTHH:MM:SS+TTT\n' +
212 'YYYY-MM-DD MM-DD\n' +
213 'HH:MM now')
214
215
216# This is a bit of a hack. The completion and validation functions for time zones
217# are static methods, but they need to access the SDNSh object to get at the
218# controller field to be able to do a REST call to load the list of time zone
219# strings. So we set up a global for the single SDNSh instance we have. An
220# alternative would have been to have a global variable for the list of time
221# zone strings that's initialized when the CLI is initialized, but it seems
222# nicer to only load it on demand, especially since setting the time zone
223# will be a pretty infrequently invoked command.
224cli = None
225
226#
227# Constants for packet tracing
228#
229SESSIONID = 'sessionId'
230FT_VNS = 'vns'
231FT_PORT = 'port'
232FT_PORT_DPID = 'dpid'
233FT_PORT_PORT = 'port'
234FT_OUTPUT = 'output'
235FT_DIRECTION = 'direction'
236FT_PERIOD = 'period'
237FT_TIMEOUT = "FilterTimeout"
238FT_PERIOD_DEFAULT = 300
239
240onos=1
241#
242# --------------------------------------------------------------------------------
243#
244class NotFound(Exception):
245 def __init__(self, obj_type, name):
246 self.obj_type = obj_type
247 self.name = name
248 def __str__(self):
249 return "Not Found: %s %s" % (self.obj_type, self.name)
250
251
252# LOOK!: The next two functions are copied from the sdncon code.
253# Should ideally figure out a way to share the code for utility
254# functions like this.
255
256def dotted_decimal_to_int(ip):
257 """
258 Converts a dotted decimal IP address string to a 32 bit integer
259 """
260 bytes = ip.split('.')
261 ip_int = 0
262 for b in bytes:
263 ip_int = (ip_int << 8) + int(b)
264 return ip_int
265
266
267def same_subnet(ip1, ip2, netmask):
268 """
269 Checks whether the two ip addresses are on the same subnet as
270 determined by the netmask argument. All of the arguments are
271 dotted decimal IP address strings.
272 """
273 ip1_int = dotted_decimal_to_int(ip1)
274 ip2_int = dotted_decimal_to_int(ip2)
275 netmask_int = dotted_decimal_to_int(netmask)
276 return (ip1_int & netmask_int) == (ip2_int & netmask_int)
277
278#
279# --------------------------------------------------------------------------------
280#
281
282class SDNSh():
283
284 debug = False # general cli debugging
285 debug_backtrace = False # backtrace on failures
286 description = False # help debug command descriptions
287 display_rest = False # display rest call details
288 display_reply_rest = False # display rest call replies details
289
290 command_dict = {}
291 mode_stack = []
292 reserved_words = ['unknown', 'all']
293
294 known_controllers = ["127.0.0.1:8000"]
295 controller = None
296 # LOOK! manage this separately until we can cetainly
297 # talk to eth0 addr instead of 127.0.0.1
298 controller_for_prompt = "127.0.0.1:8000"
299 cluster = "default"
300
301 run = True
302
303 # Are we running in batch mode, i.e. "cat commands | cli"
304 batch = False
305
306 # used for parsing as an arg
307 local_name_pattern = "config://([A-Za-z0-9_:@\-\.\/]+$)"
308
309 # name of boot/startup-config
310 boot_config_filename = '/opt/sdnplatform/run/boot-config'
311 saved_configs_dirname = '/opt/sdnplatform/run/saved-configs/'
312 feature_allow_experimental = '/opt/sdnplatform/feature/experimental'
313
314 # contained objects
315 pp = None
316 store = None
317 model_handler = None
318
319 # cached metadata
320 stats_metadata = None
321 stats_type_metadata = None
322 stats_restapi_map = {'controller-node': 'controller'}
323
324 stats_optional_params = {
325 'start-time': {'type': 'string',
326 'syntax_help': TIMESTAMP_HELP},
327 'end-time': {'type': 'string',
328 'syntax_help': TIMESTAMP_HELP},
329 'duration': {'type': 'string',
330 'syntax_help':
331 "Enter an integer followed by a time unit\n" +
332 "[integer]weeks [integer]days [integer]hours\n" +
333 "[integer]mins [integer]secs [integer]ms"},
334 'sample-interval': {'type': 'int',
335 'syntax_help': 'Enter an integer number of milliseconds'},
336 'sample-count': {'type': 'int',
337 'syntax_help': 'Enter an integer target sample count'},
338 'sample-window': {'type': 'string',
339 'syntax_help': 'Enter an integer number of milliseconds for sampling window'},
340 'data-format': {'type': 'enum', 'values': ['value', 'rate']},
341 'display': {'type': 'enum', 'values': ['latest-value', 'graph', 'table']},
342 #'limit': {'type': 'int'}
343 }
344
345 netvirt_feature_enabled_cached = False
346 warning_suppress = False #
347
348 #
349 # --------------------------------------------------------------------------------
350 # warning
351 # When a config is getting replayed, warnings are suporessed. Other situations
352 # may also require supression of warnings.
353 #
354 def warning(self, message):
355 if not self.warning_suppress:
356 print "Warning: %s" % message
357
358 #
359 # --------------------------------------------------------------------------------
360 # supress_warnings(self)
361 #
362 def suppress_warnings(self):
363 self.warning_suppress = True
364
365 #
366 # --------------------------------------------------------------------------------
367 # enable_warnings(self)
368 #
369 def enable_warnings(self):
370 self.warning_suppress = False
371
372
373 config_replay = False # only true while replaying config files
374 #
375 # --------------------------------------------------------------------------------
376 # config_replay_start
377 # Behavior is different during config replay. Warnings are
378 # suppressed, and other error conditions may be relaxed, for example
379 # the switch submode interface command requires that the interface exist,
380 # and the interface names are discovered by sdnplatform, which means that during
381 # config replay for statup, an unknown-named-interface may need to be accepted
382 # since that interface may appear once the switch connected to sdnplatform, and
383 # sdnplatform writes the ports for this switch.
384 #
385 def config_replay_start(self):
386 self.suppress_warnings()
387 self.config_replay = True
388
389 #
390 # --------------------------------------------------------------------------------
391 # config_replay_done
392 #
393 def config_replay_done(self):
394 self.enable_warnings()
395 self.config_replay = False
396
397 #
398 # --------------------------------------------------------------------------------
399 # config_replay_active
400 # Returns true when a configuration is getting replayed
401 #
402 def config_replay_active(self):
403 return self.config_replay
404
405 #
406 # --------------------------------------------------------------------------------
407 # note
408 # When a config is getting replayed, warnings/notes are suporessed. Other situations
409 # may also require supression of warnings.
410 #
411 def note(self, message):
412 if not self.warning_suppress:
413 print "Note: %s" % message
414
415 #
416 # --------------------------------------------------------------------------------
417 # debug_msg
418 # Debugging message cover. Only enabled for display once 'debug cli' is performed.
419 #
420 def debug_msg(self, message):
421 if self.debug:
422 print "debug_msg: %s" % message
423
424 #
425 # --------------------------------------------------------------------------------
426 # error_msg
427 # Error message cover, to ensure consistent error messages
428 #
429 @staticmethod
430 def error_msg(message):
431 return "Error: %s" % message
432
433 #
434 # --------------------------------------------------------------------------------
435 # syntax_msg
436 # Syntax error message cover, to ensure consistent error messages
437 #
438 @staticmethod
439 def syntax_msg(message):
440 return "Syntax: %s" % message
441
442 #
443 # --------------------------------------------------------------------------------
444 # completion_error_msg
445 # Error message cover during completion, to ensure consistent error messages
446 #
447 def completion_error_msg(self, message):
448 self.print_completion_help("Error: %s" % message)
449
450 #
451 # --------------------------------------------------------------------------------
452 # completion_syntax_msg
453 # Syntax error message cover during completion, to ensure consistent error messages
454 #
455 def completion_syntax_msg(self, message):
456 self.print_completion_help("Syntax: %s" % message)
457
458 #
459 # --------------------------------------------------------------------------------
460 # init_history_file
461 #
462 def init_history_file(self):
463 history_file = os.path.join(os.environ["HOME"], ".sdnsh_history")
464 # create the initial history file group-writable,
465 # so that the tacacs users can write to it
466 if os.path.exists(history_file):
467 st = os.stat(history_file)
468 if not (st[stat.ST_MODE] & stat.S_IWGRP):
469 buf = open(history_file).read()
470 os.rename(history_file, history_file + "~")
471 mask = os.umask(007)
472 fno = os.open(history_file, os.O_WRONLY | os.O_CREAT, 0660)
473 os.umask(mask)
474 os.write(fno, buf)
475 os.close(fno)
476 else:
477 mask = os.umask(007)
478 fno = os.open(history_file, os.O_WRONLY | os.O_CREAT, 0660)
479 os.umask(mask)
480 os.close(fno)
481 try:
482 readline.read_history_file(history_file)
483 except IOError:
484 pass
485 atexit.register(readline.write_history_file, history_file)
486
487 #
488 # --------------------------------------------------------------------------------
489 # init_command_dict
490 #
491 def init_command_dict(self):
492
493 #
494 # command_submode_dict saves a little information about
495 # submode commands. The index is 'mode', and the resulting
496 # list contains strings, each of which is a submode command
497 # available in that mode.
498 self.command_submode_dict = {}
499
500 #
501 # associate command names with features, so that when
502 # help is provided, command names which have associated
503 # features are validated to determine whether the feature
504 # is enabled or not.
505 self.command_name_feature = {}
506
507 #
508 # command_nested_dict is indexed by mode, and contains
509 # command configured with a trailing '*', for example
510 # 'mode' : 'config-*', intended to mean "all nested
511 # submodes after config-'. This can't be computed until
512 # all submodes are known, which means all the command
513 # describption must be configured., for these to make sense.
514 self.command_nested_dict = {}
515
516 #
517 # config mode commands
518 #
519 #self.command_dict['config'] = [ "boot" ]
520
521 #
522 # commands which start at 'login'
523 #
524 if onos == 0:
525 self.command_nested_dict['login'] = [ 'show', 'logout', 'exit',
526 'history', 'help', 'echo',
527 'date', 'trace', 'traceroute',
528 'ping', 'test', 'version',
529 'connect', 'watch', 'no' ]
530 else:
531 self.command_nested_dict['login'] = [ 'show', 'logout', 'exit',
532 'history', 'help', 'echo',
533 'date', 'trace',
534 'ping', 'test', 'version',
535 'connect', 'watch', 'no' ]
536
537 self.command_nested_dict['enable'] = [ 'clear', 'end' ]
538
539 #self.command_dict['config-internal'] = ['lint', 'permute']
540
541 #
542 # --------------------------------------------------------------------------------
543 # make_unit_formatter
544 #
545 @staticmethod
546 def make_unit_formatter(units):
547 return lambda x, y: '%s %s' % (x, units)
548
549 #
550 # --------------------------------------------------------------------------------
551 # init_stats_metadata
552 #
553 def init_stats_metadata(self):
554 # Initialize the stats metadata by pulling it from the rest api
555 json_data = self.rest_simple_request_to_dict('http://%s/rest/v1/stats/metadata/%s/' % \
556 (self.controller, self.cluster))
557 metadata = {}
558 fields = {}
559 for item in json_data:
560 # XXX Requesting this stat causes a long delay in reading; bug in sdncon
561 # once this is fixed we can add back in; also add to field_orderings below
562 #if json_data[item]['name'] == 'OFActiveFlow':
563 # continue
564
565 if json_data[item]['target_type'] in metadata:
566 metadata[json_data[item]['target_type']].append(json_data[item])
567 else:
568 metadata[json_data[item]['target_type']] = [json_data[item]]
569 fields[json_data[item]['target_type']] = {}
570
571 formatter = self.make_unit_formatter(json_data[item]['units'])
572 fields[json_data[item]['target_type']][json_data[item]['name']] = \
573 {'verbose-name': json_data[item]['verbose_name'],
574 'units': json_data[item]['units'],
575 'formatter': formatter}
576
577 self.stats_metadata = json_data
578 self.stats_type_metadata = metadata
579
580 self.pp.add_format('STATS-CONTROLLER',
581 'cli.init_stats_metadata()', # origina
582 {'stats-controller' : {
583 'field-orderings' : {'default':
584 ['cpu-user', 'cpu-nice', 'cpu-system',
585 'cpu-idle'
586 'mem-free', 'mem-used', 'swap-used',
587 'disk-root', 'disk-log', 'disk-boot',
588 'sdnplatform-cpu', 'database-cpu',
589 'apache-cpu', 'cli-cpu', 'statd-cpu']},
590 'fields' : fields['controller']
591 }})
592
593 self.pp.add_format('STATS-SWITCH',
594 'cli.init_stats_metadata()', # origina
595 { 'stats-switch' : {
596 'field-orderings' : {'default':
597 ['OFPacketIn', 'OFFlowMod', 'OFActiveFlow']},
598 'fields' : fields['switch']
599 }})
600
601 for obj_type in fields:
602 for field in fields[obj_type]:
603 for data_format in ['rate', 'value']:
604 units = fields[obj_type][field]['units']
605 if data_format == 'rate':
606 units += '/s'
607 self.pp.add_format('STATS-%s-%s-%s' % (obj_type,field,data_format),
608 'cli.init_stats_metadata()',
609 { 'stats-%s-%s-%s' % (obj_type,field,data_format) : { \
610 'field-orderings' : {'default': ['value', 'timestamp']},
611 'fields' : {'timestamp': {'verbose-name': 'Timestamp',
612 'formatter':
613 lambda x,y: time.ctime(x/1000)},
614 'value': {'units': units,
615 'verbose-name':
616 '%s (%s)' %
617 (fields[obj_type][field]['verbose-name'],
618 units)}}
619 }})
620
621 #
622 # --------------------------------------------------------------------------------
623 #
624 def command_packages_path(self):
625 command_descriptions = 'desc'
626 desc_path = resource_filename(__name__, command_descriptions)
627 if desc_path:
628 # print "desc_path %s" % desc_path
629 return desc_path
630 desc_path = os.path.join(os.path.dirname(__file__), command_descriptions)
631 if os.path.exists(desc_path):
632 return desc_path
633 return None
634
635
636 #
637 # --------------------------------------------------------------------------------
638 #
639 def command_packages_exists(self, version):
640 desc_path = self.command_packages_path()
641 if desc_path == None:
642 return None
643 version_path = os.path.join(desc_path, version)
644 if os.path.exists(version_path):
645 return version_path
646 return None
647
648
649 #
650 # --------------------------------------------------------------------------------
651 #
652 def add_command_packages(self, version):
653 """
654 Add all command and output format components
655
656 """
657 desc_path = self.command_packages_path()
658 if desc_path == None:
659 print 'No Command Descriptions subdirectory'
660 return
661
662 for path_version in [x for x in os.listdir(desc_path) if x.startswith(version)]:
663 print path_version
664 result_tuple = imp.find_module(path_version, [desc_path])
665 imp.load_module(version, result_tuple[0], result_tuple[1], result_tuple[2])
666 new_path = result_tuple[1]
667 cmds_imported = []
668 for cmds in os.listdir(new_path):
669 (prefix, suffix) = os.path.splitext(cmds)
670 if (suffix == '.py' or suffix == '.pyc') and prefix not in cmds_imported and prefix != '__init__':
671 cmds_imported.append(prefix)
672 result_tuple = imp.find_module(prefix, [new_path])
673 module = imp.load_module(prefix, result_tuple[0], result_tuple[1], result_tuple[2])
674 command.add_commands_from_module(version, module, self.dump_syntax)
675 self.pp.add_formats_from_module(version, module)
676 # print cmds_imported
677
678 #
679 # --------------------------------------------------------------------------------
680 #
681 def choices_text_builder(self, matches, max_len = None, col_width = None):
682 """
683 @param max_len integer, describes max width of any entry in matches
684 @param col_width integer, colun width
685 """
686 # Sort the choices alphabetically, but if the token ends
687 # in a number, sort that number numerically.
688 try:
689 entries = sorted(matches, utif.completion_trailing_integer_cmp)
690 except Exception, e:
691 traceback.print_exc()
692
693 if col_width == None:
694 # request to look it up
695 (col_width, line_length) = self.pp.get_terminal_size()
696 col_width = min(120, col_width)
697
698 if max_len == None:
699 # request to compute the max length
700 max_len = len(max(matches, key=len))
701
702 count = len(matches)
703 max_len += 1 # space after each choice
704 if max_len > col_width:
705 # one per line?
706 pass
707 else:
708 per_line = col_width / max_len
709 lines = (count + (per_line - 1)) / per_line
710 if lines == 1:
711 return ''.join(['%-*s' % (max_len, m) for m in entries])
712 else:
713 # fill the columns, which means skipping around the entries
714 result = []
715 for l in range(lines):
716 result.append(['%-*s' % (max_len, entries[i])
717 for i in range(l, count, lines)])
718 lines = [''.join(x) for x in result]
719 return '\n'.join(lines)
720
721
722 #
723 # --------------------------------------------------------------------------------
724 #
725 def matches_hook(self, subs, matches, max_len):
726
727 # one shot disabler, used to disable printing of completion
728 # help for two-column help display (for '?' character).
729 # completion printing here can only display the possible selections,
730 # for two-column mode, the reason why each keyword was added
731 # needs to be displayed, which is no longer available here.
732 if self.completion_print == False:
733 self.completion_print = True
734 return
735
736 choices_text = self.choices_text_builder(matches, max_len)
737 self.print_completion_help(choices_text)
738
739 #
740 # --------------------------------------------------------------------------------
741 #
742 def pre_input_hook(self):
743 """
744 """
745 pass
746
747
748 #
749 # --------------------------------------------------------------------------------
750 #
751 def desc_version_to_path_elem(self, version):
752 """
753 Version numbers like 1.0 need to be converted to the path
754 element associated with the number, like version100
755 """
756 try:
757 version_number = float(version)
758 version = 'version%s' % int(version_number * 100)
759 except:
760 pass
761
762 # temporary, use until version100 exists.
763 if version == 'version100' and not self.command_packages_exists(version):
764 version = 'version200'
765
766 return version
767
768
769 #
770 # --------------------------------------------------------------------------------
771 # init
772 #
773 def init(self):
774 global mi
775
776 self.completion_print = True
777 self.vendordb = VendorDB()
778 self.vendordb.init()
779
780 parser = OptionParser()
781 parser.add_option("-c", "--controller", dest="controller",
782 help="set default controller to CONTROLLER",
783 metavar="CONTROLLER", default=self.controller)
784 parser.add_option("-S", "--syntax", dest='dump_syntax',
785 help="display syntax of loaded commands",
786 action='store_true', default=False)
787 parser.add_option("-i", "--init", dest='init',
788 help="do not perform initialization checks",
789 action='store_true', default=False)
790 parser.add_option("-d", "--debug", dest='debug',
791 help='enable debug for cli (debug cli)',
792 action='store_true', default=False)
793 parser.add_option("-v", "--version", dest='desc_version',
794 help='select command versions (description group)',
795 default=None)
796 parser.add_option('-m', "--mode", dest='starting_mode',
797 help='once the cli starts, nest into this mode')
798 parser.add_option('-q', "--quiet", dest='quiet',
799 help='suppress warning messages',
800 action='store_true', default=False)
801 (self.options, self.args) = parser.parse_args()
802 self.controller = self.options.controller
803 if not self.controller:
804 self.controller = "127.0.0.1:8000"
805 self.dump_syntax = self.options.dump_syntax
806 if not self.dump_syntax:
807 self.dump_syntax = False
808 self.debug = self.options.debug
809
810 # command option, then env, then default
811 self.desc_version = self.options.desc_version
812 if self.desc_version == None:
813 self.desc_version = os.getenv('CLI_COMMAND_VERSION')
814 if self.desc_version == None:
815 self.desc_version = '2.0' # 'version200'
816
817 if self.desc_version:
818 self.desc_version = self.desc_version_to_path_elem(self.desc_version)
819
820 self.length = 0 # screen length.
821
822 self.set_controller_for_prompt()
823
824 if not sys.stdin.isatty():
825 self.batch = True
826
827 self.store = StoreClient()
828 self.store.set_controller(self.controller)
829
830 mi = Modi(self, CliModelInfo())
831 self.mi = mi
832 self.init_command_dict()
833
834 self.completion_reset()
835 self.completion_skip = False
836 self.completion_cache = True
837 readline.set_completer(self.completer)
838 readline.set_completer_delims("\t ")
839 # readline.set_pre_input_hook(self.pre_input_hook)
840 readline.set_completion_display_matches_hook(self.matches_hook)
841 if not self.batch:
842 self.init_history_file()
843 self.push_mode("login")
844
845 # starting mode,
846 starting_mode = self.options.starting_mode
847 if starting_mode == None:
848 starting_mode = os.getenv('CLI_STARTING_MODE')
849
850 if starting_mode:
851 if starting_mode == 'login':
852 pass
853 elif starting_mode == 'enable':
854 self.push_mode("enable")
855 elif starting_mode == 'config':
856 self.push_mode("enable")
857 self.push_mode("config")
858 else:
859 print 'Only login, enable or config allowed as starting modes'
860
861 # process quiet option
862 quiet = self.options.quiet
863 if quiet == None:
864 quiet = os.getenv('CLI_SUPPRESS_WARNING')
865
866 if quiet:
867 self.supress_warnings()
868
869 #
870 self.pp = PrettyPrinter(self)
871
872 #
873 # pattern matches
874 #
875 self.IP_ADDR_RE = re.compile(r'^(\d{1,3}\.){3}\d{1,3}$')
876 self.CIDR_RE = re.compile(r'^((\d{1,3}\.){3}\d{1,3})/(\d{1,2}?)$')
877 self.MAC_RE = re.compile(r'^(([A-Fa-f\d]){2}:?){5}[A-Fa-f\d]{2}$')
878 self.DPID_RE = re.compile(r'^(([A-Fa-f\d]){2}:?){7}[A-Fa-f\d]{2}$')
879 self.ACL_RE = re.compile(r'^\d+$') # just the leading digits
880 self.DIGITS_RE = re.compile(r'^\d+$')
881 self.HEX_RE = re.compile(r'^0x[0-9a-fA-F]+$')
882
883 # Initialize any middleware layers
884 init_midware(self, mi)
885 init_vnsw(self, mi)
886 rest_to_model.rest_to_model_init(self, mi)
887 run_config.init_running_config(self, mi)
888
889 # Initialize all doc tags, use a subdirectory based on
890 # the locale. This may be incorect if there's nothing
891 # in the configured locale for the returned locale value.
892 lc = locale.getdefaultlocale()
893 if lc == None or lc[0] == None:
894 print 'Locale not configured ', lc
895 lc = ('en_US', 'UTF8')
896 doc_dir = resource_filename(__name__, 'documentation/%s' % lc[0])
897 if doc_dir is None:
898 doc_dir = os.path.join(os.path.dirname(__file__), 'documentation', lc[0])
899 # print "doc_dir %s" % doc_dir
900 doc.add_doc_tags(doc_dir)
901
902 # Initialize the command module, than add command packages
903 command.init_command(self, mi)
904 self.add_command_packages(self.desc_version)
905
906 # save for possible later use
907 #print self.pp.format_table(self.pp.format_details())
908
909 #
910 if self.debug:
911 for (n,v) in self.command_submode_dict.items():
912 print "SUBMODE %s %s" % (n,v)
913 for (n,v) in self.command_dict.items():
914 print "MODE %s %s" % (n,v)
915
916
917 self.time_zone_list = None
918 command.add_completion('time-zone-completion', SDNSh.time_zone_completion)
919 command.add_validation('time-zone-validation', SDNSh.time_zone_validation)
920 command.add_action('set-clock', SDNSh.set_clock_action, {'kwargs': {'data': '$data'}})
921 command.add_action('begin-default-gateway-check', SDNSh.begin_default_gateway_check_action)
922 command.add_action('end-default-gateway-check', SDNSh.end_default_gateway_check_action)
923
924 command.add_command_type('config-with-default-gateway-check', {
925 'action': ('begin-default-gateway-check', 'write-fields', 'end-default-gateway-check'),
926 'no-action': ('begin-default-gateway-check', 'reset-fields', 'end-default-gateway-check')
927 })
928
929 #
930 if self.feature_enabled('experimental'):
931 print '***Warning: experimental features enabled***'
932 if self.debug:
933 self.sdndb = sdndb.SDNDB(self, self, self.pp)
934 print '--BEGIN--'
935 self.sdndb.display('core/switch', style='table')
936 self.sdndb.display('core/controller', style='table')
937 self.sdndb.display('core/controller', style='list')
938 print '--LINK--', self.pp.table_info['link']['field-orderings']
939 self.sdndb.display('topology/link')
940 self.sdndb.display('topology/switch-cluster')
941 self.sdndb.display('topology/switch-cluster', style='list')
942 self.sdndb.display('topology/enabled-port')
943
944 #
945 # --------------------------------------------------------------------------------
946 # check_rest_result
947 @staticmethod
948 def check_rest_result(result, message=None):
949 if isinstance(result, collections.Mapping):
950 error_type = result.get('error_type')
951 if error_type:
952 raise error.CommandRestError(result, message)
953
954 #
955 # --------------------------------------------------------------------------------
956 # get_table_from_store
957 # Caches the last result to try to cut down on the number of times large tables
958 # are loaded from the REST API
959 #
960 def get_table_from_store(self, obj_type, key = None, val = None, match = None):
961 return self.store.get_table_from_store(obj_type, key, val, match)
962
963 #
964 # --------------------------------------------------------------------------------
965 # get_object_from_store
966 # cover for call to get a specific object from a table, only a convenience.
967 #
968 def get_object_from_store(self, obj_type, obj_name):
969 return self.store.get_object_from_store(obj_type, obj_name)
970
971 #
972 # --------------------------------------------------------------------------------
973 # rest_query_objects
974 # cover for call to query objects from the database, only a convenience.
975 #
976 def rest_query_objects(self, obj_type, query_params=None):
977 return self.store.rest_query_objects(obj_type, query_params)
978
979 #
980 # --------------------------------------------------------------------------------
981 # rest_create_object
982 # REST API call to create an object, if this is the last table which
983 # was fetched, then discard that table.
984 #
985 def rest_create_object(self, obj_type, ident):
986 return self.store.rest_create_object(obj_type, ident)
987
988 #
989 # --------------------------------------------------------------------------------
990 # rest_delete_objects
991 #
992 def rest_delete_objects(self, obj_type, delete_params):
993 return self.store.rest_delete_objects(obj_type, delete_params)
994
995 #
996 # --------------------------------------------------------------------------------
997 # rest_delete_object
998 #
999 def rest_delete_object(self, obj_type, name):
1000 if type(name) == dict:
1001 return self.store.rest_delete_object(obj_type, name)
1002
1003 return self.store.rest_delete_object(obj_type, mi.pk(obj_type), name)
1004
1005 #
1006 # --------------------------------------------------------------------------------
1007 # rest_update_object
1008 #
1009 def rest_update_object(self, obj_type, name, obj_key_val, obj_key_data):
1010 return self.store.rest_update_object(obj_type, name, obj_key_val, obj_key_data)
1011
1012 #
1013 # --------------------------------------------------------------------------------
1014 # rest_simple_resquest_to_dict
1015 # Issue a store_simple_request and covert the result into a dict using json.load
1016 # Performs no exception handling
1017 #
1018 def rest_simple_request_to_dict(self, url):
1019 return json.loads(self.store.rest_simple_request(url))
1020
1021 #
1022 # --------------------------------------------------------------------------------
1023 # rest_post_request
1024 # Forward a low-level REST request to the store
1025 #
1026 def rest_post_request(self, url, obj, verb='PUT'):
1027 return self.store.rest_post_request(url, obj, verb)
1028
1029 #
1030 # --------------------------------------------------------------------------------
1031 # create_object
1032 # Helper function to help identify call sites which create objects
1033 # Errors result in printing an error message, then returning True
1034 #
1035 def create_object(self, obj_type, field_dict):
1036 try:
1037 self.rest_create_object(obj_type, field_dict)
1038 return False
1039 except Exception, e:
1040 errors = self.rest_error_to_dict(e, obj_type)
1041
1042 if errors != None:
1043 print self.rest_error_dict_to_message(errors)
1044 return True
1045
1046 #
1047 # --------------------------------------------------------------------------------
1048 # create_row
1049 # Helper function to help identify call sites which create new rows
1050 #
1051 # Return a pair of bool's, the first is an error indication, while the second
1052 # is True when the row needed to be created.
1053 #
1054 #
1055 def create_row(self, obj_type, name, row_dict = None):
1056 # create the row with a dictionary which includes all the
1057 # populated values for the foreign keys
1058 # the row is created.
1059 if row_dict == None:
1060 updated_dict = {}
1061 else:
1062 updated_dict = row_dict
1063
1064 #
1065 # Only set the primary key for obj_keys which aren't managed
1066 # by the CassandraSetting/COMPOUND_KEY_FIELDS (this is currently
1067 # a slightly different predicate than is_compound_key)
1068 #
1069 key = mi.pk(obj_type)
1070 type_info = mi.obj_type_info_dict[obj_type]['fields'][key]['type']
1071 if type_info != 'compound-key':
1072 updated_dict[mi.pk(obj_type)] = name
1073 updated_dict = self.associate_foreign_keys_with_dict(obj_type, updated_dict)
1074
1075 if self.create_object(obj_type, updated_dict):
1076 return [True, False]
1077 return [False, True]
1078
1079 #
1080 # --------------------------------------------------------------------------------
1081 # find_or_create_row
1082 # Return a pair of bool's, the first is an error indication, while the second
1083 # is True when the row needed to be created.
1084 #
1085 # When a new row is created, if the obj_type has a private_key, an attempt
1086 # is made to identify all the private keys, and populate values for each
1087 # of them.
1088 #
1089 def find_or_create_row(self, obj_type, name, row_dict = None):
1090 if row_dict == None:
1091 row_dict = {}
1092 try:
1093 result = self.get_object_from_store(obj_type, name)
1094 if result[mi.pk(obj_type)] == name:
1095 return [False, False]
1096 except Exception:
1097 pass
1098
1099 return self.create_row(obj_type, name, row_dict)
1100
1101 #
1102 # --------------------------------------------------------------------------------
1103 # associate_foreign_keys_with_dict
1104 # Passed an obj_type, and a dictionary, returns a dictionary with updated
1105 # key:value pairs for all the known private keys.
1106 #
1107 #
1108 def associate_foreign_keys_with_dict(self, obj_type, create_dict):
1109 #
1110 # Private key names are 'well known', so the names are used here to
1111 # idenify how the field ought to be populated.
1112 #
1113 for foreign_key_id in mi.obj_type_foreign_keys(obj_type):
1114 if foreign_key_id in create_dict:
1115 # already set in the dictionary
1116 pass
1117 elif mi.is_null_allowed(obj_type, foreign_key_id):
1118 continue
1119 elif foreign_key_id == 'vns':
1120 create_dict[foreign_key_id] = self.vns_name()
1121 elif foreign_key_id == 'vns-access-list' and \
1122 self.get_current_mode_obj_type() == 'vns-access-list':
1123 create_dict[foreign_key_id] = self.get_current_mode_obj()
1124 elif foreign_key_id == 'rule':
1125 # LOOK! this seems to need more work, what's the rule relationship?
1126 if obj_type == 'vns-interface':
1127 create_dict = associate_foreign_key_for_vns_interface(dict)
1128 else:
1129 traceback.print_stack()
1130 print self.error_msg("Internal: %s can't determine value " \
1131 "for foreign key %s" % (obj_type, foreign_key_id))
1132
1133 return create_dict
1134
1135 #
1136 # methods to manage the mode_stack, prompts, and mode_obj/mode_obj_type
1137 #
1138
1139 #
1140 # --------------------------------------------------------------------------------
1141 # set_contoller_for_prompt
1142 #
1143 def set_controller_for_prompt(self):
1144 if self.controller == "127.0.0.1:8000":
1145 self.controller_for_prompt = socket.gethostname()
1146 else:
1147 self.controller_for_prompt = self.controller
1148 self.update_prompt()
1149
1150 #
1151 # --------------------------------------------------------------------------------
1152 # update_prompt
1153 # There are several different prompts depending on the current mode:
1154 # 'host'> -- login mode
1155 # 'host'# -- enable mode
1156 # 'host'(config)# -- config mode
1157 # 'host'(config-vns-definition-name)# -- vns-definition mode
1158 # 'host'(config-vns-definition-interface-rule-name)# -- vns-defn,
1159 # interface-rule mode
1160 #
1161 def update_prompt(self):
1162 if self.current_mode().startswith("config"):
1163 current_mode = "(" + self.current_mode()
1164 if self.get_current_mode_obj() != None:
1165 if self.in_config_submode("config-vns"):
1166 # substitute '|' with '-'
1167 parts = self.get_current_mode_obj().split('|')
1168 current_mode += "-" + '-'.join(parts)
1169 self.prompt = str(self.controller_for_prompt) + current_mode + ")# "
1170 elif self.current_mode() == "enable":
1171 self.prompt = str(self.controller_for_prompt) + "# "
1172 else:
1173 self.prompt = str(self.controller_for_prompt) + "> "
1174
1175 #
1176 # --------------------------------------------------------------------------------
1177 # push_mode
1178 # Every pushed mode is a quad: <modeName, tableName, specificRow, exitCallback>
1179 #
1180 # obj_type is the name of an associated table/model (tableName)
1181 # obj is the key's value for the table/model's key (specificRow)
1182 #
1183 # The cli currently supports a mode of table/model row edit, where the
1184 # name of the table/mode is entered, along with an associated value for
1185 # key-column of the table. Once in that mode, other fields of the table
1186 # can be edited by entering the name of the field, along with a new value.
1187 #
1188 # The exitCallback is the nane of a method to call when the current pushed
1189 # level is getting pop'd.
1190 #
1191 def push_mode(self, mode_name, obj_type=None, obj=None, exitCallback=None):
1192 self.mode_stack.append( { 'mode_name' : mode_name,
1193 'obj_type' : obj_type,
1194 'obj' : obj,
1195 'exit' : exitCallback} )
1196 self.update_prompt()
1197
1198 #
1199 # --------------------------------------------------------------------------------
1200 # pop_mode
1201 # Pop the top of the stack of mode's.
1202 #
1203 def pop_mode(self):
1204 m = self.mode_stack.pop()
1205 if len(self.mode_stack) == 0:
1206 self.run = False
1207 else:
1208 self.update_prompt()
1209 return m
1210
1211 #
1212 # --------------------------------------------------------------------------------
1213 # mode_stack_to_rest_dict
1214 # Convert the stack of pushed modes into a collection of keys.
1215 # Can be used to build the rest api dictionary used for row creates
1216 #
1217 def mode_stack_to_rest_dict(self, rest_dict):
1218 #
1219 for x in self.mode_stack:
1220 if x['mode_name'].startswith('config-'):
1221 rest_dict[x['obj_type']] = x['obj']
1222
1223 return rest_dict
1224
1225 #
1226 # --------------------------------------------------------------------------------
1227 # current_mode
1228 # Return the string describing the current (top) mode.
1229 #
1230 def current_mode(self):
1231 if len(self.mode_stack) < 1:
1232 return ""
1233 return self.mode_stack[-1]['mode_name']
1234
1235 #
1236 # --------------------------------------------------------------------------------
1237 # in_config_submode
1238 # Return true when the current mode is editing the contents of one of the
1239 # rows of the table/model store.
1240 #
1241 def in_config_submode(self, prefix = "config-"):
1242 return self.current_mode().startswith(prefix)
1243
1244 #
1245 # --------------------------------------------------------------------------------
1246 # in_config_mode
1247 # Returns true for any config mode; this is any nested edit mode,
1248 # along with the base config mode
1249 #
1250 def in_config_mode(self):
1251 return self.current_mode().startswith("config")
1252 #
1253 # --------------------------------------------------------------------------------
1254 # in_config_switch_mode
1255 # Returns true when the switch mode has been entered via the 'switch <dpid>' command.
1256 #
1257 def in_config_switch_mode(self):
1258 return self.current_mode() == "config-switch"
1259 #
1260 # --------------------------------------------------------------------------------
1261 # in_config_switch_if_mode
1262 # Returns true when the switch-interface mode has been entered via the
1263 # 'switch <dpid>' command, then the interface <name> command
1264 #
1265 def in_config_switch_if_mode(self):
1266 return self.current_mode() == "config-switch-if"
1267
1268 #
1269 # --------------------------------------------------------------------------------
1270 # vns_name
1271 #
1272 def vns_name(self):
1273 mode_dict = self.mode_stack_to_rest_dict({})
1274 if 'vns' in mode_dict:
1275 return mode_dict['vns']
1276 if 'vns-definition' in mode_dict:
1277 return mode_dict['vns-definition']
1278 return None
1279
1280 #
1281 # --------------------------------------------------------------------------------
1282 # in_config_vns_mode
1283 # Returns true when the vns mode has been entered via the 'vns <vnsId>' command.
1284 #
1285 def in_config_vns_mode(self):
1286 return self.current_mode() == "config-vns"
1287 #
1288 # --------------------------------------------------------------------------------
1289 # in_config_vns_def_mode
1290 # Returns true when the vns mode has been entered via the
1291 # 'vns-definition <vnsId>' command.
1292 #
1293 def in_config_vns_def_mode(self):
1294 return self.current_mode() == "config-vns-def"
1295 #
1296 # --------------------------------------------------------------------------------
1297 # in_config_vns_def_if_rule_mode
1298 # Returns true when the vns mode has been entered via the
1299 # 'vns-definition <vnsId>' command, then into interface-rule mode.
1300 #
1301 def in_config_vns_def_if_rule_mode(self):
1302 return self.current_mode() == "config-vns-def-if-rule"
1303
1304 #
1305 # --------------------------------------------------------------------------------
1306 # in_config_vns_acl_mode
1307 # Returns true when the vns mode has been entered via the
1308 # 'vns <vnsId>' command, then into acl mode.
1309 #
1310 def in_config_vns_acl_mode(self):
1311 return self.current_mode() == "config-vns-acl"
1312
1313 #
1314 # --------------------------------------------------------------------------------
1315 # in_config_vns_if_mode
1316 # Returns true when the vns mode has been entered via the
1317 # 'vns <vnsId>' command, then into interface mode.
1318 #
1319 def in_config_vns_if_mode(self):
1320 return self.current_mode() == "config-vns-if"
1321
1322 #
1323 # --------------------------------------------------------------------------------
1324 # in_config_host_mode
1325 # Returns true when the vns mode has been entered via the
1326 # 'host <host-id>' command
1327 #
1328 def in_config_host_mode(self):
1329 return self.current_mode() == "config-host"
1330
1331 #
1332 # --------------------------------------------------------------------------------
1333 # in_config_controller_node_mode
1334 # Returns true when the vns mode has been entered via the
1335 # 'controller-node <controller-node-id>' command
1336 #
1337 def in_config_controller_node_mode(self):
1338 return self.current_mode() == "config-controller-node"
1339
1340 #
1341 # --------------------------------------------------------------------------------
1342 # in_config_controller_interface_mode
1343 # Returns true when the controller interface mode has been entered via the
1344 # 'interface Ethernet <#>' command from the controller-node config mode
1345 #
1346 def in_config_controller_interface_mode(self):
1347 return self.current_mode() == "config-controller-if"
1348
1349 #
1350 # --------------------------------------------------------------------------------
1351 # in_config_port_channel_mode
1352 #
1353 def in_config_port_channel_mode(self):
1354 return self.current_mode() == "config-port-channel"
1355
1356 # --------------------------------------------------------------------------------
1357 # set_current_mode_obj
1358 # Sets the name of the selected row (key's value)
1359 #
1360 def set_current_mode_obj(self, obj):
1361 self.mode_stack[-1]['obj'] = obj
1362
1363 #
1364 # --------------------------------------------------------------------------------
1365 # get_current_mode_obj
1366 # Gets the name of the current mode's selected row value (key's value)
1367 # This can return None.
1368 #
1369 def get_current_mode_obj(self):
1370 return self.mode_stack[-1]['obj']
1371
1372 #
1373 # --------------------------------------------------------------------------------
1374 # set_current_mode_obj_type
1375 # Set the table/model name for this current mode.
1376 #
1377 def set_current_mode_obj_type(self, obj_type):
1378 self.mode_stack[-1]['obj_type'] = obj_type
1379
1380 #
1381 # --------------------------------------------------------------------------------
1382 # get_current_mode_obj_type
1383 # Get the table/model name for this current mode.
1384 #
1385 def get_current_mode_obj_type(self):
1386 return self.mode_stack[-1]['obj_type']
1387
1388 #
1389 # --------------------------------------------------------------------------------
1390 # get_nested_mode_obj
1391 # Get the id of the object that matches the given object type
1392 # starting from the current mode and working backwards to the
1393 # top-level mode. If there's no mode that matches the given
1394 # object type, then return None (maybe should handle as exception?).
1395 #
1396 def get_nested_mode_obj(self, obj_type):
1397 for i in range(1, len(self.mode_stack)+1):
1398 # Use negative index so we search from the top/end of the stack
1399 mode = self.mode_stack[-i]
1400 if mode['obj_type'] == obj_type:
1401 return mode['obj']
1402 return None
1403
1404 #
1405 # helper functions to access commands, obj_types, and fields
1406 #
1407
1408 #
1409 # --------------------------------------------------------------------------------
1410 # all_obj_types_starting_with
1411 # Returns a list of all the current object types starting with the text
1412 # parameter.
1413 #
1414 #
1415 def all_obj_types_starting_with(self, text=""):
1416 if onos == 0:
1417 netvirt_feature = self.netvirt_feature_enabled()
1418 else:
1419 netvirt_feature = False
1420
1421 matched_obj_types = [x for x in mi.obj_types
1422 if x.startswith(text) and
1423 self.debug_obj_type(x) and
1424 (netvirt_feature or (x != 'vns-definition')) ]
1425 # synthetic object type based on the vns config submode.
1426 if self.in_config_vns_def_mode() and "interface-rule".startswith(text):
1427 matched_obj_types.append("interface-rule")
1428 if self.in_config_vns_mode() and "access-list".startswith(text):
1429 matched_obj_types.append("access-list")
1430 if self.in_config_vns_mode() and "interfaces".startswith(text):
1431 matched_obj_types.append("interfaces")
1432 return matched_obj_types
1433 #
1434 # --------------------------------------------------------------------------------
1435 # feature_enabled
1436 # Return True when a particular feature is enabled.
1437 #
1438 def feature_enabled(self, feature):
1439 # features not managed via store
1440 #
1441 if feature == 'experimental':
1442 return os.path.exists(self.feature_allow_experimental)
1443
1444 if feature == 'ha':
1445 try:
1446 with open(self.boot_config_filename, "r") as f:
1447 for line in f:
1448 if line.startswith('ha-config='):
1449 parts = line.split('=')
1450 if len(parts) > 0 and parts[1] == 'enabled\n':
1451 return True
1452 except:
1453 pass
1454 return False
1455
1456 # only current features which can be enabled disabled
1457 if feature not in [
1458 'vns', 'static-flow-pusher', 'performance-monitor']:
1459 return False
1460
1461 #
1462 try:
1463 entry = self.get_table_from_store('feature')
1464 except Exception, e:
1465 errors = self.rest_error_to_dict(e, 'feature')
1466 print self.rest_error_dict_to_message(errors)
1467 return True
1468
1469 feature_to_field_dict = {
1470 'vns' : 'netvirt-feature',
1471 'static-flow-pusher' : 'static-flow-pusher-feature',
1472 'performance-monitor' : 'performance-monitor-feature',
1473 }
1474
1475 if len(entry) == 0:
1476 if feature in feature_to_field_dict:
1477 field = feature_to_field_dict[feature]
1478 return mi.field_default_value('feature', field)
1479 return True
1480
1481 if len(entry) != 1:
1482 # need a mechanism to select one from the list
1483 return True
1484
1485 if feature in feature_to_field_dict:
1486 return entry[0][feature_to_field_dict[feature]]
1487
1488 # use the default value from the model (every feature needs a default value)
1489 defauilt_value = mi.field_default_value('feature', feature_to_field_dict[feature])
1490 if default_value == None:
1491 print self.error_msg('Feature %s missing default value' % feature)
1492 return True
1493 return default_value
1494
1495 '''
1496 #
1497 # --------------------------------------------------------------------------
1498 # address_space_default_create
1499 #
1500 # Create a default address space if it doesn't exist, which enables
1501 # 'address-space default',
1502 #
1503 def address_space_default_create(self):
1504 key = mi.pk('address-space')
1505 try:
1506 entries = self.get_table_from_store('address-space',
1507 key, 'default', 'exact')
1508 errors = None
1509 except Exception, e:
1510 errors = self.rest_error_to_dict(e, 'address-space')
1511 if not 'see_other' in errors:
1512 print self.rest_error_dict_to_message(errors)
1513 return
1514
1515 if len(entries) == 0:
1516 self.create_row('address-space', 'default')
1517
1518 #
1519 # --------------------------------------------------------------------------
1520 # tenant_default_create
1521 #
1522 # Create default tenant and system tenant if it doesn't exist, which enables
1523 # 'tenant default' and tenant system, router vrsystem,
1524 #
1525 def tenant_default_create(self):
1526 key = mi.pk('tenant')
1527 try:
1528 entries = self.get_table_from_store('tenant',
1529 key, 'default', 'exact')
1530 errors = None
1531 except Exception, e:
1532 errors = self.rest_error_to_dict(e, 'tenant')
1533 if not 'see_other' in errors:
1534 print self.rest_error_dict_to_message(errors)
1535 return
1536
1537 if len(entries) == 0:
1538 self.create_row('tenant', 'default')
1539 #external tenant
1540 try:
1541 entries = self.get_table_from_store('tenant',
1542 key, 'external', 'exact')
1543 errors = None
1544 except Exception, e:
1545 errors = self.rest_error_to_dict(e, 'tenant')
1546 if not 'see_other' in errors:
1547 print self.rest_error_dict_to_message(errors)
1548 return
1549
1550 if len(entries) == 0:
1551 self.create_row('tenant', 'external')
1552
1553 #system tenant and system router: vrsystem
1554 try:
1555 entries = self.get_table_from_store('tenant',
1556 key, 'system', 'exact')
1557 errors = None
1558 except Exception, e:
1559 errors = self.rest_error_to_dict(e, 'tenant')
1560 if not 'see_other' in errors:
1561 print self.rest_error_dict_to_message(errors)
1562 return
1563
1564 if len(entries) == 0:
1565 self.create_row('tenant', 'system')
1566
1567 try:
1568 entries = self.get_table_from_store('virtualrouter',
1569 key, 'vrsystem', 'exact')
1570 errors = None
1571 except Exception, e:
1572 errors = self.rest_error_to_dict(e, 'virtualrouter')
1573 if not 'see_other' in errors:
1574 print self.rest_error_dict_to_message(errors)
1575 return
1576
1577 if len(entries) == 0:
1578 self.create_row('virtualrouter', 'vrsystem',{"tenant":"system","vrname":"vrsystem"})
1579
1580 #
1581 # --------------------------------------------------------------------------
1582 # netvirt_feature_init
1583 # perform vns featrue enablement:
1584 # Create a default vns if it doesn't exist, which enables 'vns default',
1585 #
1586 def netvirt_feature_init(self):
1587 self.address_space_default_create()
1588 self.tenant_default_create()
1589 key = mi.pk('vns-definition')
1590 try:
1591 entries = self.get_table_from_store('vns-definition', key, 'default|default', 'exact')
1592 errors = None
1593 except Exception, e:
1594 errors = self.rest_error_to_dict(e, 'vns-definition')
1595 if not 'see_other' in errors:
1596 print self.rest_error_dict_to_message(errors)
1597 return
1598
1599 if len(entries) == 0:
1600 self.create_row('vns-definition', 'default', {"tenant":"default","vnsname":"default"})
1601
1602 #
1603 # --------------------------------------------------------------------------------
1604 # netvirt_feature_enabled
1605 # Return True when the vns feature is enabled
1606 #
1607 # When vns is disabled, particular vns commands are no longer
1608 # available, along with a few specific objects. Particular
1609 # code sites which are specifically concerned with these issues
1610 # disable these items. Another possible approach would be to
1611 # modify the base information sources when a feature is
1612 # enabled or disabled, ie: mi.obj_types and command_dict.
1613 # This more active approach isn't taken since during
1614 # the initialization process, a variety of different object
1615 # relationships are constructed; foreign key xref's, alias
1616 # key xref's, object key dictionaries, etc. If the feature
1617 # enabled were more active, then all these relationships would also
1618 # need to be recomputed.
1619 #
1620 def netvirt_feature_enabled(self):
1621 controller_netvirt_feature = self.feature_enabled("vns")
1622 if self.netvirt_feature_enabled_cached != controller_netvirt_feature:
1623 self.netvirt_feature_enabled_cached = controller_netvirt_feature
1624 if self.netvirt_feature_enabled_cached:
1625 self.netvirt_feature_init()
1626 if controller_netvirt_feature == False:
1627 return False
1628 return True
1629 '''
1630
1631 #
1632 # --------------------------------------------------------------------------------
1633 # get_obj_of_type
1634 # Return a single row, or None, of the table who's name is passed as obj_type
1635 # Returns None when when there's multiple matches of the nane in the table/model
1636 # Performs no error processing
1637 #
1638 def get_obj_of_type(self, obj_type, name):
1639 key = mi.pk(obj_type)
1640 if key:
1641 errors = None
1642 try:
1643 entries = self.get_table_from_store(obj_type)
1644 except Exception, e:
1645 errors = self.rest_error_to_dict(e, obj_type)
1646
1647 if errors:
1648 print self.rest_error_dict_to_message(errors)
1649 return None
1650
1651 entries = [x for x in entries if x.get(key, 'None') == name]
1652 if len(entries) != 1:
1653 return None
1654 return entries[0]
1655 else:
1656 return None
1657
1658 #
1659 # --------------------------------------------------------------------------------
1660 # fields_for_current_submode_starting_with
1661 # Return a list of choices which are fields names of model/store objects
1662 # Any fields which are primary keys are excluded from edit, as are any
1663 # foreign keys, this is managed through is_editable()
1664 #
1665 def fields_for_current_submode_starting_with(self, start_text=""):
1666 if not self.in_config_submode():
1667 return []
1668 if self.in_config_vns_mode():
1669 return []
1670 obj_type = self.get_current_mode_obj_type()
1671 if not obj_type in mi.obj_type_info_dict:
1672 return []
1673 fields = [x for x in mi.obj_type_info_dict[obj_type]['fields'].keys() \
1674 if mi.is_editable(obj_type, x)]
1675
1676 return [x for x in fields if x.startswith(start_text)]
1677
1678 #
1679 # --------------------------------------------------------------------------------
1680 # obj_types_for_config_mode_starting_with
1681 #
1682 # what obj types are available in the given submode
1683 # e.g., top-level config mode has switch and config-switch has
1684 # flow-entry but flow-entry should not be available in top-level
1685 # config nor should switch be available in config-switch submode!
1686 # LOOK! hardwired for now to test parsing - need to merge with model
1687 #
1688 def obj_types_for_config_mode_starting_with(self, start_text=""):
1689 if not self.in_config_mode():
1690 return []
1691 #
1692 # vns features
1693 if onos == 0:
1694 netvirt_features = self.netvirt_feature_enabled()
1695 else:
1696 netvirt_features = False
1697 vns_objects = [ 'vns-definition' ]
1698
1699 ret_list = [x for x in mi.obj_types
1700 if self.debug_obj_type(x) and
1701 not mi.is_obj_type_source_not_user_config(x)]
1702 return [x for x in ret_list if x.startswith(start_text) and
1703 (netvirt_features or not x in vns_objects) ]
1704
1705 @staticmethod
1706 def title_of(command):
1707 return command['title'] if type(command) is dict else command
1708
1709 #
1710 # --------------------------------------------------------------------------------
1711 # commands_feature_enabled
1712 #
1713 def commands_feature_enabled(self, commands):
1714 return [self.title_of(x) for x in commands
1715 if (not self.title_of(x) in self.command_name_feature) or
1716 command.isCommandFeatureActive(self.title_of(x),
1717 self.command_name_feature[self.title_of(x)])]
1718
1719 #
1720 # --------------------------------------------------------------------------------
1721 # commands_for_mode
1722 #
1723 def commands_for_mode(self, mode):
1724 """
1725 Walk the command dict, using interior submodes and compiling
1726 the list of available commands (could rebuild command_dict()
1727 to contain all the possible commands, but its good to know
1728 exactly which commands apply to this submode)
1729 """
1730
1731 # make a new list, so that items don't get added to the source
1732 ret_list = list(self.command_nested_dict.get('login', []))
1733 if mode == 'login':
1734 ret_list += self.command_dict.get('login', [])
1735 return ret_list
1736 ret_list += self.command_nested_dict.get('enable', [])
1737 if mode == 'enable':
1738 ret_list += self.command_dict.get('enable', [])
1739 return ret_list
1740
1741 if mode == 'config':
1742 ret_list += self.command_nested_dict.get('config', [])
1743 ret_list += self.command_dict.get('config', [])
1744 return ret_list
1745
1746 for idx in [x for x in self.command_nested_dict.keys() if mode.startswith(x)]:
1747 ret_list += self.command_nested_dict.get(idx, [])
1748
1749 ret_list += self.command_dict.get(mode, [])
1750
1751 # manage command who's names are regular expressions
1752 result = [x['re'] if type(x) == dict else x for x in ret_list]
1753
1754 return result
1755
1756 #
1757 # --------------------------------------------------------------------------------
1758 # commands_for_current_mode_starting_with
1759 #
1760 def commands_for_current_mode_starting_with(self,
1761 start_text = "", completion = None):
1762 """
1763 One of the difficult issues here is when the first item
1764 isn't a token, but rather a regular expression. This currently occur
1765 in a few places in the command description, and the mechanism for
1766 dealing with the issue here is ... uhm ... poor. The code here is
1767 a stopgap, and assumes the only regular expression supported
1768 is the <digits> one. This could be make a bit better based on
1769 the submode, but really, this entire first-token management should
1770 be improved.
1771 """
1772 if completion == None:
1773 completion = False
1774 #
1775 # vns features commands include:
1776 netvirt_feature_commands = ['vns', 'vns-definition']
1777 if onos == 0:
1778 netvirt_feature = self.netvirt_feature_enabled()
1779 else:
1780 netvirt_feature = False
1781
1782 mode_list = self.commands_for_mode(self.current_mode())
1783 ret_list = self.commands_feature_enabled(utif.unique_list_from_list(mode_list))
1784
1785 def prefix(x, start_text, completion):
1786 if type(x) == str and x.lower().startswith(start_text.lower()):
1787 return True
1788 if not completion and type(x) == re._pattern_type:
1789 return x.match(start_text)
1790 return False
1791
1792 def pattern_items(ret_list, prefix):
1793 matches = []
1794 for p in [x for x in ret_list if type(x) == re._pattern_type]:
1795 for c in command.command_registry:
1796 if c['mode'] != self.current_mode():
1797 continue
1798 if type(c['name']) != dict:
1799 continue
1800 first_word = c['name']
1801 if 'completion' not in first_word:
1802 continue
1803 completion = first_word['completion']
1804 if first_word['pattern'] == p.pattern:
1805 result = {}
1806 scopes = [ first_word,
1807 {
1808 'completions' : result,
1809 'data' : {},
1810 'text' : prefix,
1811 },
1812 ]
1813 command._call_proc(completion,
1814 command.completion_registry,
1815 scopes, c)
1816 matches = result.keys()
1817 return matches
1818
1819 matches = [x for x in ret_list if prefix(x, start_text, completion)]
1820 if completion:
1821 matches += pattern_items(ret_list, start_text)
1822 return matches
1823
1824
1825 #
1826 # --------------------------------------------------------------------------------
1827 # complete_optional_parameters
1828 #
1829 # Parse optional parameters. These can occur in any order.
1830 # Params argument is a hash mapping a parameter name to type
1831 # information.
1832 # words argument are remaining words in the command
1833 # line that aren't yet parsed
1834 #
1835 def complete_optional_parameters(self, params, words, text):
1836 i = 0
1837 while i < len(words):
1838 final = i+1 >= len(words)
1839 word = words[i]
1840 possible = [x for x in params if x.startswith(word)]
1841 param_name = possible[0]
1842 param = params[param_name]
1843
1844 if (param['type'] != 'flag'):
1845 if (final):
1846 argument = text
1847 if (param['type'] == 'enum'):
1848 return [x
1849 for x in param['values']
1850 if x.startswith(argument)]
1851 elif argument == '':
1852 if ('syntax_help' in param):
1853 self.print_completion_help(param['syntax_help'])
1854 else:
1855 self.print_completion_help('[%s argument]' % word)
1856 return
1857 i += 1
1858 i += 1
1859
1860 return [x for x in params if x.startswith(text)]
1861
1862 #
1863 # --------------------------------------------------------------------------------
1864 # parse_optional_parameters
1865 #
1866 @staticmethod
1867 def parse_optional_parameters(params, words):
1868 parsed = {}
1869 i = 0
1870 while i < len(words):
1871 word = words[i]
1872 possible = [x for x in params if x.startswith(word)]
1873 if len(possible) == 0:
1874 raise ParamException('unknown option: %s' % word)
1875 elif len(possible) > 1:
1876 raise ParamException('ambiguous option: %s\n%s' %
1877 (word, "\n".join(possible)))
1878 else:
1879 param_name = possible[0]
1880 param = params[param_name]
1881 if (param['type'] == 'flag'):
1882 parsed[param_name] = True
1883 else:
1884 if i+1 < len(words):
1885 argument = words[i+1]
1886 if (param['type'] == 'string'):
1887 parsed[param_name] = argument
1888 elif (param['type'] == 'int'):
1889 try:
1890 parsed[param_name] = int(argument)
1891 except ValueError:
1892 raise ParamException('option %s requires ' +
1893 'integer argument'
1894 % word)
1895 elif (param['type'] == 'enum'):
1896 arg_possible = [x
1897 for x in param['values']
1898 if x.startswith(argument)]
1899 if (len(arg_possible) == 0):
1900 raise ParamException('option %s value must be in (%s)' %
1901 (word,", ".join(param['values'])))
1902 elif (len(arg_possible) > 1):
1903 raise ParamException('ambiguous option %s value:\n%s' %
1904 (word, "\n".join(arg_possible)))
1905 else:
1906 parsed[param_name] = arg_possible[0]
1907 i += 1
1908 else:
1909 raise ParamException('option %s requires an argument'
1910 % word)
1911 i += 1
1912 return parsed
1913
1914 #
1915 # --------------------------------------------------------------------------------
1916 # annotated_obj_type_id
1917 #
1918 # Given an id's value for a compound key, crack the value into
1919 # parts, and annotate each part with the compound key name.
1920 #
1921 # For 'Not Found' errors, the obj_type is known, along with the value
1922 # for the id which wasn't found. If this is an id with a compound key,
1923 # leaking information about the key's implementiion should be avoided.
1924 # Here the key's value is cracked based on the composition character,
1925 # and the items are displayed with their key-value name.
1926 # eg: Error: Not Found vns:xXx rule-id:snapple (vns-interface-rule)
1927 #
1928 def annotated_obj_type_id(self, obj_type, error):
1929 key = mi.pk(obj_type)
1930 if mi.is_compound_key(obj_type, key):
1931 result = { key : error}
1932 mi.split_compound_into_dict(obj_type, key, result)
1933 return ', '.join(["%s:%s" % tuple(x) for x in result.items()
1934 if x[0] != key])
1935 return error
1936
1937 #
1938 # --------------------------------------------------------------------------------
1939 #
1940 def find_master(self):
1941 """
1942 Return a dictionary with two items: 'master', and 'ips'
1943 'master' identifes the ip address of the master, a string
1944 which is an ip address, or None.
1945 'ips' is a list of interface rows with populated ip addresses.
1946 """
1947 # collect all the controller-interfaces
1948 ips = [x for x in local_interfaces_firewall_open('tcp', [80, 8000], 'all')
1949 if (x['ip'] != '' or x['discovered-ip'] != '')]
1950
1951 master = None
1952 for ip in ips:
1953 if ip['ip'] != '':
1954 url = "http://%s/rest/v1/system/ha/role" % ip['ip']
1955 if ip['discovered-ip'] != '': # select discovered ip over ip
1956 url = "http://%s/rest/v1/system/ha/role" % ip['discovered-ip']
1957 result = self.store.rest_simple_request(url, use_cache = False)
1958 self.check_rest_result(result)
1959 ha_role = json.loads(result)
1960 if ha_role['role'] == 'MASTER':
1961 if ip['ip'] != '':
1962 master = ip['ip']
1963 if ip['discovered-ip'] != '':
1964 master = ip['discovered-ip']
1965
1966 return {'master': master, 'ips': ips}
1967
1968 #
1969 # --------------------------------------------------------------------------------
1970 # rest_error_to_dict
1971 # Turn an exception into an error dictionary, which can later be printed.
1972 # using rest_error_dict_to_message().
1973 #
1974 def rest_error_to_dict(self, e, detail=None):
1975 errors = None
1976 # try to identifify validation requests
1977 if isinstance(e, httplib.BadStatusLine):
1978 errors = {'connection_error' : 'REST API server %s: '
1979 'server not running' %
1980 self.controller}
1981 return errors
1982
1983 elif isinstance(e, urllib2.HTTPError):
1984 code = e.code
1985 error_returned = e.readline()
1986
1987 if code == 404:
1988 if detail:
1989 errors = {'not_found_error' : 'Not Found: %s' % detail}
1990 else:
1991 errors = {'not_found_error' : 'Not Found: %s' % error_returned}
1992 elif code == 500 or code == 403:
1993 # traceback.print_stack()
1994 errors = {'connection_error' : 'REST API server %s failure: %s' %
1995 (self.controller, error_returned)}
1996 elif code == 400:
1997 try:
1998 errors = json.loads(error_returned)
1999 except:
2000 # if the error can't be converted into a dictionary, then imbed the complete
2001 # errors as the value for a specific error key.
2002 errors = {'error_result_error': "Can't convert returned error: %s" % error_returned}
2003 elif code == 301:
2004 errors = {'moved_error': 'HttpError Moved Permanently %s' % e}
2005 elif code == 303:
2006 # Attempt to determine what list of ip addresses is for
2007 # the controller, try to find the Master. if any failure
2008 # occurs anywhere, drop down to a simple error message.
2009 try:
2010 minfo = self.find_master()
2011 ips = [x['ip'] for x in minfo['ips']]
2012 master = minfo['master']
2013
2014 if master:
2015 errors = {'see_other': 'This controller is in SLAVE '
2016 'mode. The command must be run in MASTER mode.'
2017 '\nMASTER at %s' % master}
2018 elif len(ips):
2019 errors = {'see_other': 'This controller is in SLAVE mode. '
2020 'The command must be run in MASTER mode.'
2021 '\nNo MASTER found, possible ip\'s %s' %
2022 (', '.join(ips))}
2023 else:
2024 errors = {'see_other': 'This controller is in SLAVE mode. '
2025 'The command must be run in MASTER mode.'
2026 '\nNo MASTER currently exists '}
2027 except:
2028 errors = {'see_other': 'This controller is in SLAVE mode. '
2029 'The command must be run in MASTER mode.'
2030 '\nNo ip addresses for controller available'}
2031 else:
2032 errors = {'unknown_error': 'HttpError %s' % error_returned}
2033 elif isinstance(e, NotFound):
2034 if e.obj_type in mi.obj_type_info_dict:
2035 errors = {'not_found_error' : 'Not Found: %s (%s)' %
2036 (self.annotated_obj_type_id(e.obj_type,
2037 e.name),
2038 e.obj_type)}
2039 else:
2040 errors = {'not_found_error' : 'Not Found: %s (%s)' %
2041 (e.name, e.obj_type)}
2042 else:
2043 if self.debug or self.debug_backtrace:
2044 traceback.print_stack()
2045 errors = {'unknown_error': "Need error management for error %s" % type(e)}
2046
2047 return errors
2048
2049 #
2050 #
2051 # --------------------------------------------------------------------------------
2052 # rest_error_is_not_found
2053 # Return True if this specific error was the result of a not-found situation
2054 #
2055 @staticmethod
2056 def rest_error_is_not_found(rest_error_dict):
2057 if 'not_found_error' in rest_error_dict:
2058 return True
2059 return False
2060
2061 #
2062 # --------------------------------------------------------------------------------
2063 # rest_error_dict_to_message
2064 # Turn an rest_error_dict returned from rest_error_to_dict
2065 # into an error message which can ge printed. Code assumes multiple errors
2066 # won't occur; if a 'field_error' exists, for example, a 'model_error' won't
2067 # also be posted in the error
2068 #
2069 def rest_error_dict_to_message(self, rest_error_dict):
2070 if rest_error_dict == None:
2071 return rest_error_dict
2072 error_msg = ""
2073 if 'field_errors' in rest_error_dict:
2074 for (k, v) in rest_error_dict['field_errors'].items():
2075 error_msg += "Syntax error: field %s: %s" % (k, v)
2076 # traceback.print_stack(), to find out why the error occured
2077 elif 'model_error' in rest_error_dict:
2078 error_msg += "%s" % rest_error_dict['model_error']
2079 elif 'not_found_error' in rest_error_dict:
2080 error_msg += "Error: %s" % rest_error_dict['not_found_error']
2081 elif 'connection_error' in rest_error_dict:
2082 error_msg += rest_error_dict['connection_error']
2083 elif 'error_result_error' in rest_error_dict:
2084 error_msg += rest_error_dict['error_result_error']
2085 elif 'moved_error' in rest_error_dict:
2086 error_msg += rest_error_dict['moved_error']
2087 elif 'see_other' in rest_error_dict:
2088 error_msg += rest_error_dict['see_other']
2089 elif 'unknown_error' in rest_error_dict:
2090 error_msg += rest_error_dict['unknown_error']
2091 elif 'description' in rest_error_dict and \
2092 rest_error_dict["description"] == 'saved':
2093 # not an error: { 'description' : 'saved' }
2094 return None
2095 elif 'error_type' in rest_error_dict: # http 400 errors
2096 description = rest_error_dict['description']
2097 if description.startswith('Error: '):
2098 description = description[len('Error: '):]
2099 return '%s (%s)' % (description,
2100 rest_error_dict['error_type'])
2101 else:
2102 error_msg = "REST API server on controller-node %s " % self.controller
2103 error_msg += "had %s error:\n" % rest_error_dict['error_type']
2104 error_msg += rest_error_dict['description']
2105 return error_msg
2106
2107 #
2108 # --------------------------------------------------------------------------------
2109 #
2110 # Commands of the CLI. A command "foo" should have two methods:
2111 # cp_foo(self, text, state) - completion for this command, return list of options
2112 # do_foo(self, line) - execute the command
2113 #
2114 # Given a string 'word', not containing any spaces, nor any break
2115 # characters associated with python, return the a invocable method
2116 # note that '-' are permitted, and converted to '_''s
2117 #
2118 # When in the vns or vns-definition mode, the selected functions
2119 # to call back have an additional component added to the called
2120 # back function. In the non-vns case, for example, the "show"
2121 # command would result in a method called do_show, while in the
2122 # vns modes, 'vns_' in inserted between the 'verb' and the noun,
2123 # for example, resulting in 'do_vns_show'
2124
2125 #
2126 # --------------------------------------------------------------------------------
2127 # method_from_name
2128 #
2129 def method_from_name(self, prefix, name):
2130 if type(name) != str:
2131 return None
2132 if self.in_config_submode("config-vns") and \
2133 getattr(self, prefix + "vns_" + name.replace("-","_"), None):
2134 return getattr(self, prefix + "vns_" + name.replace("-","_"))
2135 return getattr(self, prefix+name.replace("-","_"), None)
2136
2137 #
2138 # --------------------------------------------------------------------------------
2139 # command_method_from_name
2140 #
2141 def command_method_from_name(self, name):
2142 return self.method_from_name("do_", name) # do_XXX methods
2143
2144 #
2145 # --------------------------------------------------------------------------------
2146 # command_show_method_from_name
2147 #
2148 def command_show_method_from_name(self, name):
2149 return self.method_from_name("do_show_", name) # do_show_XXX methods
2150
2151 #
2152 # --------------------------------------------------------------------------------
2153 # cloud_method_from_name
2154 #
2155 def cloud_method_from_name(self, name):
2156 return self.method_from_name("cloud_", name) # cloud_XXX methods
2157
2158 #
2159 # --------------------------------------------------------------------------------
2160 # completion_method_from_name
2161 #
2162 def completion_method_from_name(self, name):
2163 return self.method_from_name("cp_", name) # cp_XXX (completion) methods
2164
2165 #
2166 # --------------------------------------------------------------------------------
2167 # completion_show_method_from_name
2168 #
2169 def completion_show_method_from_name(self, name):
2170 return self.method_from_name("cp_show_", name) # cp_show_XXX (completion) methods
2171
2172 #
2173 # --------------------------------------------------------------------------------
2174 # unique_key_from_non_unique
2175 #
2176 # Primary keys for cassandra for some keys are contenations of
2177 # several non-unique keys separated by a character not used for
2178 # any other purpose (in this case '|'). The concatenation
2179 # of non-unique keys is intended to create a unique key.
2180 #
2181 def unique_key_from_non_unique(self, words):
2182 return "|".join(words)
2183
2184 #
2185 # --------------------------------------------------------------------------------
2186 # prefix_search_key
2187 # Prefix's of primary keys for keys's built through unique_key_from_non_unique()
2188 #
2189
2190 def prefix_search_key(self, words):
2191 return self.unique_key_from_non_unique(words) + "|"
2192
2193 #
2194 # --------------------------------------------------------------------------------
2195 # implement_connect
2196 #
2197 def implement_connect(self, data):
2198 new_ip = data.get('ip-address')
2199 if new_ip == None:
2200 new_ip = data.get('controller-id')
2201 if not self.IP_ADDR_RE.match(new_ip):
2202 controller_id = alias_lookup('controller-alias', new_ip)
2203
2204 if controller_id == None:
2205 controller_id = new_ip
2206
2207 try:
2208 ifs = self.rest_query_objects('controller-interface',
2209 { 'controller' : controller_id } )
2210 if len(ifs) == 1:
2211 new_ip = ifs[0]['ip']
2212 except:
2213 return self.error_msg('Can\'t find controller named %s' % new_ip)
2214 pass
2215
2216 if 'port' in data:
2217 new_ip += str(data['port'])
2218 else:
2219 new_ip += ":80"
2220 #
2221 # request the version, if it fails, then don't allow the switch
2222 try:
2223 version_url = 'http://%s/rest/v1/system/version' % new_ip
2224 data = self.rest_simple_request_to_dict(version_url)
2225 except Exception, e:
2226 return self.error_msg('Could not connect to %s' % new_ip)
2227
2228 self.controller = new_ip
2229 self.store.set_controller(new_ip)
2230 self.set_controller_for_prompt()
2231 while (self.current_mode() != "login"):
2232 self.pop_mode()
2233 print "Switching to controller %s" % self.controller
2234
2235 return self.pp.format_entry(data[0], 'version')
2236
2237 def cp_exit(self, words, text, completion_char):
2238 self.print_completion_help("<cr>")
2239
2240 def do_exit(self, words=None):
2241 if self.mode_stack[-1]['exit']:
2242 method = self.mode_stack[-1]['exit']
2243 method()
2244 self.pop_mode()
2245
2246 def cp_logout(self, words, text, completion_char):
2247 self.print_completion_help("<cr>")
2248
2249 def do_logout(self, words=None):
2250 self.run = False
2251
2252 def cp_end(self, words, text, completion_char):
2253 self.print_completion_help("<cr>")
2254
2255 def do_end(self, words=None):
2256 while self.current_mode().startswith("config"):
2257 self.pop_mode()
2258
2259
2260 #
2261 # --------------------------------------------------------------------------------
2262 # do_show_tunnel
2263 # Command descriptions has no practical support for show commands yet.
2264 #
2265 def do_show_tunnel(self, words):
2266 if len(words) < 2:
2267 print self.syntax_msg("Usage: show tunnel [ <switch> | all ] active")
2268 return
2269 if words[1] != "active":
2270 return self.error_msg("Unknown request %s" % words[1])
2271
2272 # active --
2273
2274 if words[0] != 'all':
2275 words[0] = convert_alias_to_object_key("switch", words[0])
2276
2277 try:
2278 data = self.rest_simple_request_to_dict(
2279 'http://%s/rest/v1/tunnel-manager/%s/' %
2280 (self.controller, words[0]))
2281 except:
2282 return self.error_msg('Could not load tunnel-manager details')
2283
2284 if 'error' in data and data['error'] != None:
2285 return self.error_msg('tunnel-manager response: %s' % data['error'])
2286
2287 entries = []
2288 for entry in data['tunnMap'].keys():
2289 item = data['tunnMap'][entry]
2290 for remote in item['tunnelPorts']:
2291 remoteIp = "%s.%s.%s.%s" % ( int(remote[3:6]),
2292 int(remote[6:9]),
2293 int(remote[9:12]),
2294 int(remote[12:]))
2295 entries.append({ 'dpid': entry,
2296 'localTunnelIPAddr' : item['localTunnelIPAddr'],
2297 'tunnelPorts' : remoteIp
2298 })
2299
2300 return self.display_obj_type_rows('tunnel-details', entries)
2301
2302
2303 #
2304 # --------------------------------------------------------------------------------
2305 # cp_show_vns_access_list_entry
2306 #
2307 def cp_show_vns_access_list_entry(self, words, text, completion_char):
2308 return self.cp_vns_access_list_entry(words, text)
2309
2310 #
2311 # --------------------------------------------------------------------------------
2312 # do_show_vns_access_list_entry
2313 #
2314 def do_show_vns_access_list_entry(self, words):
2315 with_search_key = "<no_key>"
2316 if self.vns_name() is None:
2317 if len(words) > 0:
2318 search_object = ["vns-access-list-entry", words[0]]
2319 else:
2320 search_object = ["vns-access-list-entry"]
2321 elif len(words) > 0:
2322 with_search_key = '-'.join(words)
2323 words.insert(0, self.get_current_mode_obj())
2324 search_object = ["vns-access-list-entry",
2325 self.unique_key_from_non_unique(words)]
2326 else:
2327 search_object = ["vns-access-list-entry",
2328 self.prefix_search_key([self.get_current_mode_obj()])]
2329 return self.do_show_object(search_object, with_search_key)
2330
2331 #
2332 # --------------------------------------------------------------------------------
2333 # do_vns_no
2334 # implement the no command while in the context of vns/vns-definition
2335 #
2336 def do_vns_no(self, words):
2337 if len(words) < 1:
2338 return "Syntax: no <item> <key> : delete from the named item the specific key"
2339 else:
2340 vns_name_to_table_name = { "interfaces" : "vns-interface",
2341 "interface-rule" : "vns-interface-rule",
2342 "access-list" : "vns-access-list",
2343 "access-list-entry" : "vns-access-list-entry",
2344 "access-group" : "vns-interface-access-list",
2345 }
2346 if words[0] in vns_name_to_table_name:
2347 if len(words) < 2:
2348 return "Syntax: no %s <item> <key>" % words[0]
2349 #
2350 # the item to remove in for access-list-entry is an integer,
2351 # canonicalize the name before its used to search
2352 if words[0] == 'access-list-entry':
2353 if self.ACL_RE.match(words[1]):
2354 words[1] = str(int(words[1]))
2355 else:
2356 return self.error_msg('%s key %s must be an integer' %
2357 (words[0], words[1]))
2358
2359 name = self.unique_key_from_non_unique([self.get_current_mode_obj(), words[1]])
2360 table = vns_name_to_table_name[words[0]]
2361 errors = None
2362
2363 if words[0] == "entry" and not self.in_config_vns_acl_mode():
2364 return self.do_no(words) # let do_no() print error messages
2365 elif words[0] == "access-group" and not self.in_config_vns_if_mode():
2366 return self.do_no(words) # let do_no() print error messages
2367
2368 # access-group/vns-interface-access-list has 'id's with an additional field,
2369 # the in-out parameter, which is required to be able to delete the entry
2370 if words[0] == "access-group":
2371 if len(words) < 3:
2372 return self.syntax_msg('no access-group <acl-name> <in|out>')
2373 choices = [x for x in ["in", "out"] if x.startswith(words[2])]
2374 if len(choices) != 1:
2375 return self.syntax_msg('no access-group <acl-name> <in|out>')
2376 name = self.unique_key_from_non_unique([self.get_current_mode_obj(),
2377 self.vns_name(),
2378 words[1]])
2379 name = name + "|" + choices[0]
2380
2381 try:
2382 self.rest_delete_object(table, name)
2383 except Exception, e:
2384 errors = self.rest_error_to_dict(e, words[0] + " " + words[1])
2385
2386 if errors:
2387 return self.rest_error_dict_to_message(errors)
2388 #
2389 # cascade delete?
2390 if not errors:
2391 self.cascade_delete(table, name)
2392
2393 return None
2394
2395 return self.do_no(words)
2396
2397 #
2398 # --------------------------------------------------------------------------------
2399 # do_no_tag
2400 # Since the name and value must be present, this is currently a special
2401 # case. additionally, the two tables 'tag-mapping' and 'tag' are
2402 # intermingled. the 'tag' table manages the <namespace><name><value>
2403 # tuple, while the tag-mapping add <host> to that relationship When
2404 # the removal of the entry from the tag-mapping table leaves no further
2405 # <namespace><name><value>'s for any hosts, then the row from the
2406 # 'tag' table must be removed
2407 #
2408 def implement_no_tag(self, words):
2409 if not words or len(words) != 2 or words[1].find('=') == -1:
2410 return "Syntax: no tag <namespace.name>=<value>"
2411 else:
2412 name_and_value = words[1].split("=")
2413 if len(name_and_value) > 2:
2414 return "Syntax: no tag <namespace.name>=<value>"
2415
2416 name_part = name_and_value[0].split('.')
2417 if len(name_part) == 1:
2418 namespace = 'default'
2419 name = name_part[0]
2420 elif len(name_part) >= 2:
2421 namespace = '.'.join(name_part[:-1])
2422 name = name_part[-1]
2423
2424 value = name_and_value[1]
2425
2426 item = self.unique_key_from_non_unique([namespace,
2427 name,
2428 value,
2429 self.get_current_mode_obj()])
2430 #
2431 # To prevent leaking the unique key value in the 'Not Found'
2432 # error message, look up the item first, and display a unique
2433 # error message for this case
2434 #
2435 errors = None
2436 try:
2437 self.get_object_from_store('tag-mapping', item)
2438 except Exception, e:
2439 errors = self.rest_error_to_dict(e, "tag " + item)
2440
2441 if errors:
2442 if self.rest_error_is_not_found(errors):
2443 return self.error_msg("tag %s.%s=%s not found" %
2444 (namespace, name, value))
2445 else:
2446 return self.rest_error_dict_to_message(errors)
2447
2448 try:
2449 self.rest_delete_object('tag-mapping', item)
2450 except Exception, e:
2451 errors = self.rest_error_to_dict(e, "tag " + item)
2452
2453 if errors:
2454 return self.rest_error_dict_to_message(errors)
2455 #
2456 # if there are no more items in tag-mapping for the
2457 # <namespace>|<name>|<value>, then an entry needs to
2458 # removed from the 'tag' table (for cleanup)
2459 #
2460 key = self.unique_key_from_non_unique([namespace,
2461 name,
2462 value])
2463 try:
2464 tags = self.get_table_from_store('tag-mapping', 'tag', key)
2465 except Exception, e:
2466 errors = self.rest_error_to_dict(e, "tag-mapping tag " + key)
2467
2468 if errors:
2469 print self.rest_error_dict_to_message(errors)
2470 return
2471 if len(tags) == 0:
2472 try:
2473 self.rest_delete_object('tag', key)
2474 except Exception, e:
2475 errors = self.rest_error_to_dict(e, "tag " + item)
2476
2477 if errors:
2478 print self.rest_error_dict_to_message(errors)
2479 return
2480 #
2481 # cascade delete?
2482
2483 #
2484 # --------------------------------------------------------------------------------
2485 # vns_interface_rule_tags_validate
2486 # Provides a warning when the associated tag doesn't exist
2487 #
2488 def vns_interface_rule_tags_validate(self, obj_type, field, value):
2489 for tag_pair in value.split(','):
2490 if tag_pair.find('=') == -1 or len(tag_pair.split('=')) != 2:
2491 # field validation won't match the regular expression
2492 return
2493 (tag_name, tag_value) = tag_pair.split('=')
2494 tag_name_parts = tag_name.split('.')
2495 if len(tag_name_parts) == 1:
2496 tag_namespace = 'default'
2497 tag_name = tag_name_parts[0]
2498 elif len(tag_name_parts) >= 2:
2499 tag_namespace = '.'.join(tag_name_parts[:-1])
2500 tag_name = tag_name_parts[-1]
2501 else:
2502 # XXX not reached
2503 pass
2504
2505 # Validating using the 'tag' model to determine whether the
2506 # tag exists depends on having the tag value removed when there's
2507 # no more references to the entry. the cli manages the
2508 # 'tag'/'tag-mapping' models that way, but other uses of the
2509 # rest api may not.
2510 #
2511 key = self.unique_key_from_non_unique([tag_namespace, tag_name, tag_value])
2512 try:
2513 table = self.get_object_from_store('tag', key)
2514 except Exception, e:
2515 errors = self.rest_error_to_dict(e, 'tag')
2516 # special case for 'not found' error message
2517 if self.rest_error_is_not_found(errors):
2518 self.warning("tag `%s.%s %s' not yet defined" % \
2519 (tag_namespace, tag_name, tag_value))
2520 else:
2521 print self.rest_error_dict_to_message(errors)
2522 return None
2523
2524 #
2525 # --------------------------------------------------------------------------------
2526 # validate_switch_core_switch
2527 # Intended to display a warning message when the value is set to True
2528 #
2529 def validate_switch_core_switch(self, obj_type, field, value):
2530 if value == 'True': # XXX odd that the value needs to be quoted.
2531 self.warning("enabling core-switch on a switch with "
2532 "directly connected hosts will cause the same to "
2533 "be unable to connect")
2534
2535 #
2536 # --------------------------------------------------------------------------------
2537 # handle_specific_tag_fields
2538 # Called by handle_specific_obj_type to convert fields values from the
2539 # rest api into a displayable table
2540 #
2541 @staticmethod
2542 def handle_specific_tag_fields(entries):
2543 for entry in entries:
2544 fields = entry['id'].split('|')
2545 # XXX error if there's not four parts
2546 entry['namespace'] = fields[0]
2547 entry['name'] = fields[1]
2548 entry['value'] = fields[2]
2549
2550 #
2551 # --------------------------------------------------------------------------------
2552 # append_when_missing
2553 #
2554 @staticmethod
2555 def append_when_missing(unique_list, item):
2556 if not item in unique_list:
2557 unique_list.append(item)
2558
2559 #
2560 # --------------------------------------------------------------------------------
2561 # show_command_prefix_matches
2562 # show command's prefix matches, return True when the command matches
2563 # fpr the prefix described, used by get_valid_show_options() during
2564 # cp_show() to collect available command choice.
2565 #
2566 def show_command_prefix_matches(self, command, prefix):
2567 # convert any '-'s in the prefix tp "_"'s since func's have _ separators
2568 prefix = prefix.replace("-", "_")
2569 if command.startswith("do_show_"+prefix) and not command.startswith("do_show_vns_"):
2570 return True
2571 elif command.startswith("do_show_vns_" + prefix):
2572 if self.in_config_submode("config-vns"):
2573 if command == 'do_show_vns_access_list' and self.in_config_vns_mode():
2574 return True
2575 return self.vns_debug_show_commands(command)
2576 return False
2577 return False
2578
2579 #
2580 # --------------------------------------------------------------------------------
2581 # get_valid_show_options
2582 # used by cp_show to identify completion optinos
2583 #
2584 # returns a dictionary with two elements: 'commands', and 'objects'.
2585 # the 'commands' element is a list of possible commands while
2586 # the 'objects' element is a list of possible objects (tables/models in the store)
2587 #
2588 def get_valid_show_options(self, text):
2589 ret_hash = {}
2590 # first get commands
2591 opts = []
2592 matching_methods = [x for x in dir(self)
2593 if self.show_command_prefix_matches(x, text)]
2594
2595 if onos == 0:
2596 netvirt_feature = self.netvirt_feature_enabled()
2597 else:
2598 netvirt_feature = False
2599
2600 for method in matching_methods:
2601 m = re.search("do_show_(.*)", method)
2602 n = re.search("do_show_vns_(.*)", method)
2603 if n and self.in_config_submode('config-vns'):
2604 self.append_when_missing(opts, n.group(1).replace("_","-"))
2605 elif m:
2606 if netvirt_feature or m.group(1) != 'vns':
2607 self.append_when_missing(opts, m.group(1).replace("_","-"))
2608 #
2609 # remove command cases
2610 opts = [x for x in opts if x not in ["statistics", "object"]]
2611 if "access-group" in opts and not self.in_config_vns_if_mode():
2612 if not self.vns_debug_show_commands('vns-access-group'):
2613 opts.remove("access-group")
2614 if "access-list" in opts and not self.in_config_vns_mode():
2615 if not self.vns_debug_show_commands('vns-access-list'):
2616 opts.remove("access-list")
2617 if "interface" in opts and not self.in_config_vns_mode():
2618 if not self.vns_debug_show_commands('vns-interface'):
2619 opts.remove("interface")
2620 if "interface-rule" in opts and not self.in_config_vns_def_mode():
2621 if not self.vns_debug_show_commands('vns-interface-rule'):
2622 opts.remove("interface-rule")
2623 if "firewall" in opts and not self.in_config_controller_interface_mode():
2624 opts.remove("firewall")
2625 # synthetic object type based on the vnsconfig submode.
2626 if "access-list-entry" in opts and not self.in_config_vns_acl_mode():
2627 if not self.vns_debug_show_commands('vns-access-list-entry'):
2628 opts.remove("access-list-entry")
2629 ret_hash["commands"] = opts
2630
2631 # now get obj_types we can show
2632 opts = self.all_obj_types_starting_with(text)
2633 if self.in_config_submode() and "this".startswith(text):
2634 opts.append("this")
2635 ret_hash["objects"] = opts
2636 return ret_hash
2637
2638
2639 #
2640 # --------------------------------------------------------------------------------
2641 # cp_show_object
2642 # show <obj_type> (words[0] == obj_type)
2643 #
2644 def cp_show_object(self, words, text, completion_char):
2645 if len(words) == 1:
2646 return objects_starting_with(words[0], text)
2647 else:
2648 self.print_completion_help("<cr>")
2649 #
2650 # --------------------------------------------------------------------------------
2651 # get_attachment_points
2652 #
2653 @staticmethod
2654 def get_attachment_points(host, host_dap_dict):
2655 items = host_dap_dict.get(host['mac'])
2656 if not items or len(items) <= 1:
2657 return items
2658 #
2659 # sort by most recent items, if the last-seen field
2660 # exists for all entries
2661 #
2662 for item in items:
2663 if not 'last-seen' in item or item['last-seen'] == '':
2664 return items
2665
2666 ordered = sorted(items,
2667 key=lambda i: i['last-seen'],
2668 cmp=lambda x,y: cmp(y,x))
2669 ordered[0]['prime'] = True
2670 return ordered
2671
2672 #
2673 # --------------------------------------------------------------------------------
2674 # create_attachment_dict
2675 #
2676 def create_attachment_dict(self):
2677 return create_obj_type_dict('host-attachment-point', 'mac')
2678
2679 #
2680 # --------------------------------------------------------------------------------
2681 # get_ip_addresses
2682 #
2683 @staticmethod
2684 def get_ip_addresses(host, host_ip_dict):
2685 items = host_ip_dict.get(host['mac'])
2686 if not items or len(items) <= 1:
2687 return items
2688
2689 for item in items:
2690 if not 'last-seen' in item or item['last-seen'] == '':
2691 return items
2692 #
2693 # sort by most recent items, if every row has a 'last-seen' field
2694 #
2695 ordered = sorted(items,
2696 key=lambda i: i['last-seen'],
2697 cmp=lambda x,y: cmp(y,x))
2698 ordered[0]['prime'] = True
2699 return ordered
2700
2701 #
2702 # --------------------------------------------------------------------------------
2703 # create_ip_dict
2704 # Returns a dictionary with the host id (mac address) as the key,
2705 # and a list of associated rows for that host id.
2706 #
2707 def create_ip_dict(self):
2708 return create_obj_type_dict('host-network-address', 'mac')
2709
2710 #
2711 # --------------------------------------------------------------------------------
2712 # show_sort_obj_type
2713 # Returns a sorted list of entries based on the sort described by
2714 # the obj-dict
2715 #
2716 def show_sort_obj_type(self, obj_type, entries):
2717 sort = mi.obj_type_show_sort(obj_type)
2718 if not sort:
2719 return entries
2720 if sort == 'integer':
2721 key = mi.pk(obj_type)
2722 return sorted(entries, key=lambda k: k[key].split('|')[-1],
2723 cmp=lambda x,y: cmp(utif.try_int(x), utif.try_int(y)))
2724 elif sort == 'tail-integer':
2725 key = mi.pk(obj_type)
2726 def comparer(left, right):
2727 prefix = cmp(left[:-1], right[:-1])
2728 if prefix != 0:
2729 return prefix
2730 return cmp(utif.try_int(left[-1]), utif.try_int(right[-1]))
2731
2732 return sorted(entries, key=lambda k: k[key].split('|'), cmp=comparer)
2733 else:
2734 return self.error_msg("Unknown sort %s" % sort)
2735
2736 #
2737 # --------------------------------------------------------------------------------
2738 # display_obj_type_rows
2739 # Given an obj_type, rows, and whether or not a key was provided to search
2740 # with, generate the output table.
2741 #
2742 def display_obj_type_rows(self, obj_type, rows, with_key = '<no_key>', detail = "default"):
2743 #
2744 # handle spedicific issues with particular tables returned from storage
2745 # - change vns-id's with "|" into separate columns
2746 # - the host case does a 'merge' for attachment-points and IPs
2747 # (used in 'show host')
2748 def handle_specific_obj_type(obj_type, entries, field_orderings = 'default'):
2749 if obj_type == "host":
2750 # host_tag_dict = create_obj_type_dict('tag-mapping', 'host')
2751 for host in entries:
2752 host['vendor'] = self.vendordb.get_vendor(host['mac'])
2753 # host['tag'] = host_tag_dict.get(host['mac'], [])
2754 if obj_type == "host-config":
2755 host_dap_dict = self.create_attachment_dict()
2756 host_ip_dict = self.create_ip_dict()
2757 host_tag_dict = create_obj_type_dict('tag-mapping', 'mac')
2758 for host in entries:
2759 host['vendor'] = self.vendordb.get_vendor(host['mac'])
2760 host['attachment-points'] = self.get_attachment_points(host, host_dap_dict)
2761 host['ips'] = self.get_ip_addresses(host, host_ip_dict)
2762 host['tag'] = host_tag_dict.get(host['mac'], [])
2763 if obj_type == 'controller-node':
2764 for controller_node in entries:
2765 domain_name_servers = []
2766 domain_name_servers = self.rest_query_objects(
2767 'controller-domain-name-server',
2768 {'controller': controller_node['id'],
2769 'orderby' : 'timestamp'})
2770 controller_node['domain-name-servers'] = [domain_name_server['ip']
2771 for domain_name_server in domain_name_servers]
2772 controller_node['id'] = ' '.join(controller_node['id'].split('|'))
2773 if obj_type == 'controller-interface':
2774 for intf in entries:
2775 rules = [x['rule'] for x in self.get_firewall_rules(intf['id'])]
2776 intf['firewall'] = ', '.join(rules)
2777 # del intf['id'] XXX should the id really get displayed?
2778 tables_with_vns_ids = [ "vns-interface-rule", "vns-interface",
2779 "host-vns-interface", "vns-access-list",
2780 "vns-access-list-entry", "vns-interface-access-list" ]
2781 if obj_type in tables_with_vns_ids:
2782 self.vns_table_id_to_vns_column(obj_type, entries)
2783 if self.in_config_submode("config-vns") and field_orderings == 'default':
2784 field_orderings = 'vns-config'
2785 self.vns_join_host_fields(obj_type, entries)
2786 self.vns_foreign_key_to_base_name(obj_type, entries)
2787 if obj_type == 'vns-access-list-entry':
2788 # display the alternative access-list-text format
2789 vns_acl_entries_to_brief(entries)
2790 # field_orderings = 'acl-brief'
2791 if obj_type == 'vns-access-list-entry-brief':
2792 # display the alternative access-list-text format
2793 vns_acl_entries_to_brief(entries)
2794 field_orderings = 'acl-brief'
2795 #if obj_type == 'tag-mapping':
2796 #self.handle_specific_tag_fields(entries)
2797
2798 # XXX may need to revisit to prevent don't leak vns information
2799
2800 if obj_type in ['dvs-port-group', 'firewall-rule', 'tag-mapping']:
2801 for entry in entries:
2802 mi.split_compound_into_dict(obj_type,
2803 mi.pk(obj_type),
2804 entry)
2805 #
2806 # objects where foreigh keys are references to
2807 # compount key's, needing to be split.
2808 # XXX these can be identified by scanning the objects during init
2809 if obj_type in ['switch-interface-alias',
2810 ]:
2811 # only pick foreign keys which are compound keys
2812 fks = [x for x in mi.obj_type_foreign_keys(obj_type)
2813 if mi.is_compound_key(obj_type,x)]
2814 for entry in entries:
2815 for fk in fks:
2816 if fk in entry: # fk may be null-able
2817 mi.split_compound_into_dict(obj_type, fk, entry)
2818 if 'switch-interface' in entry:
2819 si = entry['switch-interface']
2820
2821 #
2822 if obj_type == 'firewall-rule':
2823 self.firewall_rule_add_rule_to_entries(entries)
2824 return field_orderings
2825
2826 field_orderings = handle_specific_obj_type(obj_type, rows, detail)
2827 #
2828 # join alias names. there is a good chance the alias's tables
2829 # are small. since iteration over all the rows is needed,
2830 # first identify the alias tables involved, then snap the
2831 # complete table into memory, and then decorae with the
2832 # specific alias fields. XXX it would be even better to
2833 # get a size of 'rows' vs the 'alias' table, and choose
2834 # the more performance path...
2835 #
2836 if obj_type in mi.alias_obj_type_xref:
2837 obj_key = mi.pk(obj_type)
2838 for alias in mi.alias_obj_type_xref[obj_type]:
2839 alias_field = mi.alias_obj_type_field(alias)
2840 if not alias_field:
2841 print self.error_msg("internal alias in display_obj_type")
2842 try:
2843 alias_rows = self.get_table_from_store(alias)
2844 except Exception, e:
2845 errors = self.rest_error_to_dict(e, alias)
2846 print self.rest_error_dict_to_message(errors)
2847 alias_rows = []
2848
2849 # invert the row once, moving the key to the data.
2850 alias_key = mi.pk(alias)
2851 rows_alias = dict([[a[alias_field], a[alias_key]] for a in alias_rows])
2852
2853 # collect the name of the field to join (fk_name)
2854 #
2855 (fk_obj_type, fk_name) = \
2856 mi.foreign_key_references(alias, alias_field)
2857 # now associate alias's with the row's field:
2858 for row in rows:
2859 if fk_name in row:
2860 if row[fk_name] in rows_alias:
2861 row[alias] = rows_alias[row[fk_name]]
2862 elif fk_obj_type in row:
2863 if row[fk_obj_type] in rows_alias:
2864 row[alias] = rows_alias[row[fk_obj_type]]
2865 #
2866 # this choice is better if the alias's table is large.
2867 if 0: #for row in rows:
2868 key = mi.pk(obj_type)
2869 for alias in mi.alias_obj_type_xref[obj_type]:
2870 field = mi.alias_obj_type_field(alias)
2871 if not field:
2872 print self.error_msg("internal alias in display_obj_type")
2873 alias_row = self.get_table_from_store(alias,
2874 field,
2875 row[key],
2876 "exact")
2877 if len(alias_row) > 1:
2878 print 'Error: multiple aliases for %s' % \
2879 alias
2880 elif len(alias_row) == 1:
2881 row[alias] = alias_row[0][mi.pk(alias)]
2882
2883 if len(rows) == 0:
2884 if with_key != '<no_key>':
2885 return self.error_msg("No %s %s found" % (obj_type, with_key))
2886 else:
2887 return self.pp.format_table(rows, obj_type, field_orderings)
2888 elif len(rows) == 1 and with_key != '<no_key>':
2889 return self.pp.format_entry(rows[0], obj_type,
2890 debug = self.debug)
2891
2892 return self.pp.format_table(self.show_sort_obj_type(obj_type, rows),
2893 obj_type,
2894 field_orderings)
2895
2896 #
2897 # --------------------------------------------------------------------------------
2898 # do_show_object
2899 # Implements the show command for objects (table/model output of rows)
2900 #
2901 def do_show_object(self, words, with_search_key = None):
2902 obj_type = words[0]
2903
2904 #
2905 # The cli replaces some user names with table names
2906 if obj_type == 'tag':
2907 obj_type = 'tag-mapping'
2908
2909 match = "startswith"
2910 #
2911 # Allow "this" reference if we are in config-submode, change the
2912 # match to an exact match to prevent prefix matching
2913 if obj_type == "this":
2914 if self.in_config_submode():
2915 obj_type = self.get_current_mode_obj_type()
2916 words = [obj_type, "this"]
2917 match = "exact"
2918 else:
2919 return self.error_msg("\"this\" can only be used in configuration"
2920 " submode, such as switch, port, host, etc.")
2921 #
2922 # complete table lookup: 'show <table>'
2923 if len(words) == 1:
2924 if with_search_key == None:
2925 with_search_key = "<no_key>"
2926 errors = None
2927 try:
2928 entries = self.get_table_from_store(obj_type)
2929 except Exception, e:
2930 errors = self.rest_error_to_dict(e, obj_type)
2931
2932 if errors:
2933 return self.rest_error_dict_to_message(errors)
2934 return self.display_obj_type_rows(obj_type, entries, with_search_key)
2935
2936 # table with key: "show <table> <key>"
2937 if len(words) == 2:
2938 obj = words[1]
2939 if with_search_key == None:
2940 with_search_key = obj
2941
2942 # Allow "this" reference if we are in config-submode
2943 if obj == "this" and \
2944 obj_type == self.get_current_mode_obj_type() \
2945 and self.in_config_submode():
2946 obj = self.get_current_mode_obj()
2947
2948 key = mi.pk(obj_type)
2949 if key:
2950 obj = convert_alias_to_object_key(obj_type, obj)
2951 if mi.obj_type_has_model(obj_type):
2952 entries = self.get_table_from_store(obj_type, key, obj, match)
2953 else:
2954 # XXX raise internal error? model doesn't exist.
2955 print command._line(), obj_type
2956 entries = []
2957 return self.display_obj_type_rows(obj_type, entries, with_search_key)
2958 else:
2959 return self.pp.format_table(self.get_table_from_store(obj_type),
2960 obj_type)
2961 elif "stats".startswith(words[2]):
2962 return self.helper_show_object_stats(words)
2963 else:
2964 return "Syntax: show <table> <key>: " \
2965 "Unrecognized input: " + " ".join(words[2:])
2966
2967 #
2968 # --------------------------------------------------------------------------------
2969 # helper_show_object_stats
2970 #
2971 def helper_show_object_stats(self, words):
2972 if self.stats_metadata == None:
2973 self.init_stats_metadata()
2974
2975 # get object type
2976 object_type = words[0]
2977
2978 # The rest stats api differs from the CLI in some of the
2979 # object names, so this map allows us to fix it
2980 if object_type in self.stats_restapi_map:
2981 object_type = self.stats_restapi_map[object_type]
2982
2983 if object_type not in self.stats_type_metadata:
2984 return self.error_msg('no statistics available for object type %s' %
2985 object_type)
2986
2987 # Get the specific object to operate on
2988 sobject = words[1];
2989
2990 # the controller-node objects don't currently have any real
2991 # concept of a unique ID for a controller; for now the stats
2992 # are always labeled 'localhost' even though that will never
2993 # appear in the controller-node list
2994 if ('controller-node'.startswith(object_type)):
2995 sobject = 'localhost'
2996
2997 tocheck = []
2998 # get statistic name
2999 if len(words) > 3:
3000 if (words[3] not in self.stats_metadata or
3001 self.stats_metadata[words[3]]['target_type'] != object_type):
3002 return self.error_msg('no statistic %s available for object type %s' %
3003 (words[3], object_type))
3004 tocheck.append(words[3])
3005 else:
3006 tocheck = [item['name']
3007 for item in self.stats_type_metadata[object_type]]
3008
3009 parsed_options = {}
3010 if (len(words) > 4):
3011 try:
3012 parsed_options = \
3013 self.parse_optional_parameters(self.stats_optional_params,
3014 words[4:])
3015 except ParamException as e:
3016 return self.error_msg(e)
3017
3018 display = 'latest-value'
3019 timespec = False
3020 now = int(time.time()*1000)
3021 query_params = {'start-time': now - 3600000,
3022 'end-time': now,
3023 'sample-count': 25,
3024 'data-format': 'value'}
3025
3026 if 'start-time' in parsed_options:
3027 timespec = True
3028 try:
3029 query_params['start-time'] = self.parse_date(parsed_options['start-time'])
3030 except Exception as e:
3031 return self.error_msg('invalid start time %s: %s' %
3032 (parsed_options['start-time'], e))
3033
3034 if 'end-time' in parsed_options:
3035 timespec = True
3036 try:
3037 query_params['end-time'] = self.parse_date(parsed_options['end-time'])
3038 except Exception as e:
3039 return self.error_msg('invalid end time %s: %s' %
3040 (parsed_options['end-time'], e))
3041
3042 if 'duration' in parsed_options:
3043 timespec = True
3044 try:
3045 query_params['duration'] = parsed_options['duration']
3046 if 'start-time' in parsed_options:
3047 del query_params['end-time']
3048 else:
3049 del query_params['start-time']
3050
3051 except:
3052 return self.error_msg('invalid duration %s' %
3053 parsed_options['duration'])
3054
3055 if 'display' in parsed_options:
3056 display = parsed_options['display']
3057
3058 if timespec and display == 'latest-value':
3059 display = 'graph'
3060
3061 if display == 'graph':
3062 query_params['sample-count'] = self.pp.get_terminal_size()[0]
3063
3064 for p in ['sample-count', 'limit', 'sample-window',
3065 'sample-interval', 'data-format']:
3066 if p in parsed_options:
3067 query_params[p] = parsed_options[p]
3068
3069 if display == 'latest-value':
3070 data = {}
3071 for item in tocheck:
3072 url = ('http://%s/rest/v1/stats/data/%s/%s/%s/%s/' %
3073 (self.controller, self.cluster, object_type,
3074 sobject, item))
3075 try:
3076 (timestamp, value) = self.rest_simple_request_to_dict(url)
3077 data[self.stats_metadata[item]['name']] = "%d" % value
3078 except:
3079 data[self.stats_metadata[item]['name']] = "[Null]"
3080
3081 return self.pp.format_entry(data, 'stats-%s' % object_type)
3082 else:
3083 item = tocheck[0]
3084 url = ('http://%s/rest/v1/stats/data/%s/%s/%s/%s/?%s' %
3085 (self.controller, self.cluster, object_type,
3086 sobject, item, "&".join(['%s=%s' % (k,v)
3087 for k,v
3088 in query_params.iteritems()])))
3089 try:
3090 json_data = self.rest_simple_request_to_dict(url)
3091 except Exception as e:
3092 if self.debug_backtrace:
3093 print 'FAILED URL', url
3094 return self.error_msg('could not load stats data: %s' % e)
3095
3096 if display == 'table':
3097 data = []
3098 for (timestamp, value) in json_data:
3099 data.append({'timestamp': int(timestamp),
3100 'value': value})
3101 return self.pp.format_table(data, 'stats-%s-%s-%s' %
3102 (object_type, item,
3103 query_params['data-format']))
3104 elif display == 'graph':
3105 return self.pp.format_time_series_graph(json_data,
3106 'stats-%s-%s-%s' %
3107 (object_type, item,
3108 query_params['data-format']))
3109 #
3110 # --------------------------------------------------------------------------------
3111 # parse_date
3112 #
3113 @staticmethod
3114 def parse_date(text):
3115 if (text == 'now' or text == 'current'):
3116 return int(time.time()*1000)
3117
3118 try:
3119 return int(text)
3120 except:
3121 pass
3122
3123 for f, pre in [('%Y-%m-%dT%H:%M:%S', None),
3124 ('%Y-%m-%d %H:%M:%S', None),
3125 ('%Y-%m-%dT%H:%M:%S%z', None),
3126 ('%Y-%m-%d %H:%M:%S%z', None),
3127 ('%Y-%m-%d', None),
3128 ('%m-%d', '%Y-'),
3129 ('%H:%M', '%Y-%m-%dT')]:
3130 try:
3131 t = text
3132 if pre:
3133 pref = datetime.datetime.now().strftime(pre)
3134 f = pre + f
3135 t = pref + t
3136
3137 thetime = datetime.datetime.strptime(t, f)
3138 return int(time.mktime(thetime.timetuple())*1000)
3139 except:
3140 pass
3141
3142 raise ValueError('count not parse %s as a timestamp' % text)
3143
3144
3145 #
3146 # --------------------------------------------------------------------------------
3147 # display_vns_interface
3148 # Search dist is a collection of compund key entities
3149 #
3150 def display_vns_interface(self, vns_name, search_dict, with_search_key, detail = 'default'):
3151 obj_type = 'vns-interface'
3152 obj_key = mi.pk(obj_type)
3153 try:
3154 entries = rest_to_model.get_model_from_url(obj_type, search_dict)
3155 #entries = self.rest_query_objects(obj_type,
3156 #search_dict)
3157 errors = None
3158 except Exception, e:
3159 errors = self.rest_error_to_dict(e, obj_type)
3160
3161 if errors:
3162 return self.rest_error_dict_to_message(errors)
3163 vns_rules = create_obj_type_dict('vns-interface-rule',
3164 mi.pk('vns-interface-rule'),
3165 search_dict
3166 )
3167 #
3168 # divide up the result into two parts, the first part is
3169 # the physical interfaces, the second is the mac interfaces...
3170 phys = []
3171 macs = []
3172
3173 for entry in entries:
3174 fields = entry[obj_key].split('|')
3175 entry['tenant'] = fields[0]
3176 entry['vns']=fields[1]
3177 # all these fields must have two parts.
3178 if len(fields) != 3:
3179 continue
3180
3181 # choose between switch based rules and mac/ip based rules
3182 rule = 'default'
3183 switch = None
3184 virt = None
3185 #
3186 # Each address-space would have a default vns named
3187 # named <as>-default, except the 'default' adddress-space which
3188 # has a default vns named just 'default'
3189 #
3190 if fields[1] == "default" or fields[1].endswith("-default"):
3191 virt = 'default'
3192 if fields[2].find('/') >= 0:
3193 virt = fields[2].split('/')[1]
3194 else:
3195 if not 'rule' in entry or entry['rule']=='default':
3196 if_rule = []
3197 virt = True
3198 # continue
3199 else:
3200 rule = entry['rule']
3201 if_rule = vns_rules[rule][0]
3202
3203 if 'switch' in if_rule:
3204 switch = if_rule['switch']
3205 elif 'mac' in if_rule:
3206 virt = if_rule['mac']
3207 entry['mac'] = virt
3208 elif 'ip-subnet' in if_rule:
3209 virt = {'ip-address': if_rule['ip-subnet']}
3210 if 'ips' in entry:
3211 entry['ips'].append(virt)
3212 else:
3213 entry['ips'] = [virt]
3214 elif 'tags' in if_rule:
3215 virt = if_rule['tags']
3216 entry['tags'] = virt
3217 elif 'vlans' in if_rule:
3218 virt = "vlan %s" % if_rule['vlans']
3219 entry['vlans'] = virt
3220
3221 if switch:
3222 phys.append(entry)
3223 if virt:
3224 entry['veth'] = entry[obj_key].split('|')[2]
3225 macs.append(entry)
3226
3227 output = ''
3228 if len(phys):
3229 self.vns_join_switch_fields(vns_name, phys)
3230 output += self.display_obj_type_rows('vns-interface-phys', phys, with_search_key, detail)
3231 if len(macs):
3232 if len(phys):
3233 output += '\n'
3234 self.vns_join_host_fields(obj_type, macs)
3235 output += self.display_obj_type_rows('vns-interface-macs', macs, with_search_key, detail)
3236
3237 if output != '':
3238 return output
3239 return self.pp.format_table([],'display-vns-interface')
3240
3241 #
3242 # --------------------------------------------------------------------------------
3243 # find_and_display_vns_interfaces
3244 #
3245 def find_and_display_vns_interface(self, vns_name, words):
3246 with_search_key = "<no_key>"
3247 search_dict = { 'vns' : vns_name }
3248 if len(words) == 1:
3249 search_dict['interface'] = words[0]
3250 elif len(words) > 1:
3251 return self.error_msg("Additional search keys after interace")
3252
3253 return self.display_vns_interface(vns_name, search_dict, with_search_key)
3254
3255 #
3256 # --------------------------------------------------------------------------------
3257 # do_show_vns_interfaces
3258 # This definition is needed to allow the vns-interface table/model to
3259 # be displayed in basic config mode. do_show() searches names of functions
3260 # using the prefix of the 'show' argument; without this function,
3261 # it find 'do_show_vns_interface_rule' and selects that function to
3262 # display results for 'show vns-interface`.
3263 #
3264 def do_show_vns_interfaces(self, words):
3265 with_search_key = "<no_key>"
3266 local_vns_name = self.vns_name()
3267 if self.vns_name() is None:
3268 if len(words) > 0:
3269 local_vns_name = words[0]
3270 search_dict = { 'vns' : local_vns_name }
3271 else:
3272 # only case where the vns name is included is the output
3273 return self.do_show_object(['vns-interface'], with_search_key)
3274 else:
3275 search_dict = { 'vns' : local_vns_name }
3276 if len(words) == 1:
3277 with_search_key = words
3278 search_dict['interface'] = words[0]
3279 elif len(words) > 2:
3280 return self.error_msg("Additional search keys after interace")
3281
3282 return self.display_vns_interface(local_vns_name, search_dict, with_search_key)
3283
3284
3285 #
3286 # --------------------------------------------------------------------------------
3287 # display_vns_mac_address_table
3288 # Used by 'show vns <n> mac-address-table', and vns mode 'show mac-address-table'
3289 #
3290 def display_vns_mac_address_table(self, vns_name, words):
3291 # note: 'config-vns' matches both vns and vns-definition mode,
3292 # while 'config-vns-' matches only vns-definition mode.
3293 # the id for the host-vns-interface table has the host as the 'prefix',
3294 # preventing searching based on the prefix. the vns is not even a foreign
3295 # key, which means the complete table must be read, then the vns association
3296 # must be determined, and then matched
3297 filter = { 'vns' : vns_name } if vns_name != 'all' else {}
3298 if len(words):
3299 filter['mac'] = words[0]
3300 entries = rest_to_model.get_model_from_url('host-vns-interface', filter)
3301 for entry in entries:
3302 fields=entry['vns'].split('|')
3303 entry['tenant']=fields[0]
3304 entry['vns']=fields[1]
3305 with_key = '<no_key>'
3306 if len(words) > 0:
3307 with_key = words[0]
3308
3309 #detail = 'vns' # exclude vns column, vns is named
3310 detail = 'default' # exclude vns column, vns is named
3311 if vns_name == 'all':
3312 detail = 'default' # include vns column, vns not named
3313
3314 # self.vns_join_host_fields('host-vns-interface', entries)
3315 return self.display_obj_type_rows('host-vns-interface-vns', entries, with_key, 'default')
3316
3317 #
3318 # --------------------------------------------------------------------------------
3319 # do_show_vns_mac_address_table
3320 # config and vns mode 'show mac-address-table' command
3321 #
3322 def do_show_vns_mac_address_table(self, words):
3323 if words == None or len(words) > 2:
3324 return self.syntax_msg('show mac-address-table <host>')
3325
3326 if self.in_config_submode('config-vns'):
3327 return self.display_vns_mac_address_table(self.vns_name(), words)
3328
3329 if len(words) > 0:
3330 return self.do_show_object(['host'] + words, 'with_key')
3331 return self.do_show_object(['host'])
3332
3333 #
3334 # --------------------------------------------------------------------------------
3335 # do_show_switch_cluster
3336 #
3337 def do_show_switch_cluster(self, words):
3338 data = self.rest_simple_request_to_dict(
3339 'http://%s/rest/v1/realtimestatus/network/cluster/' % self.controller)
3340
3341 formatted_data = []
3342 for (clusterid, switchids) in data.items():
3343 row = {}
3344 row['switches'] = switchids
3345 row['cluster-id'] = clusterid
3346 formatted_data.append(row)
3347 return self.display_obj_type_rows('switch-cluster', formatted_data)
3348
3349 #
3350 # --------------------------------------------------------------------------------
3351 # do_show_controller_summary
3352 #
3353 def do_show_controller_summary(self, words):
3354 data = self.rest_simple_request_to_dict(
3355 'http://%s/rest/v1/controller/summary' % self.controller)
3356
3357 # XXX Need a way to sort the data in a way that makes sense
3358 for (key, value) in data.items():
3359 yield key + ": " + str(value) + "\n"
3360
3361 #
3362 # --------------------------------------------------------------------------------
3363 # get_statistics_type
3364 #
3365 @staticmethod
3366 def get_statistics_type(stat_type):
3367 for s in ["flow", "port", "table", "desc", "aggregate", "features", "trace",]:
3368 if s.startswith(stat_type):
3369 return s
3370 return None
3371
3372 #
3373 # --------------------------------------------------------------------------------
3374 # fix_realtime_flows_wildcard_fields
3375 #
3376 def fix_realtime_flow_wildcard_fields(self, in_data):
3377 for l in in_data:
3378 if 'wildcards' in l:
3379 wildcards = l['wildcards']
3380 bit_field_map = {
3381 0 : 'inputPort',
3382 1 : 'dataLayerVirtualLan',
3383 2 : 'dataLayerSource',
3384 3 : 'dataLayerDestination',
3385 4 : 'dataLayerType',
3386 5 : 'networkProtocol',
3387 6 : 'transportSource',
3388 7 : 'transportDestination',
3389 13 : 'networkSource',
3390 19 : 'networkDestination',
3391 20 : 'dataLayerVirtualLanPriorityCodePoint',
3392 21 : 'networkTypeOfService',
3393 }
3394 for (k, v) in bit_field_map.items():
3395 if wildcards & (1 << k):
3396 l[v] = "*"
3397
3398 #
3399 # --------------------------------------------------------------------------------
3400 # fix_realtime_flows
3401 #
3402 def fix_realtime_flows(self, in_data):
3403 if not in_data:
3404 return
3405 # flatten match data
3406 for l in in_data:
3407 match_dict = l['match']
3408 l.update(match_dict)
3409 del(l['match'])
3410
3411 self.fix_realtime_flow_wildcard_fields(in_data)
3412 return in_data
3413
3414 #
3415 # --------------------------------------------------------------------------------
3416 # add_dpid_to_data
3417 #
3418 @staticmethod
3419 def add_dpid_to_data(data):
3420 if not type(data) is dict: # backward-compatible
3421 return data
3422 formatted_list = []
3423 for (dpid, rows) in data.items():
3424 if rows != None:
3425 for row in rows:
3426 row['switch'] = dpid
3427 formatted_list.append(row)
3428 ## TODO - Alex - handle timeouts + sorting
3429 return formatted_list
3430
3431 #
3432 # --------------------------------------------------------------------------------
3433 # do_show_statistics
3434 #
3435 # LOOK! we could get rid of show statistics at this point as it is all under
3436 # show switch!
3437 #
3438 # it's confusing to remap it over here...
3439 # we could remove show flow-entry realtime too for now
3440 #
3441 def do_show_statistics(self, words):
3442 if len(words) < 1:
3443 return "Syntax: show statistics < flow | port | table | desc | aggregate | " \
3444 "features | trace > < Switch DPID | Switch alias | all >"
3445 stat_type = self.get_statistics_type(words[0])
3446 dpid = None
3447 if len(words) > 1:
3448 words[1] = convert_alias_to_object_key("switch", words[1])
3449 dpid = words[1] # LOOK! Eventually optional so we can show across switches
3450 if not dpid in objects_starting_with("switch", dpid) and dpid != "all":
3451 return self.error_msg("No switch %s found" % dpid)
3452 if stat_type == "flow":
3453 data = self.rest_simple_request_to_dict(
3454 'http://%s/rest/v1/realtimestats/flow/%s/' % (self.controller,dpid))
3455 data = self.add_dpid_to_data(data)
3456 data = self.fix_realtime_flows(data)
3457
3458 #
3459 # it may make sense to move ['details', 'brief'] tp some sort of
3460 # 'show_object_optional_format_keywords' list
3461 field_ordering = "default"
3462 if len(words) == 3:
3463 choices = [x for x in ["details", "brief"] if x.startswith(words[2])]
3464 if len(choices) != 1:
3465 return self.error_msg("switch flow options are either 'details' or 'brief'")
3466 else:
3467 field_ordering = choices[0]
3468 table_data = self.pp.format_table(data, "realtime_flow", field_ordering)
3469 return table_data
3470 elif stat_type == "trace":
3471 return self.do_trace(words)
3472 elif words[0] == "switches":
3473 data = self.rest_simple_request_to_dict(
3474 'http://%s/rest/v1/controller/stats/%s/' % (self.controller, words[0]))
3475 table_data = self.pp.format_table(data, "controller_"+words[0])
3476 return table_data
3477 else:
3478 data = self.rest_simple_request_to_dict(
3479 'http://%s/rest/v1/realtimestats/%s/%s/' % (self.controller, stat_type, dpid))
3480 if stat_type == "features":
3481 for dpid in data:
3482 data[dpid] = data[dpid]["ports"]
3483 data = self.add_dpid_to_data(data)
3484 table_data = self.pp.format_table(data, "realtime_"+stat_type)
3485 return table_data
3486
3487 #
3488 # --------------------------------------------------------------------------------
3489 # do_show_version
3490 #
3491 def do_show_version(self, words):
3492 # LOOK! this should probably come from the controller, but for now
3493 # we assume the CLI/controller are from the same build
3494 version_string = "SDNOS 1.0 - custom version"
3495 try:
3496 fh = open("/opt/sdnplatform/release")
3497 version_string = fh.readline().rstrip()
3498 fh.close()
3499 except:
3500 pass
3501 return version_string
3502
3503 #
3504 # --------------------------------------------------------------------------------
3505 # do_date
3506 #
3507 @staticmethod
3508 def do_date(words):
3509 return time.strftime("%Y-%m-%d %H:%M:%S %Z", time.localtime())
3510
3511 #
3512 # --------------------------------------------------------------------------------
3513 # cp_show_logging
3514 #
3515 def cp_show_logging(self, words, text, completion_char):
3516 if len(words) == 1:
3517 return [x for x in ["sdnplatform", "cassandra", "console-access",
3518 "syslog", "authlog", "orchestrationlog",
3519 "all" ] if x.startswith(text)]
3520 else:
3521 self.print_completion_help("<cr>")
3522
3523 #
3524 # --------------------------------------------------------------------------------
3525 # generate_subpprocess_output
3526 #
3527 def generate_subprocess_output(self, cmd):
3528 process = subprocess.Popen(cmd, shell=True,
3529 stdout=subprocess.PIPE,
3530 stderr=subprocess.STDOUT,
3531 bufsize=1)
3532 while True:
3533 line = process.stdout.readline()
3534 if line != None and line != "":
3535 yield line
3536 else:
3537 break
3538
3539 #
3540 # --------------------------------------------------------------------------------
3541 #
3542 def implement_show_logging(self, words):
3543 log_files = { "sdnplatform" : "cat /opt/sdnplatform/sdnplatform/log/sdnplatform.log",
3544 "cassandra" : "cat /opt/sdnplatform/db/log/system.log",
3545 "console-access" : "cat /opt/sdnplatform/con/log/access.log",
3546 "syslog" : "cat /var/log/syslog",
3547 "authlog" : "cat /var/log/auth.log",
3548 "orchestrationlog" : "cat /tmp/networkservice.log",
3549 "dmesg" : "dmesg",
3550 "all" : "" # special
3551 }
3552 if len(words) < 1:
3553 yield "Syntax: show logging < %s > " % " | ".join(log_files.keys())
3554 return
3555 for (f, cmd) in log_files.items():
3556 if (f.startswith(words[0]) or "all".startswith(words[0])) and f != "all":
3557 yield "*"*80 + "\n"
3558 yield "output of %s for %s\n" % (cmd, f)
3559 yield "*"*80 + "\n"
3560 for item in self.generate_subprocess_output(cmd):
3561 yield item
3562
3563 #
3564 # --------------------------------------------------------------------------------
3565 # do_show_tech_support
3566 #
3567 def do_show_tech_support(self, words):
3568
3569 oldd = self.debug
3570
3571 tech_support_table = []
3572
3573 # Add the user customized command in tech-support-config table
3574 entries = self.get_table_from_store('tech-support-config')
3575 for entry in entries:
3576 # need explicit type casting str() to make command handling work
3577 tech_support_table.append([str(entry['cmd-type']), str(entry['cmd']), None])
3578
3579 # Add the list of command configured for the basic support
3580 for entry in tech_support.get_show_tech_cmd_table():
3581 tech_support_table.append(entry)
3582
3583
3584 for entry in tech_support_table:
3585 cmd_type = entry[0]
3586 cmd = entry[1]
3587 feature = entry[2]
3588
3589 # If feature is non-null, then check feature is enabled
3590 # before executing the command
3591 if feature != None and self.feature_enabled(feature) == False :
3592 continue
3593
3594 if cmd_type == 'cli':
3595 yield "\n\n" + "-"*80 + "\nExecute cli: " + cmd + "\n\n"
3596 try:
3597 for item in self.generate_command_output(self.handle_single_line(cmd)):
3598 yield item
3599 except Exception as e:
3600 yield "Failed to execute %s: %s\n" % (cmd, e)
3601 elif cmd_type == 'shell':
3602 yield "\n\n" + "-"*80 + "\nExecuting os command: " + cmd + "\n\n"
3603 try:
3604 for item in self.generate_subprocess_output(cmd):
3605 yield item
3606 except Exception as e:
3607 yield "Failed to run command %s: %s\n" % (cmd, e)
3608 else:
3609 yield "Unknown command type %s: %s\n" % (cmd_type, cmd)
3610
3611 self.debug = oldd
3612
3613
3614
3615 #
3616 # --------------------------------------------------------------------------------
3617 # ell_ess_minus_r
3618 # Do "ls -R", returing the list of files underneath "prefix".
3619 # The intended external call is
3620 # ell_ess_minus_r(returned_items, "/opt/sdnplatform/...", ""),
3621 # where the path parameter is null to begin the walk.
3622 #
3623 # The returned items won't have the original prefix in the items
3624 #
3625 # XXX: There's no bounding for the recursive descent.
3626 #
3627 def ell_ess_minus_r(self, items, prefix, path):
3628 p = os.path.join(prefix, path)
3629 if os.path.isdir(p):
3630 for entry in os.listdir(p):
3631 new_path = os.path.join(path, entry)
3632 if os.path.isdir(os.path.join(prefix, new_path)):
3633 self.ell_ess_minus_r(items, prefix, new_path)
3634 else:
3635 items.append(os.path.join(path, entry))
3636 return items
3637
3638 #
3639 # --------------------------------------------------------------------------------
3640 # implement_show_config_file
3641 # 'show config file' displays the local files under self.saved_configs_dirname
3642 # currently /opt/sdnplatform/run/saved-configs
3643 #
3644 def implement_show_config_file(self, words):
3645 items = []
3646 self.ell_ess_minus_r(items, self.saved_configs_dirname, "")
3647
3648 if len(words) == 1:
3649 entries = []
3650 for i in items:
3651 cf = os.path.join(self.saved_configs_dirname, i)
3652 t = time.strftime("%Y-%m-%d.%H:%M:%S",
3653 time.localtime(os.path.getmtime(cf)))
3654 entries.append({'name' : i,
3655 'timestamp': t,
3656 'length' : str(os.path.getsize(cf)),
3657 })
3658 return self.pp.format_table(entries,'config')
3659 elif len(words) == 2:
3660 if words[1] in items:
3661 src_file = self.path_concat(self.saved_configs_dirname, words[1])
3662 if src_file == "":
3663 return self.error_msg("file %s could not be "
3664 "interpreted as a valid source" % words[1])
3665 try:
3666 return self.store.get_text_from_url("file://" + src_file)
3667 except Exception, e:
3668 return self.error_msg(words[1] + ":" + str(e))
3669 else:
3670 return self.error_msg("%s file unknown" % words[1])
3671 #
3672 #
3673 # --------------------------------------------------------------------------------
3674 # implement_show_config
3675 #
3676 def implement_show_config(self, words):
3677 name = None
3678 show_version = "latest"
3679 # handle sh config [ <name> [ all | <version #> ] ]
3680
3681 if len(words) == 1:
3682 if words[0] == "all":
3683 show_version = "all"
3684 elif words[0] == "file":
3685 return self.implement_show_config_file(words)
3686 elif words[0].startswith('config://'):
3687 name = words[0][len('config://'):]
3688 else:
3689 name = words[0]
3690 elif len(words) == 2:
3691 if words[0] == 'file':
3692 return self.implement_show_config_file(words)
3693 name = words[0]
3694 show_version = words[1]
3695 elif len(words) == 3 and words[1] == "diff":
3696 return self.diff_two_configs(words[0], words[2])
3697 elif len(words) == 4 and words[1] == "diff":
3698 return self.diff_two_versions(words[0], words[2], words[3])
3699
3700 data = self.store.get_user_data_table(name, show_version)
3701 if data and len(data) > 1 or name == None:
3702 return self.pp.format_table(data,'config')
3703 elif data and len(data) == 1:
3704 return self.store.get_user_data_file(data[0]['full_name'])[:-1]
3705
3706 #
3707 # --------------------------------------------------------------------------------
3708 # diff_two_versions
3709 #
3710 def diff_two_versions(self, name, va, vb):
3711 va_info = self.store.get_user_data_table(name, va)
3712 if len(va_info) == 0:
3713 return self.error_msg("Version %s missing for %s" % (va, name))
3714 va_txt = self.store.get_user_data_file(va_info[0]['full_name'])
3715 vb_info = self.store.get_user_data_table(name, vb)
3716 if len(vb_info) == 0:
3717 return self.error_msg("Version %s missing for %s" % (vb, name))
3718 vb_txt = self.store.get_user_data_file(vb_info[0]['full_name'])
3719 import tempfile
3720 fa = tempfile.NamedTemporaryFile(delete=False)
3721 fa.write(va_txt)
3722 fa.close()
3723 fb = tempfile.NamedTemporaryFile(delete=False)
3724 fb.write(vb_txt)
3725 fb.close()
3726 cmd = 'diff %s %s ' % (fa.name, fb.name)
3727 process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
3728 (output, stderr) = process.communicate()
3729 os.unlink(fa.name)
3730 os.unlink(fb.name)
3731 return output
3732
3733 #
3734 # --------------------------------------------------------------------------------
3735 # diff_two_configs
3736 #
3737 def diff_two_configs(self, config_a, config_b):
3738 items = self.store.get_user_data_table(config_a, "latest")
3739 if len(items) == 0:
3740 if config_a == 'running-config':
3741 txt_a = run_config.implement_show_running_config([]) + "\n"
3742 else:
3743 return self.error_msg("%s config missing" % config_a)
3744 else:
3745 txt_a = self.store.get_user_data_file(items[0]['full_name'])
3746
3747 items = self.store.get_user_data_table(config_b, "latest")
3748 if len(items) == 0:
3749 if config_b == 'running-config':
3750 txt_b = run_config.implement_show_running_config([]) + "\n"
3751 else:
3752 return self.error_msg("%s config missing" % config_b)
3753 else:
3754 txt_b = self.store.get_user_data_file(items[0]['full_name'])
3755
3756 import tempfile
3757 fa = tempfile.NamedTemporaryFile(delete=False)
3758 fa.write(txt_a)
3759 fa.close()
3760 fb = tempfile.NamedTemporaryFile(delete=False)
3761 fb.write(txt_b)
3762 fb.close()
3763 cmd = 'diff %s %s ' % (fa.name, fb.name)
3764 process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
3765 (output, stderr) = process.communicate()
3766 os.unlink(fa.name)
3767 os.unlink(fb.name)
3768 return output
3769
3770 #
3771 # --------------------------------------------------------------------------
3772 #
3773
3774 def get_host_attachment_switch_port (self, query_dict):
3775 """
3776 Get a host's attachment point if it is not already provided.
3777 see if the host has an attachment point, use the switch and port
3778 of the attachment point, if its available
3779 """
3780 host = rest_to_model.get_model_from_url('host', query_dict)
3781 if len(host) == 0:
3782 print self.error_msg('Can\'t identify host details: %s' % query_dict)
3783 return (False,None,None)
3784 attachment_points = host[0]['attachment-points']
3785 if attachment_points == None or len(attachment_points) == 0:
3786 print self.error_msg('Can\'t identify attachment point %s' % query_dict)
3787 return (False,None,None)
3788 if len(attachment_points) == 1:
3789 src_sw = attachment_points[0]['switch']
3790 src_sw_port = attachment_points[0]['ingress-port']
3791 return (True, src_sw, src_sw_port)
3792
3793 interface_dict = create_obj_type_dict('interfaces', 'id')
3794 show_list = []
3795 for ap in attachment_points:
3796 dpid = ap['switch']
3797 ingress_port = ap['ingress-port']
3798
3799 # alias for dpid
3800 switch_name = alias_lookup_with_foreign_key('switch-alias', dpid)
3801 if not switch_name:
3802 switch_name = dpid
3803 # interface name for attachment point
3804 ap_port_id = dpid + '|' + str(ingress_port)
3805 if ap_port_id in interface_dict:
3806 show_list.append("%s/%s" % (switch_name,
3807 interface_port[ap_port_od][0]['portName']))
3808 else:
3809 show_list.append("%s/port %s" % (switch_name, ingress_port))
3810
3811 print self.error_msg('src-host %s: multiple attachment points: %s' %
3812 (src_mac, ', '.join(show_list)))
3813 print self.error_msg('rerun, and identify src-switch and interface')
3814 return (False,None,None)
3815
3816
3817 #
3818 # --------------------------------------------------------------------------
3819 # test packet-in: swap source and destination attributes
3820 #
3821 def test_pkt_in_swap_src_dest_attributes(self, post_data):
3822 # Remove the source switch and port if needed
3823 if ("srcSwitchDpid" in post_data):
3824 post_data.pop("srcSwitchDpid")
3825 if ("srcSwitchInPort" in post_data):
3826 post_data.pop("srcSwitchInPort")
3827
3828 #
3829 # Get destination host's attachment switch/port information
3830 #
3831 (result, src_sw, src_sw_port) = self.get_host_attachment_switch_port(
3832 { 'mac' : post_data["destinationMACAddress"]})
3833 if not result:
3834 return
3835 post_data["srcSwitchDpid"] = src_sw
3836 post_data["srcSwitchInPort"] = src_sw_port
3837
3838 # Swap the macs
3839 temp = post_data["sourceMACAddress"]
3840 post_data["sourceMACAddress"] = post_data["destinationMACAddress"]
3841 post_data["destinationMACAddress"] = temp
3842 # swap the IP addresses if specified
3843 tempSrc = None
3844 tempDst = None
3845 if ("sourceIpAddress" in post_data):
3846 tempSrc = post_data["sourceIpAddress"]
3847 post_data.pop("sourceIpAddress")
3848 if ("destinationIpAddress" in post_data):
3849 tempDst = post_data["destinationIpAddress"]
3850 post_data.pop("destinationIpAddress")
3851 if (tempSrc is not None):
3852 post_data["destinationIpAddress"] = tempSrc
3853 if (tempDst is not None):
3854 post_data["sourceIpAddress"] = tempDst
3855 # swap the IP ports if specified
3856 tempSrc = None
3857 tempDst = None
3858 if ("ipL4SourcePort" in post_data):
3859 tempSrc = post_data["ipL4SourcePort"]
3860 post_data.pop("ipL4SourcePort")
3861 if ("ipL4DestinationPort" in post_data):
3862 tempDst = post_data["ipL4DestinationPort"]
3863 post_data.pop("ipL4DestinationPort")
3864 if (tempSrc is not None):
3865 post_data["ipL4DestinationPort"] = tempSrc
3866 if (tempDst is not None):
3867 post_data["ipL4SourcePort"] = tempDst
3868
3869 #
3870 # --------------------------------------------------------------------------
3871 # test packet-in: display packet-in processing result for test packet-in
3872 #
3873 def test_pkt_in_result_display(self, response):
3874 out = "\nResult of packet-in processing\n"
3875 out += ("------------------------------\n")
3876 src_vns_name = response['srcVNSName']
3877 dst_vns_name = response['destVNSName']
3878
3879 if (response['explanation'] == "Source switch not found"):
3880 out += response['explanation']
3881 return out
3882
3883 out += "\nVirtual Routing Processing iterations\n"
3884 out += ("--------------------------------------\n")
3885 ep_vrouting = response['expPktVRouting']
3886 for ep_vr in ep_vrouting:
3887 srcIface = ep_vr['srcIface']
3888 dstIface = ep_vr['dstIface']
3889 action = ep_vr['action']
3890 out += ("src VNS iface : %s\n" % srcIface)
3891 out += ("dst VNS iface : %s\n" % dstIface)
3892 out += ("action : %s\n" % action)
3893 if action == 'DROP':
3894 dropReason = ep_vr['dropReason']
3895 out += ("drop reason : %s\n" % dropReason)
3896 else:
3897 nextHopIp = ep_vr['nextHopIp']
3898 out += ("next hop IP : %s\n" % nextHopIp)
3899 nextHopGatewayPool = ep_vr['nextHopGatewayPool']
3900 if (nextHopGatewayPool != None):
3901 out += ("next hop Gateway Pool : %s\n" % nextHopGatewayPool)
3902 out += "\n"
3903
3904 if (src_vns_name == None) and (dst_vns_name == None):
3905 out += "Source and destination hosts are not in the same vns"
3906 return out
3907
3908 out += ("Status : %s\n" % response['status'])
3909 out += ("Explanation : %s\n" % response['explanation'])
3910
3911 out += "\nResult of Virtual Routing\n"
3912 out += ("--------------------------\n")
3913 out += ("Source VNS : %s\n" % src_vns_name)
3914 out += ("Dest. VNS : %s\n" % dst_vns_name)
3915 out += ("Input ACL:\n")
3916 out += (" Input ACL Name : %s\n" % response['inputAcl']['aclName'])
3917 out += (" Input ACL Entry : %s\n" % response['inputAcl']['aclEntry'])
3918 out += (" Input ACL Action : %s\n" % response['inputAcl']['aclResult'])
3919 out += ("Output ACL:\n")
3920 out += (" Output ACL Name : %s\n" % response['outputAcl']['aclName'])
3921 out += (" Output ACL Entry : %s\n" % response['outputAcl']['aclEntry'])
3922 out += (" Output ACL Action : %s\n" % response['outputAcl']['aclResult'])
3923
3924 if response['expPktRoute'] == None:
3925 out += "\n"
3926 return out
3927
3928 out += ("Routing Action : %s\n" % response['routingAction'])
3929
3930 obj_type_show_alias_update('test-pktin-route')
3931 out += ('\nFlow Path:\n')
3932 ep_route = response['expPktRoute']
3933 for ep_cluster in ep_route:
3934 ep_cluster_path = ep_cluster['path']
3935 hop = 1
3936 route_data = []
3937 for ep_path in ep_cluster_path:
3938 oneHop = {}
3939 oneHop['cluster'] = ep_cluster['clusterNum']
3940 oneHop['hop'] = hop
3941 hop += 1
3942 oneHop.update(ep_path)
3943 route_data.append(oneHop)
3944 route_data = self.pp.format_table(route_data, 'test-pktin-route')
3945 out += route_data
3946 out += "\n"
3947 return out
3948
3949 #
3950 # --------------------------------------------------------------------------------
3951 # implement_test_packet_in
3952 #
3953 def implement_test_packet_in(self, data):
3954 # Currently only supports <test packet-in> for "explain packet" feature
3955 self.debug_msg("In do_test(): data=%s" % (data))
3956
3957 # REST API expects MAC not host aliases
3958 if not 'src-host' in data:
3959 print self.error_msg("Missing src-host")
3960 return
3961 src_mac = data['src-host']
3962
3963 if not 'dst-host' in data:
3964 print self.error_msg("Missing dst-host")
3965 return
3966 dst_mac = data['dst-host']
3967
3968 # Check that src and dest hosts are not the same
3969 if src_mac == dst_mac:
3970 print self.error_msg("source and destination hosts can not be same")
3971 return
3972
3973 #
3974 if 'src-switch' in data:
3975 src_sw = data['src-switch']
3976 src_sw_port = data['src-switch-port']
3977 else:
3978
3979 #
3980 # Get host's attachment switch/port information
3981 #
3982 (result, src_sw, src_sw_port) = \
3983 self.get_host_attachment_switch_port({'mac' : src_mac})
3984 if not result:
3985 return
3986
3987 post_data = {
3988 "sourceMACAddress": src_mac, "destinationMACAddress": dst_mac,
3989 "srcSwitchDpid" : src_sw, "srcSwitchInPort" : src_sw_port
3990 }
3991
3992 if 'vlan' in data:
3993 post_data["vlanID"] = data['vlan']
3994 if 'ether-type' in data:
3995 post_data["etherType"] = data['ether-type']
3996 if (post_data['etherType'] != "2048"):
3997 yield(self.error_msg("Supported ethertypes: 2048 (ip)"))
3998 return
3999 if 'priority' in data:
4000 post_data["priorityCode"] = data['priority']
4001 if 'src-ip-address' in data:
4002 post_data["sourceIpAddress"] = data['src-ip-address']
4003 if 'dst-ip-address' in data:
4004 post_data["destinationIpAddress"] = data['dst-ip-address']
4005 if 'protocol' in data:
4006 post_data["ipv4Protocol"] = data['protocol']
4007 if 'tos' in data:
4008 #Uncomment the line below once we support tos in the REST API
4009 #post_data["tos"] = data['tos']
4010 pass
4011 if 'src-port' in data:
4012 post_data["ipL4SourcePort"] = data['src-port']
4013 if 'dst-port' in data:
4014 post_data["ipL4DestinationPort"] = data['dst-port']
4015
4016 self.debug_msg("Post Data = %s" % post_data)
4017
4018 url = "http://%s/rest/v1/realtimetest/network/explain-packet" % self.controller
4019
4020 try:
4021 jresponse = self.store.rest_post_request(url, post_data)
4022 except Exception, e:
4023 errors = self.rest_error_to_dict(e, url)
4024 yield(self.error_msg(self.rest_error_dict_to_message(errors)))
4025 return
4026
4027 response = json.loads(jresponse)
4028 self.debug_msg("Response = %s" % response)
4029
4030
4031 yield ("Input packet\n")
4032 yield ("------------\n")
4033 explPkt = response['explainPktParams']
4034 switch_alias = alias_lookup_with_foreign_key("switch-alias", explPkt['srcSwitchDpid'])
4035 src_host_alias = alias_lookup_with_foreign_key("host-alias", explPkt['sourceMACAddress'])
4036 dst_host_alias = alias_lookup_with_foreign_key("host-alias", explPkt['destinationMACAddress'])
4037
4038 if (src_host_alias):
4039 yield ("Source host : %s (%s), ip=%s\n" %
4040 (explPkt['sourceMACAddress'], src_host_alias, explPkt['sourceIpAddress']))
4041 else:
4042 yield ("Source host : %s, ip=%s\n" % (explPkt['sourceMACAddress'], explPkt['sourceIpAddress']))
4043
4044 if (dst_host_alias):
4045 yield ("Destination host : %s (%s), ip=%s\n" %
4046 (explPkt['destinationMACAddress'], dst_host_alias, explPkt['destinationIpAddress']))
4047 else:
4048 yield "Destination host : %s, ip=%s\n" % (explPkt['destinationMACAddress'], explPkt['destinationIpAddress'])
4049 if (explPkt['vlanID'] == '-1'):
4050 yield "VLAN : %s (Untagged)\n" % explPkt['vlanID']
4051 else:
4052 yield "VLAN : %s\n" % explPkt['vlanID']
4053 yield "802.1Q priority : %s\n" % explPkt['priorityCode']
4054 yield "Ether type : %s\n" % explPkt['etherType']
4055 if (switch_alias):
4056 yield "Source switch/port : %s (%s)/%s\n" % (explPkt['srcSwitchDpid'], switch_alias, explPkt['srcSwitchInPort'])
4057 else:
4058 yield "Source switch/port : %s/%s\n" % (explPkt['srcSwitchDpid'], explPkt['srcSwitchInPort'])
4059 yield "Protocol : %s\n" % explPkt['ipv4Protocol']
4060 yield "L4 source port : %s\n" % explPkt['ipL4SourcePort']
4061 yield "L4 destination port: %s\n" % explPkt['ipL4DestinationPort']
4062
4063 yield("\nForward path:")
4064 yield("=============\n")
4065 yield(self.test_pkt_in_result_display(response))
4066
4067 # Now send the reverse test packet
4068 self.test_pkt_in_swap_src_dest_attributes(post_data)
4069 try:
4070 jresponse = self.store.rest_post_request(url, post_data)
4071 except Exception, e:
4072 errors = self.rest_error_to_dict(e, url)
4073 yield(self.error_msg(self.rest_error_dict_to_message(errors)))
4074 return
4075
4076 response = json.loads(jresponse)
4077 self.debug_msg("Response = %s" % response)
4078 yield("\nReverse path:")
4079 yield("=============\n")
4080 yield(self.test_pkt_in_result_display(response))
4081
4082 #
4083 # --------------------------------------------------------------------------------
4084 #
4085 def implement_test_path(self, data):
4086
4087 # compute source
4088 #
4089 if 'src-switch' in data:
4090 src_sw = data['src-switch']
4091 src_sw_port = data['src-switch-port']
4092 elif 'src-host' in data:
4093 (result, src_sw, src_sw_port) = \
4094 self.get_host_attachment_switch_port({ 'mac' : data['src-host']})
4095 if not result:
4096 return
4097 elif 'src-ip' in data:
4098 # look up the ip in the device api
4099 (result, src_sw, src_sw_port) = \
4100 self.get_host_attachment_switch_port({ 'ipv4' : data['src-ip']})
4101 if not result:
4102 return
4103 else:
4104 return self.error_msg('No source attachment point in request')
4105
4106 # compute dest
4107 if 'dst-switch' in data:
4108 dst_sw = data['dst-switch']
4109 dst_sw_port = data['dst-switch-port']
4110 elif 'dst-host' in data:
4111 (result, dst_sw, dst_sw_port) = \
4112 self.get_host_attachment_switch_port({ 'mac' : data['dst-host']})
4113 if not result:
4114 return
4115 elif 'dst-ip' in data:
4116 # look up the ip in the device api
4117 (result, dst_sw, dst_sw_port) = \
4118 self.get_host_attachment_switch_port({ 'ipv4' : data['dst-ip']})
4119 if not result:
4120 return
4121 else:
4122 return self.error_msg('No dest attachment point in request')
4123
4124 url = "http://%s/rest/v1/realtimetest/network/path" % self.controller
4125 request = {
4126 'src-switch' : src_sw,
4127 'src-switch-port' : src_sw_port,
4128 'dst-switch' : dst_sw,
4129 'dst-switch-port' : dst_sw_port,
4130 }
4131
4132 try:
4133 response = self.store.rest_post_request(url, request)
4134 self.debug_msg("Response = %s" % response)
4135 if response != '':
4136 command.query_result = json.loads(response)
4137 else:
4138 command.query_result = None
4139
4140 except Exception, e:
4141 errors = self.rest_error_to_dict(e, url)
4142 return self.error_msg(self.rest_error_dict_to_message(errors))
4143
4144
4145 #
4146 # --------------------------------------------------------------------------------
4147 #
4148 def cp_help(self, words, text, completion_char):
4149 """
4150 Completion for the help command must be done using the collection
4151 of command descriptions; ie: help uses the command descriptions
4152 to complete the help commands.
4153 """
4154 if completion_char == ord('?'):
4155 if len(words) > 1:
4156 command.do_command_completion_help(words[1:], text)
4157 else:
4158 print self.help_splash([], text)
4159 return
4160 if len(words) == 1:
4161 items = self.commands_for_current_mode_starting_with(text)
4162 return utif.add_delim(items, ' ')
4163 else:
4164 return command.do_command_completion(words[1:], text)
4165
4166
4167 #
4168 # --------------------------------------------------------------------------------
4169 # command_short_help
4170 # Associate short help strings with known commands
4171 # These ought to be associated with the COMMAND_DESCRIPTIONs,
4172 # but since not all command use that mechanism yet, provide
4173 # these short descriptions here.
4174 #
4175 command_short_help = {
4176 'access-list' : 'Define access-list',
4177 'access-group' : 'Configure access-list to interface association',
4178 'boot' : "Configure boot configuration",
4179 'clock' : 'Configure timezone or set clock',
4180 'clear' : 'Reset counters',
4181 'connect' : "Connect to a controller's REST API via ip[:port]",
4182 'date' : 'Display current date and time',
4183 'debug' : 'Enter various debug modes',
4184 'echo' : 'Echo remaining arguments',
4185 'enable' : 'Move to enable mode',
4186 'exit' : 'Exit current mode',
4187 'feature' : 'Enable or disable features',
4188 'firewall' : 'Enable controller interfaces acls',
4189 'help' : 'Help on commands or topics',
4190 'history' : 'Display history of commands',
4191 'ip' : 'Configure various controller node ip values',
4192 'logout' : 'Exit from cli',
4193 'logging' : 'Configure logging/syslog',
4194 'ntp' : 'Configure ntp',
4195 'ping' : 'Ping request from controller to switch or ip address',
4196 'show' : 'Display configuration or settings',
4197 'test' : 'Test various behaviors',
4198 'trace' : 'Trace various streams',
4199 'traceroute' : 'Traceroute from controller to switch or ip address',
4200 'configure' : 'Enter configuration mode',
4201 'reload' : 'Reboot controller, reload configuration',
4202 'write' : 'Write configuraion',
4203 'no' : 'Delete or disable configuration parameters',
4204 'vns-definition' : 'Enter vns-definiton submode, describe membership',
4205 'vns' : 'Enter vns submode, manage access lists',
4206 'copy' : 'Copy configurations',
4207 'switchto' : 'Switch to another vns definition',
4208 'interface-rule' : 'VNS Membership rule',
4209 'end' : 'End all nested configuration modes',
4210 'interface' : 'Enter interface submode',
4211 'watch' : 'Iterate indicated command displaying results',
4212 }
4213
4214 #
4215 # --------------------------------------------------------------------------------
4216 # obj_type_short_help
4217 #
4218 obj_type_short_help = {
4219 'controller-node' : 'Configure specific controller nodes',
4220 'host' : 'Configure host details',
4221 'statd-config' : 'Statd Configuration',
4222 'statdropd-config' : 'Stat dropd configuration',
4223 'statdropd-progress-info' : 'Stat dropd progress configuration',
4224 'switch' : 'Switch Configuration',
4225
4226 # -- debug only tables
4227 'vns-access-list' : 'VNS Access List object',
4228 'vns-access-list-entry' : 'VNS Access List Entry object',
4229 'vns-interface' : 'VNS Interface object',
4230 'vns-interface-access-list' : 'VNS Interface Access List object',
4231 'vns-interface-rule' : 'VNS Interface Rule object',
4232 'host-alias' : 'Host Alias object',
4233 'port-alias' : 'Port Alias object',
4234 'switch-alias' : 'Switch Alias object',
4235 }
4236
4237 #
4238 # --------------------------------------------------------------------------------
4239 # help_splash
4240 #
4241 def help_splash(self, words, text):
4242 ret = ""
4243 if not words:
4244 if text == "":
4245 ret += "For help on specific commands type help <topic>\n"
4246
4247 count = 0
4248 longest_command = 0
4249 # this submode commands
4250 s_ret = ""
4251 mode = self.current_mode()
4252 nested_mode_commands = [self.title_of(x) for x in
4253 self.command_nested_dict.get(mode, [])]
4254 possible_commands = [self.title_of(x) for x in
4255 self.command_dict.get(mode, []) ] + \
4256 nested_mode_commands
4257 available_commands = self.commands_feature_enabled(possible_commands)
4258 submode_commands = sorted(utif.unique_list_from_list(available_commands))
4259 if len(submode_commands):
4260 longest_command = len(max(submode_commands, key=len))
4261 for i in submode_commands:
4262 if not i.startswith(text):
4263 continue
4264 count += 1
4265 short_help = command.get_command_short_help(i)
4266 if not short_help:
4267 short_help = self.command_short_help.get(i, None)
4268 if short_help:
4269 s_ret += " %s%s%s\n" % (i,
4270 ' ' * (longest_command - len(i) + 1),
4271 short_help)
4272 else:
4273 s_ret += " %s\n" % i
4274
4275 # commands
4276 c_ret = ""
4277 upper_commands = [x for x in self.commands_for_current_mode_starting_with()
4278 if not x in submode_commands]
4279 commands = sorted(upper_commands)
4280 if len(commands):
4281 longest_command = max([len(x) for x in commands] + [longest_command])
4282 for i in commands:
4283 if not i.startswith(text):
4284 continue
4285 count += 1
4286 short_help = command.get_command_short_help(i)
4287 if not short_help:
4288 short_help = self.command_short_help.get(i, None)
4289 if short_help:
4290 c_ret += " %s%s%s\n" % (i,
4291 ' ' * (longest_command - len(i) + 1),
4292 short_help)
4293 else:
4294 c_ret += " %s\n" % i
4295
4296 # objects
4297 o_ret = ""
4298 if self.in_config_mode():
4299 obj_types = sorted(self.obj_types_for_config_mode_starting_with())
4300 if len(obj_types) > 0:
4301 for i in obj_types:
4302 longest_command = max([len(x) for x in commands] +
4303 [longest_command])
4304 for i in obj_types:
4305 if i in commands:
4306 continue
4307 if i.startswith(text):
4308 count += 1
4309 short_help = self.obj_type_short_help.get(i, None)
4310 if short_help:
4311 o_ret += " %s%s%s\n" % (i,
4312 ' ' * (longest_command - len(i) + 1),
4313 short_help)
4314 else:
4315 o_ret += " %s\n" % i
4316
4317 # fields
4318 f_ret = ""
4319 if self.in_config_submode():
4320 # try to get both the fields and the commands to line up
4321 longest_field = longest_command
4322 for i in self.fields_for_current_submode_starting_with():
4323 if i.startswith(text):
4324 longest_field = max(longest_field, len(i))
4325
4326 f_count = 0
4327 # LOOK! could be getting the help text for each of these...
4328 for i in sorted(self.fields_for_current_submode_starting_with()):
4329 if not i.startswith(text):
4330 continue
4331 count += 1
4332 field_info = mi.obj_type_info_dict[
4333 self.get_current_mode_obj_type()]['fields'].\
4334 get(i, None)
4335 if not field_info and self.debug:
4336 print 'no field for %s:%s' % (self.get_current_mode_obj_type(), i)
4337 if field_info and field_info.get('help_text', None):
4338 f_ret += " %s%s%s\n" % (i,
4339 ' ' * (longest_field - len(i) + 1),
4340 field_info.get('help_text'))
4341 else:
4342 f_ret += " %s\n"% i
4343
4344 if (text == "" or count > 1) and s_ret != "":
4345 ret += "Commands:\n"
4346 ret += s_ret
4347
4348 if (text == "" or count > 1) and c_ret != "":
4349 ret += "All Available commands:\n"
4350 ret += c_ret
4351
4352 if (text == "" or count > 1) and o_ret != "":
4353 ret += "\nAvailable config submodes:\n"
4354 ret += o_ret
4355
4356 if (text == "" or count > 1) and f_ret != "":
4357 ret += "\nAvailable fields for %s:\n" % self.get_current_mode_obj_type()
4358 ret += f_ret
4359 elif words[0] in ["show", "help", "copy", "watch" ]:
4360 method = self.command_method_from_name(words[0])
4361 if method:
4362 ret = method(None)
4363 else:
4364 #try:
4365 #ret = command.get_command_syntax_help(words, 'Command syntax:')
4366 #except:
4367 #if self.debug or self.debug_backtrace:
4368 #traceback.print_exc()
4369 #ret = "No help available for command %s" % words[0]
4370
4371 try:
4372 ret = command.get_command_documentation(words)
4373 except:
4374 if self.debug or self.debug_backtrace:
4375 traceback.print_exc()
4376 ret = "No help available for command %s" % words[0]
4377 else:
4378 #try:
4379 #ret = command.get_command_syntax_help(words, 'Command syntax:')
4380 #except:
4381 #if self.debug or self.debug_backtrace:
4382 #traceback.print_exc()
4383 #ret = "No help available for command %s" % words[0]
4384 try:
4385 ret = command.get_command_documentation(words)
4386 except:
4387 if self.debug or self.debug_backtrace:
4388 traceback.print_exc()
4389 ret = "No help available for command %s" % words[0]
4390 return ret
4391
4392 #
4393 # --------------------------------------------------------------------------------
4394 # do_help
4395 #
4396 def do_help(self, words):
4397 return self.help_splash(words, "")
4398
4399 #
4400 # --------------------------------------------------------------------------------
4401 # do_lint
4402 #
4403 def do_lint(self, words):
4404 return command.lint_command(words)
4405
4406
4407 #
4408 # --------------------------------------------------------------------------------
4409 # cp_history
4410 #
4411 def cp_history(self, words, text, completion_char):
4412 if len(words) == 1:
4413 ret_val = " <num> - to display a specific number of commands (default:all)\n"
4414 ret_val += " <cr>\n"
4415 self.print_completion_help(ret_val)
4416 else:
4417 self.print_completion_help("<cr>")
4418
4419 #
4420 # --------------------------------------------------------------------------------
4421 # do_history
4422 #
4423 def do_history(self, words = None):
4424 ret_val = ""
4425 how_many = num_commands = readline.get_current_history_length()
4426 if words:
4427 how_many = words[0]
4428 for i in range(num_commands-int(how_many) + 1, num_commands):
4429 yield "%s: %s\n" % (i, readline.get_history_item(i))
4430 return
4431
4432
4433 debug_cmd_options = ["python", "bash", "cassandra-cli", "netconfig", "tcpdump",
4434 "cli", "cli-backtrace", "cli-batch", "cli-interactive"]
4435
4436 #
4437 # --------------------------------------------------------------------------------
4438 # do_debug
4439 #
4440 def implement_debug(self, words):
4441 if len(words) < 1 or len([x for x in self.debug_cmd_options if x.startswith(words[0])]) < 1:
4442 return "Syntax: debug < %s >" % " | ".join(self.debug_cmd_options)
4443 def shell(args):
4444 subprocess.call(["env", "SHELL=/bin/bash", "/bin/bash"] + list(args), cwd=os.environ.get("HOME"))
4445 print
4446 print "\n***** Warning: this is a debug command - use caution! *****"
4447 if "python".startswith(words[0]):
4448 print '***** Type "exit()" or Ctrl-D to return to the SDNOS CLI *****\n'
4449 shell(["-l", "-c", "python"])
4450 elif "bash".startswith(words[0]):
4451 print '***** Type "exit" or Ctrl-D to return to the SDNOS CLI *****\n'
4452 shell(["-l", "-i"])
4453 elif "cassandra-cli".startswith(words[0]):
4454 print '***** Type "exit" or Ctrl-D to return to the SDNOS CLI *****\n'
4455 shell(["-l", "-c", "/opt/sdnplatform/db/bin/cassandra-cli --host localhost"])
4456 elif "netconfig".startswith(words[0]):
4457 if not re.match("/dev/ttyS?[\d]+$", os.ttyname(0)):
4458 print '***** You seem to be connected via SSH or another remote protocol;'
4459 print '***** reconfiguring the network interface may disrupt the connection!'
4460 print '\n(Press Control-C now to leave the network configuration unchanged)\n'
4461 subprocess.call(["sudo", "env", "SHELL=/bin/bash", "/opt/sdnplatform/sys/bin/bscnetconfig", "eth0"], cwd=os.environ.get("HOME"))
4462 elif "tcpdump".startswith(words[0]):
4463 print '***** Type Ctrl-C to return to the SDNOS CLI *****\n'
4464 try:
4465 shell(["-l", "-c", "sudo /opt/openflow/sbin/tcpdump " + " ".join(words[1:])])
4466 except:
4467 pass # really, we need to ignore this unconditionally!
4468 time.sleep(0.2)
4469 elif "cli".startswith(words[0]):
4470 if self.debug:
4471 self.debug = False
4472 print "debug disabled"
4473 else:
4474 self.debug = True
4475 print "debug enabled"
4476 elif "cli-backtrace".startswith(words[0]):
4477 # This feature is separated from the cli debug mode so that backtraces
4478 # can be collected during cli qa
4479 self.debug_backtrace = True
4480 print '***** Enabled cli debug backtrace *****'
4481 elif "cli-batch".startswith(words[0]):
4482 self.batch = True
4483 print '***** Enabled cli batch mode *****'
4484 elif "cli-interactive".startswith(words[0]):
4485 self.batch = False
4486 print '***** Disabled cli batch mode *****'
4487 else:
4488 return self.error_msg("invoking debug")
4489 #
4490 # --------------------------------------------------------------------------------
4491 # do_echo
4492 #
4493 def do_echo(self, words):
4494 print " ".join(words)
4495
4496 #
4497 # --------------------------------------------------------------------------------
4498 # cp_trace
4499 #
4500 trace_optional_params = {
4501 'screen': {'type': 'flag'},
4502# 'port': {'type': 'string',
4503# 'syntax_help': "Enter <switch dpid> <physical port#>, where the packets traces are spanned."},
4504 'file': {'type': 'string',
4505 'syntax_help': "Enter <filename> where the packets traces are captured."},
4506 }
4507
4508 trace_cmds = ["detail", "oneline", "vns", "in", "out", "both",
4509 "echo_reply", "echo_request", "features_rep", "flow_mod", "flow_removed", "get_config_rep", "hello",
4510 "packet_in", "packet_out", "port_status", "set_config", "stats_reply", "stats_reques"]
4511
4512 def cp_trace(self, words, text, completion_char):
4513 vns_cache = objects_starting_with('vns-definition')
4514 if len(words) == 1:
4515 return [x for x in self.trace_cmds if x.startswith(text)]
4516 elif len(words) == 2 and words[1].lower().startswith("vns"):
4517 return vns_cache
4518 elif len(words) == 2 and words[1].lower().startswith("output"):
4519 return self.complete_optional_parameters(self.trace_optional_params,
4520 words[len(words):], text)
4521 elif (len(words) == 3 and
4522 words[1].lower().startswith("vns") and
4523 words[2].lower() in vns_cache):
4524 return [x for x in ["in", "out", "both", "output"] if x.startswith(text)]
4525 elif (len(words) == 4 and
4526 words[1].lower().startswith("vns") and
4527 words[2].lower() in objects_starting_with('vns-definition') and
4528 words[3].lower() in ["in", "out", "both"]):
4529 return [x for x in ["output"] if x.startswith(text)]
4530 elif len(words) == 2 and words[1].lower()in ["in", "out", "both"]:
4531 return [x for x in ["output"] if x.startswith(text)]
4532 elif (len(words) > 2 and
4533 (words[1].lower().startswith("vns") or
4534 words[1].lower().startswith("output") or
4535 words[2].lower().startswith("output"))) :
4536 offset = len(words)
4537 if words[len(words)-1].lower() == 'screen':
4538 self.print_completion_help("<cr>")
4539 return []
4540 elif words[len(words)-1].lower() in ['file']:
4541 offset = len(words) - 1
4542 return self.complete_optional_parameters(self.trace_optional_params, words[offset:], text)
4543
4544 self.print_completion_help("<cr>")
4545
4546 #
4547 # --------------------------------------------------------------------------------
4548 # do_trace
4549 #
4550 def do_trace(self, *args):
4551 dpid = None
4552 if len(args) > 0:
4553 args = args[0] # first element is the array of 'real' args
4554 if len(args) > 1 and args[0].lower() in ['vns', 'in', 'out', 'both', 'output']:
4555 try :
4556 return self.do_sdnplatform_trace(args)
4557 except KeyboardInterrupt, e:
4558 return
4559
4560 if len(args) > 1 and args[0].lower() == 'trace':
4561 dpid = args[1]
4562 args = args[2:] # And in this case, args are after the first 2 parameters
4563 return self.do_tcpdump_trace(dpid, args)
4564
4565 def do_sdnplatform_trace(self, args):
4566 vns = None
4567 direction = 'both'
4568 output = 'screen'
4569 dpid = None
4570 port = None
4571 file_name = None
4572 sessionId = None
4573 error = 'Error: Invalid command syntax.\n' + \
4574 'trace [vns <vnsId>] [in|out|both] output [screen]'
4575# 'trace [vns <vnsId>] [in|out|both] output [screen|port <dpid> <physical port>|file <filename>]'
4576
4577 if args[0].lower() == 'vns':
4578 if len(args) > 1:
4579 vns = args[1]
4580 args = args[2:]
4581 else:
4582 return error
4583
4584 while args:
4585 if args[0].lower() == 'output':
4586 args = args[1:]
4587 continue
4588 if args[0].lower() in ['in', 'out', 'both']:
4589 direction = args[0].lower()
4590 args = args[1:]
4591 continue
4592 if args[0].lower() == 'screen':
4593 output = 'screen'
4594 break
4595 if args[0].lower() == 'port':
4596 output = 'port'
4597 if len(args) > 2:
4598 dpid = args[1]
4599 port = args[2:]
4600 break
4601 else:
4602 return error
4603 if args[0].lower() == 'file':
4604 output = 'file'
4605 if len(args) > 1:
4606 file_name = args[1]
4607 break
4608 else:
4609 return error
4610
4611 filter_request = {FT_DIRECTION:direction, FT_OUTPUT:output, FT_PERIOD:FT_PERIOD_DEFAULT}
4612 if vns :
4613 filter_request[FT_VNS] = vns
4614 if dpid and port :
4615 filter_request[FT_PORT] = {FT_PORT_DPID:dpid, FT_PORT_PORT:port}
4616
4617
4618 post_data = json.dumps(filter_request)
4619
4620 while 1:
4621 response_text = None
4622 sessionId = None
4623 url = 'http://%s/rest/v1/packettrace/' % cli.controller
4624 request = urllib2.Request(url, post_data, {'Content-Type':'application/json'})
4625 try:
4626 response = urllib2.urlopen(request)
4627 response_text = response.read()
4628 response_text = json.loads(response_text)
4629 except Exception, e:
4630 return "Error: failed to get a response for a trace request. %s" % e
4631
4632 if response_text and SESSIONID in response_text:
4633 sessionId = response_text[SESSIONID]
4634 else :
4635 return "Error: failed to get a trace session. %s" % response_text
4636
4637 # Span to a physical port
4638 # No data is shown on cli. ^C to stop the trace
4639 if output.lower() == 'port':
4640 while 1:
4641 try :
4642 time.sleep(1000)
4643 except KeyboardInterrupt, e:
4644 self.terminateTrace(sessionId)
4645 return
4646
4647 lpurl = None
4648
4649 if sessionId:
4650 lpurl = 'http://%s/poll/packets/%s' % (cli.controller, sessionId)
4651 else:
4652 return "Error: failed to start a trace session. %s" % response_text
4653
4654 FILE = None
4655 if file_name:
4656 FILE = open(file_name, "w")
4657
4658 flagTimeout = False
4659 while 1:
4660 try:
4661 response_text = urllib2.urlopen(lpurl).read()
4662 data = json.loads(response_text)
4663 if output.lower() == "screen":
4664 flagTimeout = self.dumpPktToScreen(data)
4665 elif output.lower() == 'file' and FILE != None:
4666 self.dumpPktToFile(FILE, data)
4667 except KeyboardInterrupt, e:
4668 self. terminateTrace(sessionId)
4669 return
4670 except Exception, e:
4671 return "Error: failed to start a trace session. %s" % e
4672
4673 if flagTimeout:
4674 continueString = raw_input("Do you want to continue tracing? [y/n]: ")
4675 if 'y' in continueString.lower():
4676 break
4677 else:
4678 return
4679
4680
4681 @staticmethod
4682 def dumpPktToScreen(data):
4683 for entry in data:
4684 if FT_TIMEOUT in entry:
4685 return True
4686 else:
4687 print entry
4688 return False
4689
4690 @staticmethod
4691 def dumpPktToFile(FILE, data):
4692 FILE.writelines(data)
4693
4694 @staticmethod
4695 def terminateTrace(sid):
4696 post_data = json.dumps({SESSIONID:sid, FT_PERIOD:-1})
4697 url = 'http://%s/rest/v1/packettrace/' % cli.controller
4698 request = urllib2.Request(url, post_data, {'Content-Type':'application/json'})
4699 try:
4700 response = urllib2.urlopen(request)
4701 response_text = response.read()
4702 except Exception, e:
4703 # Sdnplatform may not be running, but we don't want that to be a fatal
4704 # error, so we just ignore the exception in that case.
4705 pass
4706
4707 def do_tcpdump_trace(self, dpid, args):
4708 ret = ''
4709 bsc_port = '6633' # LOOK: listen addr/port are wrong in the controller-node, so hard code 6633 for now
4710 trace_cmd = 'sudo /opt/sdnplatform/cli/bin/trace --filter \'%s\' %s'
4711 trace_rule = '((tcp) and (port %s))' % (bsc_port,)
4712 single_session = False
4713
4714 trace_args = '--alias'
4715 if 'detail' in args:
4716 trace_args += ' --detail'
4717 args.remove('detail')
4718 if 'oneline' in args:
4719 trace_args += ' --oneline'
4720 args.remove('oneline')
4721 if 'single_session' in args:
4722 single_session = True
4723 args.remove('single_session')
4724
4725 if dpid:
4726 query_dict = { 'dpid' : dpid }
4727 row = rest_to_model.get_model_from_url('switches', query_dict)
4728 if len(row) >= 1:
4729 if not 'ip-address' in row[0] or row[0].get('ip-address') == '':
4730 return self.error_msg("switch %s currently not connected " % dpid)
4731 addr = row[0]['ip-address']
4732 port = row[0]['tcp-port']
4733 if single_session:
4734 trace_rule = '((tcp) and (port %s) and (host %s) and (port %s))' % (bsc_port, addr, port)
4735 else:
4736 trace_rule = '((tcp) and (port %s) and (host %s))' % (bsc_port, addr)
4737 if len(args) > 0:
4738 trace_args += (' ' + ' '.join(args))
4739 try:
4740 # print args, dpid, trace_cmd % (trace_rule, trace_args)
4741 process = subprocess.Popen(trace_cmd % (trace_rule, trace_args), shell=True)
4742 status = os.waitpid(process.pid, 0)[1]
4743 if status != 0:
4744 ret = 'Errno: %d' % (status, )
4745 except:
4746 pass # really, we need to ignore this unconditionally!
4747
4748 time.sleep(0.2)
4749 return ret
4750
4751
4752 #
4753 # --------------------------------------------------------------------------------
4754 # get_auth_token_file_path
4755 #
4756 @staticmethod
4757 def get_auth_token_file_path():
4758 auth_token_path = os.environ.get('BSC_AUTH_TOKEN_PATH')
4759 if auth_token_path is None:
4760 auth_token_path = '/opt/sdnplatform'
4761 auth_token_path += '/auth_token'
4762 return auth_token_path
4763
4764 #
4765 # --------------------------------------------------------------------------------
4766 # play_config_lines
4767 #
4768 def play_config_lines(self, src_text):
4769 """
4770 The complete collection of lines to replay are in src_text.
4771 In the future, this would provide for a way to first identify
4772 what needs to be removed, then apply what needs to be added.
4773
4774 Some smart diffing from the exiting running-config would
4775 be needed to support that. The stanza groups would need to
4776 be collected, the diff'd
4777 """
4778 print "Updating running-config ..."
4779
4780 num_lines_played = 0
4781 # scan the lines looking for the version number.
4782 #
4783 version = None
4784 for line in src_text.splitlines():
4785 m = re.match("(\s*)(\w+)\s*", line) # only match non-blank lines
4786 if m:
4787 if m.group(0).startswith('version '):
4788 line_parts = line.split()
4789 if line_parts > 1:
4790 version = line_parts[1]
4791 break
4792
4793 # construct the command to run.
4794 command = ['env']
4795 if version:
4796 command.append('CLI_COMMAND_VERSION=%s' % version)
4797 command.append('CLI_STARTING_MODE=config')
4798 command.append('CLI_SUPPRESS_WARNING=True')
4799
4800 if os.path.exists('/opt/sdnplatform/cli/bin/cli'):
4801 # controller VM
4802 command.append('/opt/sdnplatform/cli/bin/cli')
4803 command.append('--init')
4804 else:
4805 # developer setup
4806 base = os.path.dirname(__file__)
4807 command.append(os.path.join(base, 'cli.py'))
4808 command.append('--init')
4809
4810 # create a subprocess for the configuration, then push
4811 # each of the lines into that subprocess.
4812 p = subprocess.Popen(command,
4813 stdin=subprocess.PIPE,
4814 stdout=subprocess.PIPE,
4815 stderr=subprocess.STDOUT,
4816 bufsize=1)
4817 postprocesslines=[]
4818 for line in src_text.splitlines(1):
4819 m = re.match("(\s*)(\w+)\s*", line) # only match non-blank lines
4820 if m:
4821 if 'tenant' or 'router' in line: #special handling for routing rule commands VNS-226
4822 postprocesslines.append(line)
4823 p.stdin.write(line)
4824 num_lines_played += 1
4825 for line in postprocesslines:
4826 p.stdin.write(line)
4827 num_lines_played += 1
4828 p.stdin.close()
4829 output = p.stdout.read()
4830 if output and (self.debug or self.debug_backtrace or self.description):
4831 print output
4832 p.stdout.close()
4833 return "Num lines applied: %s\n" % num_lines_played
4834
4835 #
4836 # --------------------------------------------------------------------------------
4837 # path_concat
4838 # Concatenate a suffix path to a prefix, making the guarantee that the
4839 # suffix will always be "under" the prefix. the suffix is normalized,
4840 # then any prefix up-dirs ("..") are removed.
4841 #
4842 def path_concat(self, prefix, path):
4843 suffix = posixpath.normpath(path.lstrip('/').rstrip('/'))
4844 (head, tail) = os.path.split(suffix)
4845 tails = [tail]
4846 while head:
4847 (head, tail) = os.path.split(head)
4848 tails.insert(0, tail)
4849 drop = ''
4850 while tails[0] == '..':
4851 drop = os.path.join(drop, tails.pop(0))
4852 if drop != '':
4853 self.note("Ignoring %s prefix" % drop)
4854 for t in tails:
4855 prefix = os.path.join(prefix, t)
4856 return prefix
4857
4858 #
4859 # --------------------------------------------------------------------------------
4860 # implement_copy
4861 # startup-config and update-config are simply abbreviations for
4862 # config://startup-config and config://update_config
4863 #
4864 def implement_copy(self, words):
4865 if not words or len(words) > 3:
4866 print "Syntax: copy <src> <dst>"
4867 print "where <src> and <dst> can be"
4868 print " running-config startup-config update-config"
4869 print " config://<name-of-config-in-db> - A-Za-z0-9_-.@ "
4870 print " <url> - starting with either tftp://, ftp://, http:// or file://"
4871 return
4872 if len(words) == 1:
4873 src = words[0]
4874 dst = "terminal"
4875 elif len(words) == 2:
4876 (src, dst) = (words[0], words[1])
4877
4878 # Complete as required.
4879 # NOTE: No completion for upgrade-config as only our scripts should be using it
4880 if "running-config".startswith(src):
4881 src = "running-config"
4882 elif "startup-config".startswith(src):
4883 src = "startup-config"
4884 if "running-config".startswith(dst):
4885 dst = "running-config"
4886 elif "startup-config".startswith(dst):
4887 dst = "startup-config"
4888 elif "terminal".startswith(dst):
4889 dst = "terminal"
4890
4891 if src == dst:
4892 print "src and dst for copy command cannot be equal"
4893 return
4894
4895 # get from the source first
4896 src_text = ""
4897
4898 # map any abbreviations
4899 abbreviations = {'startup-config': 'config://startup-config',
4900 'upgrade-config': 'config://upgrade-config'
4901 }
4902 src = abbreviations.get(src, src)
4903 data = None
4904
4905 if src == "running-config": # get running
4906 src_text = run_config.implement_show_running_config([]) + "\n"
4907 elif src.startswith("config://"): # get local file
4908 m = re.search(self.local_name_pattern, src)
4909 if m:
4910 data = self.store.get_user_data_table(m.group(1), "latest")
4911 if data and len(data) > 0:
4912 src_text = self.store.get_user_data_file(data[0]['full_name'])
4913 else:
4914 print self.error_msg("src name does not match %s" % self.local_name_pattern)
4915 return
4916 elif src.startswith("ftp") or src.startswith("http"): # get URL
4917 src_text = self.store.get_text_from_url(src)
4918 elif src.startswith("file://"):
4919 #src_file = self.saved_configs_dirname + posixpath.normpath(src[7:]).lstrip('/').rstrip('/')
4920 src_file = self.path_concat(self.saved_configs_dirname, src[7:])
4921 if src_file == "":
4922 return self.error_msg("src %s could not be "
4923 "interpreted as a valid source for a file" % src)
4924 try:
4925 src_text = self.store.get_text_from_url("file://" + src_file)
4926 except Exception, e:
4927 return self.error_msg(src + ":" + str(e))
4928 else:
4929 return self.error_msg("Unknown configuration")
4930
4931 message = ""
4932 if len(words) > 2:
4933 message = " ".join(words[2:]).strip('"')
4934
4935 #
4936 # map any abbreviations
4937 dst = abbreviations.get(dst, dst)
4938
4939 # now copy to dest
4940 if dst == "running-config":
4941 return self.play_config_lines(src_text)
4942 elif dst == "terminal":
4943 return src_text # returning src_text here allow '|' to work
4944 elif dst.startswith("config://"):
4945 m = re.search(self.local_name_pattern, dst)
4946 if m:
4947 store_result = self.store.set_user_data_file(m.group(1), src_text)
4948 if store_result:
4949 result = json.loads(store_result)
4950 else:
4951 return self.error_msg("rest store result not json format")
4952 if 'status' in result and result['status'] == 'success':
4953 return None
4954 elif 'message' not in result:
4955 return self.error_msg("rest store result doesn't contain error message")
4956 else:
4957 return self.error_msg(result['message'])
4958 else:
4959 return self.error_msg("dst name does not match %s" % self.local_name_pattern)
4960 elif dst.startswith("ftp") or dst.startswith("http"):
4961 self.store.copy_text_to_url(dst, src_text, message)
4962 elif dst.startswith("file://"):
4963 dst_file = self.path_concat(self.saved_configs_dirname, dst[7:])
4964 dst_dir = '/'.join(dst_file.split('/')[:-1])
4965 if not os.path.exists(dst_dir):
4966 try:
4967 os.makedirs(dst_dir)
4968 except Exception as e:
4969 return self.error_msg("in creating destination directory: %s [%s]"
4970 % (str(e), dst_dir))
4971 if not os.path.isdir(dst_dir):
4972 return self.error_msg("destination directory is not a "
4973 "valid directory: %s" % dst_dir)
4974 with open(dst_file, 'w') as f:
4975 try:
4976 f.write(src_text)
4977 except Exception as e:
4978 return self.error_msg("in saving config file: %s [%s]" %
4979 (str(e), dst_file))
4980 elif dst == 'trash':
4981 if data:
4982 result = self.store.delete_user_data_file(data[0]['full_name'])
4983 if 'status' in result and result['status'] == 'success':
4984 return None
4985 elif 'message' not in result:
4986 return self.error_msg("rest store result doesn't contain error message")
4987 else:
4988 return self.error_msg(result['message'])
4989 else:
4990 print self.error_msg("source file retrieved,"
4991 "but could not copy to destination %s" % dst)
4992 return
4993
4994 #
4995 # --------------------------------------------------------------------------------
4996 # cp_conf_object_type
4997 # Completion function for a named object, returns names of
4998 # available obj_types
4999 #
5000 def cp_conf_object_type(self, words, text):
5001 if words[0] in mi.obj_types and len(words) == 1:
5002 return utif.add_delim(objects_starting_with(words[0], text), ' ')
5003 self.print_completion_help("<cr>")
5004 return []
5005
5006 #
5007 # --------------------------------------------------------------------------------
5008 # cp_conf_field
5009 # Handle if we've got a bunch of fields being concatenated,
5010 # e.g., priority 42 src-port 80
5011 #
5012 def cp_conf_field(self, obj_type, words, text):
5013 if len(words) % 2 == 1:
5014 latest_field_name = words[-1]
5015
5016 field_info = mi.obj_type_info_dict[obj_type]['fields'].get(latest_field_name, None)
5017 # if there was no shallow match of text, do deep match of xrefs if any
5018 if field_info is None:
5019 if obj_type in mi.alias_obj_type_xref:
5020 if latest_field_name in mi.alias_obj_type_xref[obj_type]:
5021 field_info = mi.obj_type_info_dict[latest_field_name]['fields']['id']
5022
5023 if field_info:
5024 completion_text = "< %s >: " % field_info['verbose-name']
5025 completion_text += field_info.get('help_text', "")
5026 self.print_completion_help(completion_text)
5027
5028
5029 # return choices based on the types
5030 if mi.is_field_boolean(obj_type, latest_field_name):
5031 return utif.add_delib([x for x in ["True", "False"] if x.startswith(text)],
5032 ' ')
5033
5034 # Use the 'Values:' prefix in the help_text as a way
5035 # of enumerating the different choices
5036 if field_info.get('help_text', "").startswith('Values:'):
5037 values = field_info.get('help_text').replace("Values:", "")
5038 return utif.add_delim([x for x in values.replace(" ","").split(',')
5039 if x.startswith(text)], ' ')
5040
5041 #
5042 # special cases:
5043 if obj_type == 'vns-interface-rule' and latest_field_name == 'mac':
5044 return utif.add_delim(objects_starting_with('host', text), ' ')
5045 elif obj_type == 'vns-interface-rule' and latest_field_name == 'switch':
5046 return utif.add_delim(objects_starting_with('switch', text), ' ')
5047 else:
5048 # we've got an even number of field-value pairs, so just give the list
5049 # of fields for this obj_type
5050 return utif.add_fields(
5051 self.fields_for_current_submode_starting_with(text),
5052 ' ')
5053
5054 #
5055 # --------------------------------------------------------------------------------
5056 # vns methods.
5057 #
5058
5059 #
5060 # --------------------------------------------------------------------------------
5061 # vns_obj_type
5062 # return True for an obj_type associated with vns, intended to identify
5063 # tables which are or aren't available in debug mode
5064 #
5065 @staticmethod
5066 def vns_obj_type(obj_type):
5067 if obj_type in [ 'vns-interface', 'vns-access-list-entry', 'vns-access-list',
5068 'vns-interface-access-list', 'vns-interface-rule',
5069 'vns-access-group', 'vns-interfaces', 'host-vns-interface']:
5070 return True
5071 return False
5072
5073 #
5074 # --------------------------------------------------------------------------------
5075 # vns_debug_obj_type
5076 # return True if the obj_type is available in the non-debug mode
5077 #
5078 def vns_debug_obj_type(self, obj_type):
5079 if self.debug:
5080 return True
5081 if self.in_config_submode("config-vns-") and obj_type == 'vns-access-list-entry':
5082 return False
5083 return not self.vns_obj_type(obj_type)
5084
5085 #
5086 # --------------------------------------------------------------------------------
5087 # vns_debug_show_commands
5088 # return True if the show command vns option is availabe in the current debug mode
5089 #
5090 def vns_debug_show_commands(self, show_command):
5091 if self.debug:
5092 return True
5093 if self.in_config_submode() and show_command == 'do_show_vns_interface_rule':
5094 return True
5095 if self.in_config_vns_if_mode() and show_command == 'do_show_vns_access_group':
5096 return True
5097 if show_command == 'do_show_vns':
5098 return True
5099 return not self.vns_obj_type(show_command.replace('_', '-').replace('do-show-', '', 1))
5100
5101 #
5102 # --------------------------------------------------------------------------------
5103 # vns_find_name
5104 # Return True when found, False otherwise
5105 #
5106 def vns_find_name(self, name):
5107 try:
5108 self.get_object_from_store("vns-definition", name)
5109 return True
5110 except Exception, e:
5111 pass
5112 return False
5113
5114 #
5115 # --------------------------------------------------------------------------------
5116 # vns_find_and_set_name
5117 # Return True when the name is known, as a side effect, set self.vns_name
5118 # Invalid names are managed by the model, see VnsNameValidator
5119 #
5120 def vns_find_and_set_name(self, words):
5121 if len(words) == 0:
5122 print self.error_msg("vns <name> required")
5123 return False
5124 elif len(words) > 1:
5125 print self.error_msg("Additional text after vns name ignored")
5126 name = words[0]
5127 if self.vns_find_name(name):
5128 return True
5129 return False
5130
5131 #
5132 # --------------------------------------------------------------------------------
5133 # vns_find_and_set_existing_name
5134 # Return True when the name is known, as a side effect, set self.vns_name
5135 # And display an error message concerning the failure.
5136 #
5137 def vns_find_and_set_existing_name(self, words):
5138 if self.vns_find_and_set_name(words):
5139 return True
5140 if len(words) > 0:
5141 print self.error_msg("Not Found: vns '%s'" % words[0])
5142 return False
5143
5144 #
5145 # --------------------------------------------------------------------------------
5146 # vns_find_or_add_name
5147 # Return True when either the vns name currently exists, or of a create
5148 # for the vns name succeeded.
5149 #
5150 def vns_find_or_add_name(self, words):
5151 if self.vns_find_and_set_name(words):
5152 return True
5153 name = words[0]
5154 errors = None
5155 ident = {mi.pk("vns-definition"): name }
5156 try:
5157 self.rest_create_object("vns-definition", ident)
5158 except Exception, e:
5159 errors = self.rest_error_to_dict(e, 'vns-definition')
5160 print self.rest_error_dict_to_message(errors)
5161 return False
5162
5163 return True
5164
5165
5166 #
5167 # --------------------------------------------------------------------------------
5168 # vns_all_ids
5169 #
5170 def vns_all_ids(self):
5171 return [x['id'] for x in self.get_table_from_store("vns-definition")]
5172
5173 #
5174 # --------------------------------------------------------------------------------
5175 # vns_convert_table_id
5176 # return a list of two entries, the first is the vns name, the second is the
5177 # updated column value
5178 #
5179 def vns_convert_table_id(self, obj_type, row, id):
5180 fields = row[id].split('|')
5181 if len(fields) == 1:
5182 # Just in case there's an error in the table
5183 return [fields[0], "<error>"]
5184 if len(fields) == 2:
5185 return fields
5186 elif len(fields) == 3:
5187 if obj_type == 'host-vns-interface':
5188 return [fields[1], fields[2]]
5189 elif obj_type == 'vns-access-list-entry':
5190 row['vns-access-list'] = fields[1]
5191 return [fields[0], fields[2]]
5192 else:
5193 print self.error_msg("3: vns_convert_table_id case %s" % obj_type)
5194 elif len(fields) == 4:
5195 if obj_type == 'vns-interface-access-list':
5196 # XXX ought to validate
5197 if row['vns-access-list'].split('|')[1] != fields[2]:
5198 print self.error_msg('vns-interface-access-list inconsistent %s and id %d' % \
5199 (row['vns-access-list'], row[id]))
5200 row['vns-access-list'] = fields[2]
5201 row['vns-interface'] = fields[1]
5202 return [fields[0], fields[2]]
5203 else:
5204 print self.error_msg("4: vns_convert_table_id case %s" % obj_type)
5205 else:
5206 print self.error_msg("vns_convert_table_id: length %d not managed" % len(fields))
5207 #
5208 # --------------------------------------------------------------------------------
5209 # vns_table_id_to_vns_column
5210 #
5211 # vns related tables in the model have unique id's created by concatenating the
5212 # vns name with other fields. For those tables, separate the vns id's from the
5213 # associated fields, and transform the vns association into its own column.
5214 #
5215 # obj_type is the name of the table.
5216 #
5217 # for the vns_interface, the vns name is extracted from several fields,
5218 # validated, updated to exclude the vns name, then a new column is added
5219 #
5220 def vns_table_id_to_vns_column(self, obj_type, entries):
5221 id = mi.pk(obj_type)
5222 for row in entries:
5223 if row[id]:
5224 type_info = mi.obj_type_info_dict[obj_type]['fields'][id]['type']
5225 if type_info == 'compound-key':
5226 mi.split_compound_into_dict(obj_type, id, row)
5227 else:
5228 convert_id = self.vns_convert_table_id(obj_type, row, id)
5229 row['vns'] = convert_id[0]
5230 row[id] = convert_id[1]
5231
5232 #
5233 # --------------------------------------------------------------------------------
5234 # vns_foreign_key_to_base_name
5235 # For foreign key's which vns wants to 'show', convert the
5236 # foriegn key to the last entry for the field, and display
5237 # that entry
5238 #
5239 def vns_foreign_key_to_base_name(self, obj_type, entries):
5240 foreign_keys = mi.obj_type_foreign_keys(obj_type)
5241 for row in entries:
5242 # convert the rule id into the rule suffix
5243 for foreign_key in foreign_keys:
5244 if foreign_key in row and row[foreign_key].find('|'):
5245 fields = row[foreign_key].split('|')
5246 row[foreign_key] = fields[-1]
5247
5248 #
5249 # --------------------------------------------------------------------------------
5250 # vns_join_host_fields
5251 # For vns-interfaces new fields are joined from the host table.
5252 #
5253 def vns_join_host_fields(self, obj_type, entries):
5254 if obj_type == 'vns-interface':
5255 #
5256 host_dap_dict = self.create_attachment_dict()
5257 host_ip_dict = self.create_ip_dict()
5258 #
5259 vns_interface_key = mi.pk('vns-interface')
5260 #
5261 # for vns-interface, currently vlan is shown in the output,
5262 # and this must be joined from the host's table. Again, it
5263 # is better to collect the complee host table, then create
5264 # a host dictionary
5265
5266 hosts_rows_dict = create_obj_type_dict('host', 'mac')
5267
5268 #
5269 # re-index host_rows_dict based not on mac, but on attachment point,
5270 # which is the sum of the switch+port. the objective here is to
5271 # identify the number of items on a specific-switch port.
5272 #
5273 ap_dict = {} # indexed by dpid|port
5274 for (mac, values) in host_dap_dict.items():
5275 for value in values:
5276 key = "%s|%s" % (value['switch'], value['ingress-port'])
5277 if not key in ap_dict:
5278 ap_dict[key] = 0
5279 ap_dict[key] += 1
5280 #
5281 # the ap_dict counts the number of mac's associated with an ap.
5282 # sort the host_rows_dict based on the lowest ap in each group.
5283 #
5284 for (mac, value) in host_dap_dict.items():
5285 new_value = sorted(value,
5286 key=lambda k:ap_dict["%s|%s" % (
5287 k['switch'],
5288 k['ingress-port'])],
5289 cmp=lambda x,y: int(x) - int(y))
5290 # add a tag to the dictionary of the first list item
5291 # when the ap_dict's entry's count is 1.
5292 first = new_value[0]
5293 if ap_dict['%s|%s' % (first['switch'], first['ingress-port'])] == 1:
5294 new_value[0]['prime'] = True
5295 host_dap_dict[mac] = new_value
5296
5297 for row in entries:
5298 fields = row['id'].split('/')
5299 if not 'rule' in row:
5300 row['rule'] = fields[0]
5301 if len(fields) != 2:
5302 continue # silently ignore any id's not containing hosts
5303 host = fields[1]
5304
5305 row['mac'] = host
5306 host_dict = {'mac' : host}
5307 row['attachment-points'] = self.get_attachment_points(
5308 host_dict,
5309 host_dap_dict)
5310 row['ips'] = self.get_ip_addresses(host_dict, host_ip_dict)
5311
5312 host_row = hosts_rows_dict.get(host, None)
5313 if host_row and len(host_row) == 1 and 'vlan' in host_row[0]:
5314 row['vlan'] = host_row[0]['vlan']
5315
5316 #
5317 # --------------------------------------------------------------------------------
5318 # vns_join_switch_fields
5319 # For vns-interfaces with entries arising from switch interface rules,
5320 # join in the switch and port.
5321 #
5322 def vns_join_switch_fields(self, vns_name, entries):
5323 #
5324 # store all the rules associated with a specific vns,
5325 # index them by the primary key.
5326 key = mi.pk('vns-interface-rule')
5327 if vns_name == None:
5328 # vns_name == None, this means 'all'
5329 vns_ifr_dict = create_obj_type_dict('vns-interface-rule',
5330 key)
5331 else:
5332 search_key = self.unique_key_from_non_unique([vns_name])
5333 vns_ifr_dict = create_obj_type_dict('vns-interface-rule',
5334 key,
5335 key,
5336 search_key)
5337 for entry in entries:
5338 if 'rule' in entry:
5339 rule = entry['rule']
5340 if rule in vns_ifr_dict:
5341 entry['switch'] = vns_ifr_dict[rule][0].get('switch', "")
5342 entry['ports'] = vns_ifr_dict[rule][0].get('ports', "")
5343
5344 #
5345 # end of vns methods.
5346 # --------------------------------------------------------------------------------
5347 #
5348
5349 #
5350 #
5351 # --------------------------------------------------------------------------------
5352 # debug_obj_type
5353 # Return True when the object is available, in self.debug mode, more objects
5354 # are available.
5355 #
5356 def debug_obj_type(self, obj_type):
5357 if self.debug:
5358 return True
5359 if mi.is_obj_type_source_debug_only(obj_type):
5360 return False
5361 if self.vns_debug_obj_type(obj_type):
5362 if not obj_type in mi.alias_obj_types:
5363 return True
5364 return False
5365
5366 #
5367 # generic parsing routines
5368 #
5369
5370 #
5371 # --------------------------------------------------------------------------------
5372 # completion_reset
5373 #
5374 def completion_reset(self):
5375 self.last_line = None
5376 self.last_options = None
5377 self.completion_cache = True
5378 self.last_completion_char = readline.get_completion_type()
5379
5380
5381 #
5382 # --------------------------------------------------------------------------------
5383 # completer
5384 # This is the main function that is called in order to complete user input
5385 #
5386 def completer(self, text, state):
5387 question_mark = ord('?')
5388 if readline.get_completion_type() == question_mark:
5389 if len(readline.get_line_buffer()) == 0:
5390 #
5391 # manage printing of help text during command completion
5392 help_text = self.help_splash(None, text)
5393 if help_text != "":
5394 self.print_completion_help(help_text)
5395 return
5396
5397 try:
5398 origline = readline.get_line_buffer()
5399 # See if we have a cached reply already
5400 if (self.completion_cache and origline == self.last_line and
5401 self.last_completion_char == readline.get_completion_type() and
5402 self.last_options):
5403
5404 if state < len(self.last_options):
5405 return self.last_options[state]
5406 else:
5407 # apparently, for the linux VM choice don't print
5408 if self.last_options and \
5409 len(self.last_options) > 1 and \
5410 self.last_completion_char == ord('\t'):
5411 choices_text = self.choices_text_builder(self.last_options)
5412 self.print_completion_help(choices_text)
5413
5414 if self.completion_skip:
5415 self.completion_cache = False
5416 self.completion_skip = False
5417 return None
5418
5419 self.completion_reset()
5420
5421 # parse what user has typed so far
5422
5423 begin = readline.get_begidx()
5424 end = readline.get_endidx()
5425
5426 # Find which command we're in for a semicolon-separated list of single commands
5427 # LOOK! This doesn't handle cases where an earlier command in the line changed
5428 # the mode so the completion for later commands in the line should be different.
5429 # For example, if you typed "enable; conf" it won't detect that it should be
5430 # able to complete "conf" to "configure" because the enable command has not been
5431 # executed yet, so you're not in enable mode yet. Handling that case would be
5432 # non-trivial I think, at least with the current CLI framework.
5433 command_begin = 0
5434 command_end = 0
5435 while True:
5436 command_end = self.find_with_quoting(origline, ';', start_index=command_begin)
5437 if command_end < 0:
5438 command_end = len(origline)
5439 break
5440 if begin >= command_begin and end <= command_end:
5441 break
5442 command_begin = command_end + 1
5443
5444 # Skip past any leading whitespace in the command
5445 while command_begin < begin and origline[command_begin].isspace():
5446 command_begin += 1
5447
5448 words = origline[command_begin:end].split()
5449
5450 # remove last term if it is the one being matched
5451 if begin != end:
5452 words.pop()
5453
5454 # LOOK! there are at least three places that try to parse the valid options:
5455 # 1. When actually handling a command
5456 # 2. When trying to show completions (here)
5457 # 3. When displaying help
5458
5459 # complete the first word in a command line
5460 if not words or begin == command_begin:
5461 options = self.commands_for_current_mode_starting_with(text, completion = True)
5462 if self.in_config_mode():
5463 for item in self.obj_types_for_config_mode_starting_with(text):
5464 self.append_when_missing(options, item)
5465 if self.in_config_submode():
5466 for item in self.fields_for_current_submode_starting_with(text):
5467 self.append_when_missing(options, item)
5468 options = [x if x.endswith(' ') else x + ' ' for x in sorted(options)]
5469 # Complete the 2nd word or later
5470 else:
5471 commands = self.commands_for_current_mode_starting_with(words[0])
5472 obj_types = self.obj_types_for_config_mode_starting_with(words[0]) if self.in_config_mode() else []
5473 fields = self.fields_for_current_submode_starting_with(words[0]) if self.in_config_submode() else []
5474 if len(commands) + len(obj_types) + len(fields) > 1:
5475 if len(fields) > 1:
5476 fields = [words[0]] if words[0] in fields else []
5477 if len(commands) > 1:
5478 commands = [words[0]] if words[0] in commands else []
5479 if len(obj_types) > 1:
5480 obj_types = [words[0]] if words[0] in obj_types else []
5481
5482 if len(fields) == 1:
5483 options = self.cp_conf_field(self.get_current_mode_obj_type(), words, text)
5484 elif len(obj_types) == 1:
5485 options = self.cp_conf_object_type(obj_types + words[1:], text)
5486 elif len(commands) == 1:
5487 try:
5488 # options[0] is expanded while words[0] is not
5489 method = self.completion_method_from_name(commands[0])
5490 if method:
5491 options = method(words, text, readline.get_completion_type())
5492 if not options:
5493 # no match
5494 return None
5495 else:
5496 if readline.get_completion_type() == question_mark:
5497 options = command.do_command_completion_help(words, text)
5498 else:
5499 options = command.do_command_completion(words, text)
5500 #else:
5501 #if options:
5502 #print syntax_help
5503 #else:
5504 #pass
5505 #self.print_completion_help(syntax_help)
5506
5507 except AttributeError:
5508 if self.debug or self.debug_backtrace:
5509 traceback.print_exc()
5510 return None
5511
5512 else:
5513 options = None
5514
5515 except Exception, e:
5516 if self.debug or self.debug_backtrace:
5517 traceback.print_exc()
5518 # errors in connect are caught silently, complain here
5519 # TODO - Maybe we should log this in a file we can review
5520 # at a later date?
5521
5522 try:
5523 if options:
5524 self.last_line = origline
5525 self.last_options = options
5526 self.last_completion_char = readline.get_completion_type()
5527 return options[state]
5528 except IndexError:
5529 return None
5530
5531 return None
5532
5533 #
5534 # --------------------------------------------------------------------------------
5535 # handle_alias
5536 # Manage alias creation for an obj_type, only manage one alias for each of the
5537 # targets, although that requirement exists so that a single alias
5538 # can be displayed for output from (show) commands
5539 #
5540 # Note that setting the alias to its current value currently causes the
5541 # alias to be created (ie: there's no specific search to find the item)
5542 #
5543 def handle_alias(self, obj_type, alias_obj_type, alias_value):
5544
5545 if alias_value in self.reserved_words:
5546 return self.error_msg("alias value %s is a reserved word (%s)" %
5547 (alias_value, ', '.join(self.reserved_words)))
5548 #
5549 # allow the use of 'alias' when only one alias entry
5550 # exists for the obj_type. only-one ought to be typical.
5551 # this may need to be removed after a compatability period.
5552 if alias_obj_type == 'alias':
5553 # there should be only one alias table for this obj_type
5554 aliases = mi.alias_obj_type_xref[obj_type]
5555 if len(aliases) != 1:
5556 print self.error_msg("Internal more than one alias choice")
5557 alias_obj_type = aliases[0]
5558
5559 obj = self.get_current_mode_obj()
5560 alias_key = mi.pk(alias_obj_type)
5561
5562 #
5563 # create row for table with foreign key
5564 # find the name of the field which is the foreign key...
5565 foreign_field = mi.alias_obj_type_field(alias_obj_type)
5566 if not foreign_field:
5567 print self.error_msg("internal handle_alias: alias_obj_type_field")
5568 return None
5569
5570 #
5571 # delete the current alias if it exists
5572 try:
5573 exists = self.get_object_from_store(alias_obj_type, alias_value)
5574 if len(exists):
5575 self.rest_delete_object(alias_obj_type, alias_value)
5576 except:
5577 pass
5578
5579 try:
5580 create_dict = { alias_key : alias_value, foreign_field : obj }
5581 self.rest_create_object(alias_obj_type, create_dict)
5582 errors = None
5583 except Exception, e:
5584 errors = self.rest_error_to_dict(e, alias_obj_type)
5585
5586 if errors:
5587 return self.error_msg("could not create %s for %s (%s): %s" %
5588 (alias_obj_type, alias_value, obj_type,
5589 self.rest_error_dict_to_message(errors)))
5590 #
5591 # remove other existing alias for the same foreign key
5592 # find any other alias for this config object, then remove them
5593 #
5594 try:
5595 rows = self.get_table_from_store(alias_obj_type,
5596 foreign_field,
5597 obj,
5598 "exact")
5599 except Exception, e:
5600 errors = self.rest_error_to_dict(e, alias_obj_type)
5601 print self.rest_error_dict_to_message(errors)
5602 rows = []
5603 #
5604 #
5605 for row in rows:
5606 #
5607 # skip the entry which was just inserted
5608 if row[alias_key] == alias_value and row[foreign_field] == obj:
5609 continue
5610 try:
5611 self.rest_delete_object(alias_obj_type, row[alias_key])
5612 self.warning("removed other alias '%s' for %s '%s'" %
5613 (row[alias_key], foreign_field, row[foreign_field]))
5614 except:
5615 pass
5616
5617 return None
5618
5619
5620 #
5621 # --------------------------------------------------------------------------------
5622 # update_foreign_key
5623 # Given an obj_type, its primary key name, and the old and new
5624 # id values, find any associated tables which have foreign keys
5625 # associated with this table, and update the foreign key so that
5626 # the table again points to a valid id.
5627 #
5628 # its unlikely, although not impossible, for the foreign key
5629 # update to need to cascade.
5630 #
5631 def update_foreign_key(self, obj_type, obj_key, old_id, new_id):
5632 if obj_type in mi.foreign_key_xref and \
5633 obj_key in mi.foreign_key_xref[obj_type]:
5634 for (fk_obj_type, fk_name) in mi.foreign_key_xref[obj_type][obj_key]:
5635 #
5636 # find any affected rows
5637 try:
5638 rows = self.get_table_from_store(fk_obj_type,
5639 fk_name,
5640 old_id,
5641 "exact")
5642 except:
5643 rows = []
5644
5645 key = mi.pk(fk_obj_type)
5646 for row in rows:
5647 self.warning("updating %s key %s field %s to %s" % \
5648 (fk_obj_type, row[key], fk_name, new_id))
5649 try:
5650 self.rest_update_object(fk_obj_type,
5651 key,
5652 row[key],
5653 { fk_name : new_id })
5654 errors = None
5655 except Exception, e:
5656 errors = self.rest_error_to_dict(e, fk_obj_type)
5657
5658 if errors:
5659 print self.rest_error_dict_to_message(errors)
5660
5661 #
5662 # --------------------------------------------------------------------------------
5663 # handle_field
5664 #
5665 # handle_field calls itself recursively to parse fields and build
5666 # a field dict. It then passes the field dict to one rest call.
5667 #
5668 def handle_field(self, words, accumulated_data=None):
5669 if accumulated_data is None:
5670 accumulated_data = {}
5671 if len(words) < 2:
5672 return self.error_msg("incorrect number of args (must be <field> <value>)")
5673 obj_type = self.get_current_mode_obj_type()
5674 obj = self.get_current_mode_obj()
5675 last_word = 2
5676 field = words[0]
5677 value = words[1]
5678
5679 # complete the field if needed
5680 field_choices = self.fields_for_current_submode_starting_with(words[0])
5681 # LOOK!: robv: Fix to work with a field names which is a prefix of another
5682 if len(field_choices) > 1:
5683 for field_choice in field_choices:
5684 if field_choice == words[0]:
5685 field_choices = [field_choice]
5686 break
5687 if len(field_choices) > 1:
5688 return "Multiple matches for field name %s." % field
5689 if len(field_choices) == 0:
5690 return self.error_msg("%s has no field named %s." % (obj_type, field))
5691 field = field_choices[0]
5692
5693 #
5694 # handle special case alias's which aren't understood via xref
5695 # (this is because vns-interface-rule doesn't foreignKey neigther
5696 # switch or mac references to the switch/host tables)
5697 #
5698 if obj_type == 'vns-interface-rule' and field == 'switch':
5699 if not self.DPID_RE.match(value):
5700 alias_value = alias_lookup('switch-alias', value)
5701 if alias_value:
5702 value = alias_value
5703 else:
5704 return "Syntax: Unknown switch alias '%s'; Specify switch as DPID or alias" % value
5705 if obj_type == 'vns-interface-rule' and field == 'mac':
5706 if not self.MAC_RE.match(value):
5707 alias_value = alias_lookup('host-alias', value)
5708 if alias_value:
5709 value = alias_value
5710 else:
5711 return "Syntax: Unknown host alias '%s'; Specify host as MAC or alias" % value
5712
5713 #
5714 # Replace common type values with expected types, for example true or false
5715 # for True/False.
5716 if mi.is_field_boolean(self.get_current_mode_obj_type(), field):
5717 if value.lower() == 'false':
5718 value = 'False'
5719 elif value.lower() == 'true':
5720 value = 'True'
5721 #
5722 elif mi.is_hex_allowed(obj_type, field) and self.HEX_RE.match(value):
5723 value = str(int(value, 16))
5724 #
5725 # Look up any validation or value changing callouts...
5726 validate = mi.field_validation(self.get_current_mode_obj_type(), field)
5727 if validate:
5728 validate_error = validate(obj_type, field, value)
5729 if validate_error:
5730 return validate_error
5731
5732 accumulated_data[field] = value
5733
5734 ret_val = None
5735
5736 if len(words) > last_word:
5737 # more to munch
5738 ret_val = self.handle_field(words[last_word:], accumulated_data)
5739 else:
5740 # munched everything, now update
5741 obj_key = mi.pk(obj_type)
5742
5743 prepare_update = mi.obj_type_prepare_row_update(obj_type)
5744 if prepare_update:
5745 accumulated_data = prepare_update(obj_type, obj_key, obj,
5746 accumulated_data)
5747 # in case the obj_type's key is updated
5748 obj = accumulated_data[obj_key]
5749
5750 #
5751 # Note: if all the fields for a row are all manually reverted
5752 # to a default value, perhaps the row ought to be deleted.
5753 errors = None
5754 try:
5755 self.rest_update_object(obj_type, obj_key, obj, accumulated_data)
5756 except Exception, e:
5757 errors = self.rest_error_to_dict(e, obj_type)
5758
5759 if errors:
5760 ret_val = self.rest_error_dict_to_message(errors)
5761 else:
5762 #
5763 # if the primary key was part of the update. find relatred
5764 # tables and update the foreign key. XXX perhaps this is
5765 # the rest api's problem?
5766 #
5767 if obj_key in accumulated_data and \
5768 accumulated_data[obj_key] != obj:
5769
5770 self.update_foreign_key(obj_type, obj_key, obj,
5771 accumulated_data[obj_key])
5772 #
5773 # delete the old key.
5774 self.rest_delete_object(obj_type, obj)
5775
5776 #
5777 # move the the current id
5778 self.set_current_mode_obj(accumulated_data[obj_key])
5779
5780 return ret_val
5781
5782 #
5783 # --------------------------------------------------------------------------------
5784 # convert_hex_to_decimal
5785 # Review the contents of all the values, looking for hex
5786 # valued fields. When a hex value is identified, change
5787 # it to decimal when 'is_hex_allowed' is true.
5788 #
5789 def convert_hex_to_decimal(self, obj_type, field_dict):
5790 for key in field_dict.keys():
5791 if mi.is_hex_allowed(obj_type, key) and \
5792 self.HEX_RE.match(field_dict[key]):
5793 field_dict[key] = int(field_dict[key], 16)
5794
5795 #
5796 # --------------------------------------------------------------------------------
5797 # handle_obj_type
5798 # In config mode, this handles if first word is an object-type
5799 # (ex: "flow-entry catch-web")
5800 #
5801 def handle_obj_type(self, words):
5802 if len(words) < 2:
5803 return "Syntax: %s <key>: where key is an id of %s" % (words[0], words[0])
5804
5805 (obj_type, obj_name) = (words[0], words[1])
5806
5807 obj_name = convert_alias_to_object_key(obj_type, obj_name)
5808 # First check if this is something we can actually configure
5809 if obj_type not in mi.obj_types:
5810 return self.error_msg("unknown command or object.")
5811
5812 # Next, find the object
5813 # Deal with any changes to the lookup name based on the 'concatenation'
5814 # of the config mode name to the named identifer.
5815 #
5816 found_obj = self.get_obj_of_type(obj_type, obj_name)
5817
5818 if found_obj == None:
5819 # need to create object, obj_data is the rest api dictionary used
5820 # to create the new row.
5821 obj_data = { mi.pk(obj_type): obj_name }
5822
5823 if self.in_config_submode():
5824 # add in all the key value from modes in the stack
5825 # LOOK! this is brittle as it depends on the name of
5826 # the mode matching the name of the attribute.
5827 #
5828 obj_data = self.mode_stack_to_rest_dict(obj_data)
5829
5830 #
5831 self.convert_hex_to_decimal(obj_type, obj_data)
5832
5833 # make sure all the foreign keys are populated
5834 #
5835 for key in mi.obj_type_foreign_keys(obj_type):
5836 if not key in obj_data:
5837 print self.error_msg("foreign key '%s' missing from mode stack %s" %
5838 (key, obj_data))
5839
5840 prepare_update = mi.obj_type_prepare_row_update(obj_type)
5841 if prepare_update:
5842 obj_data = prepare_update(obj_type,
5843 mi.pk(obj_type), obj_name,
5844 obj_data)
5845
5846 # create the object
5847 errors = None
5848 try:
5849 self.rest_create_object(obj_type, obj_data)
5850 except Exception, e:
5851 errors = self.rest_error_to_dict(e, obj_type)
5852
5853 if errors:
5854 return self.rest_error_dict_to_message(errors)
5855
5856 # get it back, populating found_obj
5857 found_obj = self.get_obj_of_type(obj_type, obj_name)
5858
5859 # push on the submode for the object
5860 # LOOK! assumes all object commands drop you into a submode...
5861 if found_obj:
5862 sub_mode = "config-%s" % obj_type
5863 self.push_mode(sub_mode, obj_type, obj_name)
5864
5865 # hand off the rest of the line to handle_field
5866 if len(words) > 2:
5867 return self.handle_field(words[2:])
5868 else:
5869 return self.error_msg("No %s with %s = %s" %
5870 (obj_type, mi.pk(obj_type), obj_name))
5871
5872 #
5873 # --------------------------------------------------------------------------------
5874 # print_completion_help
5875 #
5876 def print_completion_help(self, completion_help_text):
5877 origline = readline.get_line_buffer()
5878 end = readline.get_endidx()
5879 cur_command = origline[0:end]
5880
5881 help_text = "\n%s\n%s%s" % ( completion_help_text,
5882 self.prompt,
5883 cur_command)
5884 self.completion_skip = True
5885 sys.stdout.write(help_text)
5886
5887 #
5888 # --------------------------------------------------------------------------------
5889 # get_ip_from_switch_dpid_or_alias
5890 # Returns the IP associated with the switch if it exists
5891 # or the original string if it's not a dpid or alias.
5892 #
5893 def get_ip_from_switch_dpid_or_alias(self, alias):
5894 dpid = convert_alias_to_object_key("switches", alias)
5895 if self.DPID_RE.match(dpid):
5896 query_dict = { 'dpid' : dpid }
5897 row = rest_to_model.get_model_from_url('switches', query_dict)
5898 if len(row) >= 1:
5899 if not 'ip-address' in row[0] or row[0].get('ip-address') == '':
5900 return self.error_msg("switch %s currently not connected "
5901 % alias)
5902 return row[0].get('ip-address')
5903 return alias
5904
5905 #
5906 # --------------------------------------------------------------------------------
5907 # implement_write
5908 #
5909 def implement_write(self, words):
5910 if len(words) == 1:
5911 if "memory".startswith(words[0]):
5912 return self.implement_copy(["running-config", "startup-config"])
5913 elif "erase".startswith(words[0]):
5914 print "This will clear the startup-config and set it to be empty."
5915 resp = raw_input("Are you sure that want to proceed? [n]")
5916 if resp and "yes".startswith(resp.lower()):
5917 print "Erasing startup config ..."
5918 result = self.store.delete_user_data_file("startup-config/time/len/version")
5919 if 'status' in result and result['status'] == 'success':
5920 return None
5921 elif 'message' not in result:
5922 return self.error_msg("rest store result doesn't contain error message")
5923 else:
5924 return self.error_msg(result['message'])
5925 else:
5926 print "Command aborted by user: write erase"
5927 return
5928 elif "terminal".startswith(words[0]):
5929 return self.implement_copy(["running-config", "terminal"])
5930 return "Syntax: write < terminal | memory | erase>"
5931
5932 #
5933 # --------------------------------------------------------------------------------
5934 #
5935
5936 @staticmethod
5937 def set_clock_action(data):
5938 time_values = data['time'].split(':')
5939 if len(time_values) != 3:
5940 raise error.CommandError('Invalid time; must be HH:MM:SS')
5941 hour = int(time_values[0])
5942 minute = int(time_values[1])
5943 second = int(time_values[2])
5944
5945 MONTH_NAMES = ('January', 'February', 'March', 'April', 'May', 'June',
5946 'July', 'August', 'September', 'October', 'November', 'December')
5947
5948 day_of_month = int(data['day-of-month'])
5949 month_name = data['month']
5950 if month_name not in MONTH_NAMES:
5951 raise error.CommandError('Invalid month name (e.g. January, May, July)')
5952 month = MONTH_NAMES.index(month_name) + 1
5953 year = int(data['year'])
5954
5955 date_time_info = {
5956 'year': year,
5957 'month': month,
5958 'day': day_of_month,
5959 'hour': hour,
5960 'minute': minute,
5961 'second': second
5962 }
5963 url = 'http://%s/rest/v1/system/clock/local' % cli.controller
5964 result = cli.store.rest_post_request(url, date_time_info)
5965 date_time_info = json.loads(result)
5966 clock_string = SDNSh.get_clock_string(date_time_info, False)
5967 return clock_string
5968
5969 #
5970 # --------------------------------------------------------------------------------
5971 # get_misconfigured_default_gateway
5972 #
5973 @staticmethod
5974 def get_misconfigured_default_gateway():
5975 """
5976 Determine if the controller is configured with a default gateway
5977 setting that doesn't match (i.e. is not on the same subnet)
5978 any of the interfaces configured with static IP addresses.
5979 This is used by the begin_default_gateway_check_action and
5980 end_default_gateway_check_action actions that check if a command
5981 results in a misconfigured default gateway setting and, if so,
5982 emit an error message.
5983 If the default gateway is misconfigured, the return value is the
5984 default gateway value. Otherwise, the return value is None.
5985 """
5986 # First figure out which controller-node we're working with.
5987 # This is a little kludgy right now. We check the mode stack
5988 # looking for a controller node element.
5989 controller_id = cli.get_nested_mode_obj('controller-node')
5990 if not controller_id:
5991 raise error.CommandDescriptionError('check_default_gateway_action must be called from a (possibly nested) controller node config mode')
5992
5993 controller = cli.get_object_from_store('controller-node', controller_id)
5994 if not controller:
5995 # This shouldn't really happen unless someone/thing has mucked with the
5996 # controller object out from under this instance of the CLI
5997 # but just to be safe...
5998 raise error.CommandInvocationError('Current controller settings have been deleted.')
5999
6000 # Check if the controller node is configured with a default gateway.
6001 # If not, then there's no possible misconfiguration and we're done.
6002 default_gateway = controller.get('default-gateway')
6003 if default_gateway == '':
6004 return
6005
6006 # There is a default gateway configured, so we need to check if there's
6007 # an interface that matches (i.e. is on the same subnet as) the default
6008 # gateway
6009 interfaces = cli.rest_query_objects('controller-interface', {'controller': controller_id})
6010 for interface in interfaces:
6011 mode = interface.get('mode')
6012 if mode == 'static':
6013 ip = interface.get('ip')
6014 netmask = interface.get('netmask')
6015 if (ip != '') and (netmask != '') and same_subnet(default_gateway, ip, netmask):
6016 return None
6017
6018 return default_gateway
6019
6020 #
6021 # --------------------------------------------------------------------------------
6022 # check_default_gateway_action
6023 #
6024 @staticmethod
6025 def begin_default_gateway_check_action():
6026 """
6027 This is an action proc for the data-driven command module that is used
6028 in conjunction with the end_default_gateway_check_action to check if a
6029 CLI command results in an improperly configured default gateway setting.
6030 Currently the backend code that maps the network/interface settings
6031 for the controller to the /etc/network/interfaces file will only
6032 apply the default gateway setting if it's on the same subnet as an
6033 interface that's configured with a static IP address/netmask.
6034 So if there's no interface that matches the default gateway (either
6035 because it's configured to use DHCP or the subnet doesn't match)
6036 then the default gateway setting will be ignored and we want to warn
6037 the user about the misconfiguration. The check should be performed
6038 on any configuration change that could affect this check. This includes:
6039
6040 1) setting the default gateway
6041 2) setting the mode of an interface to DHCP
6042 3) setting/resetting the ip/netmask of an interface
6043 4) deleting an interface
6044
6045 We only want to emit the warning if the default gateway was previously
6046 not misconfigured and the current command resulted in a misconfiguration,
6047 so we want to check for a misconfiguration before applying the command
6048 and then check again after the settings have been updated. The
6049 begin_default_gateway_check_action is the action that is performed before
6050 the settings are updated and the corresponding end_default_gateway_check_action
6051 action is the one that is performed after the settings are updated.
6052
6053 LOOK!: Ideally we should be doing this check in the sdncon code instead
6054 of here in the CLI code. That way the warning would also be returned
6055 for clients that are changing these settings directly via the REST
6056 API instead of via the CLI. But there isn't a really good mechanism
6057 currently for the REST API to return a warning but still apply the
6058 config change.
6059 """
6060 # Stash away if the gateway setting was already misconfigured at the
6061 # beginning of the current command.
6062 # LOOK! This is a bit kludgy to stash this away in the CLI object.
6063 # When the command module action API is changed to allow access to the
6064 # context, this code should be updated to stash the value in the context
6065 # instead of the CLI.
6066 cli.begin_misconfigured_default_gateway = SDNSh.get_misconfigured_default_gateway()
6067
6068 @staticmethod
6069 def end_default_gateway_check_action():
6070 """
6071 This is an action proc for the data-driven command module that is used
6072 in conjunction with the begin_default_gateway_check_action to check if a
6073 CLI command results in an improperly configured default gateway setting.
6074 Check the doc comments for begin_default_gateway_check_action for more
6075 details about how the two actions are intended to be used.
6076 """
6077 end_misconfigured_default_gateway = SDNSh.get_misconfigured_default_gateway()
6078 if (end_misconfigured_default_gateway and
6079 (end_misconfigured_default_gateway != cli.begin_misconfigured_default_gateway)):
6080 return ('The controller is configured with a default gateway setting (%s), that\n'
6081 'is not on the same subnet as any of the interfaces configured to use a\n'
6082 'static IP address. The default gateway setting will be ignored.' %
6083 end_misconfigured_default_gateway)
6084
6085
6086 #
6087 # --------------------------------------------------------------------------------
6088 # load_time_zone_list
6089 # Use the rest api to collect all the known time zones
6090 #
6091 @staticmethod
6092 def load_time_zone_list():
6093 if not cli.time_zone_list:
6094 url = "http://%s/rest/v1/system/timezones/all" % cli.controller
6095 cli.time_zone_list = cli.rest_simple_request_to_dict(url)
6096
6097 #
6098 # --------------------------------------------------------------------------------
6099 # time_zone_completion
6100 #
6101 @staticmethod
6102 def time_zone_completion(words, text, completions):
6103 SDNSh.load_time_zone_list()
6104 completion_dict = {}
6105 tz_completions = []
6106 for tz in cli.time_zone_list:
6107 if tz.lower().startswith(text.lower()):
6108 tz_completions.append(tz)
6109 for index in range(len(text), len(tz)):
6110 if tz[index] == '/':
6111 tz = tz[:index+1]
6112 break
6113 completion_dict[tz] = 'Timezone Choice'
6114
6115 if len(completion_dict) >= 1:
6116 completions.update(completion_dict)
6117
6118 #
6119 # --------------------------------------------------------------------------------
6120 # time_zone_validation
6121 #
6122 @staticmethod
6123 def time_zone_validation(typedef, value):
6124 SDNSh.load_time_zone_list()
6125 if value not in cli.time_zone_list:
6126 raise error.ArgumentValidationError('Invalid time zone')
6127
6128 #
6129 # --------------------------------------------------------------------------------
6130 # cp_clock
6131 #
6132 @staticmethod
6133 def cp_clock(words, text, completion_char):
6134 return command.do_command_completion(words, text)
6135
6136 #
6137 # --------------------------------------------------------------------------------
6138 # do_clock
6139 #
6140 @staticmethod
6141 def do_clock(words):
6142 return command.do_command(['clock'] + words)
6143
6144 #
6145 # --------------------------------------------------------------------------------
6146 # do_show_clock
6147 #
6148 def cp_show_clock(self, words, text, completion_char):
6149 if len(words) == 1 and 'detail'.startswith(text.lower()):
6150 return ['detail']
6151
6152
6153 @staticmethod
6154 def get_clock_string(time_info, detail = None):
6155 # The tz item contains the abbreviated time zone string (e.g. PST, EST)
6156 # This is different from the full time zone string (e.g. America/Los_Angeles)
6157 # which we get from the controller-node object below when we're showing
6158 # detail info.
6159 tz = time_info['tz']
6160 del time_info['tz']
6161 dt = datetime.datetime(**time_info)
6162 if detail:
6163 time_info['tz'] = tz
6164
6165 # Arista-style format
6166 #show_result = dt.strftime('%c')
6167
6168 # Cisco-style format
6169 clock_string = dt.strftime('%H:%M:%S '+ tz + ' %a %b %d %Y')
6170
6171 return clock_string
6172
6173 #
6174 # --------------------------------------------------------------------------------
6175 # do_show_clock
6176 #
6177 @staticmethod
6178 def do_show_clock(words):
6179 syntax_help = "Syntax: show clock [detail]"
6180
6181 if len(words) == 0:
6182 detail = False
6183 elif len(words) == 1:
6184 if not 'detail'.startswith(words[0].lower()):
6185 return syntax_help
6186 detail = True
6187 else:
6188 return syntax_help
6189
6190 url = "http://%s/rest/v1/system/clock/local/" % cli.controller
6191 time_info = cli.rest_simple_request_to_dict(url)
6192 clock_string = SDNSh.get_clock_string(time_info, detail)
6193 return clock_string
6194
6195 #
6196 # --------------------------------------------------------------------------------
6197 # cp_show_vns
6198 #
6199 def cp_show_vns(self, words, text, completion_char):
6200 if len(words) == 1:
6201 return objects_starting_with('vns-definition', text)
6202 elif len(words) == 2:
6203 return [x for x in
6204 [ 'interface', 'mac-address-table', 'interface-rules', \
6205 'access-lists', 'running-config', 'switch', 'flow' ]
6206 if x.startswith(text)]
6207 elif len(words) == 3 and 'switch'.startswith(words[2]):
6208 return objects_starting_with("switch", text)
6209 elif (len(words) >= 3) and (words[2] == 'flow'):
6210 return self.cp_show_vns_flow(words, text)
6211 else:
6212 self.print_completion_help("<cr>")
6213
6214 #
6215 # --------------------------------------------------------------------------------
6216 # show_vns_definition_running_config
6217 # Display the vns-definition -- the interface rules for the vns.
6218 #
6219 def show_vns_definition_running_config(self, config, vns_name,tenant=None,indent=0):
6220 if tenant==None and vns_name!='all':
6221 vns_id='default|'+vns_name
6222 else:
6223 vns_id=tenant +'|'+vns_name
6224 try:
6225 vns = self.get_object_from_store('vns-definition', vns_id)
6226 except:
6227 return self.error_msg('no vns named %s' % vns_name)
6228
6229 config.append(' ' *2*indent + "vns-definition %s\n" % vns_name)
6230 run_config.running_config_vns_details(config, vns, indent+1)
6231
6232 vns_rules = None
6233 try:
6234 vns_rules = self.get_table_from_store('vns-interface-rule',
6235 'vns', vns_id, "exact")
6236 except Exception:
6237 pass
6238
6239 if vns_rules:
6240 run_config.running_config_vns_if_rule(config,
6241 vns_rules,indent+1)
6242
6243 #
6244 # --------------------------------------------------------------------------------
6245 # show_vns_running_config
6246 # In the larger 'running-config' for vns, the complete vns tables are read
6247 # since the complete 'running-config' will display all the data from the
6248 # fields. Here, the tables are searched only for specific vns entries.
6249 #
6250 # The procedure's name is choosen carefully, since its not intended to
6251 # part of any of show command, completion, or command processing
6252 #
6253 def show_vns_running_config(self, vns_name, tenant=None, indent=0):
6254 config=[]
6255 preconfig=[' '*2*indent +"vns %s\n" % vns_name]
6256 if tenant==None and vns_name!='all':
6257 vns_name='default|'+vns_name
6258 if tenant!=None:
6259 vns_name=tenant+'|'+vns_name
6260 try:
6261 self.get_object_from_store('vns-definition', vns_name)
6262 except:
6263 return self.error_msg('no vns named %s' % vns_name)
6264 vns_acls = []
6265 try:
6266 vns_acls = self.get_table_from_store('vns-access-list',
6267 'vns', vns_name, 'exact')
6268 except Exception, e:
6269 pass
6270
6271 for acl in vns_acls:
6272 # note use of key initialized above
6273
6274 vns_acl_entries = None
6275 try:
6276 vns_acl_entries = self.get_table_from_store('vns-access-list-entry',
6277 'vns-access-list',
6278 acl['id'],
6279 'exact')
6280 except Exception:
6281 pass
6282 run_config.running_config_vns_acl(config, vns_name, acl, vns_acl_entries,indent+1)
6283
6284 vns_interface_acl = None
6285 try:
6286 vns_interface_acl = self.get_table_from_store('vns-interface-access-list')
6287 except Exception:
6288 pass
6289
6290 for vns_if in run_config.running_config_active_vns_interfaces(vns_name, vns_interface_acl):
6291 config.append(' ' *2*(indent+1) + "interface %s\n" % vns_if)
6292 run_config.running_config_vns_if_and_access_group(config,
6293 vns_name,
6294 vns_if,
6295 vns_interface_acl,indent+2)
6296 if len(config) > 0:
6297 config = preconfig + config
6298 return ''.join(config)
6299
6300 #generate tenant xxx running config
6301 def show_tenant_running_config(self, config, tenant_name):
6302 try:
6303 tenants = self.get_object_from_store('tenant', tenant_name)
6304 except:
6305 return self.error_msg('no tenant named %s' % tenant_name)
6306
6307 config.append("!\ntenant %s\n" % tenant_name)
6308 run_config.running_config_tenant_details(config,tenants)
6309
6310 try:
6311 vnses = self.get_table_from_store('vns-definition','tenant',tenant_name,"exact")
6312 except Exception:
6313 vnses = {}
6314 pass
6315 try:
6316 virtual_routers = self.get_table_from_store('virtualrouter','tenant', tenant_name, "exact")
6317 except Exception:
6318 virtual_routers = {}
6319 pass
6320
6321 for vns in vnses:
6322 vns_name=vns['vnsname']
6323 self.show_vns_definition_running_config(config, vns_name, tenant=tenant_name,indent=1)
6324 config += self.show_vns_running_config(vns_name, tenant=tenant_name,indent=1)
6325
6326 for virtual_router in virtual_routers:
6327 virtual_router_name=virtual_router['vrname']
6328 virtual_router_id=virtual_router['id']
6329 config.append(" router %s\n" % virtual_router_name)
6330 run_config.running_config_tenant_router_details(config,virtual_router,indent=1)
6331 try:
6332 vr_interfaces = self.get_table_from_store('virtualrouter-interface','virtual-router', virtual_router_id, "exact")
6333 except Exception:
6334 vr_interfaces = {}
6335 pass
6336 try:
6337 vr_routes = self.get_table_from_store('virtualrouter-routingrule','virtual-router', virtual_router_id, "exact")
6338 except Exception:
6339 vr_routes = {}
6340 pass
6341 try:
6342 vr_gwpools = self.get_table_from_store('virtualrouter-gwpool','virtual-router', virtual_router_id, "exact")
6343 except Exception:
6344 vr_gwpools = {}
6345 pass
6346
6347 for vr_interface in vr_interfaces:
6348 run_config.running_config_router_interface_details(config,vr_interface,indent=2)
6349 for vr_route in vr_routes:
6350 run_config.running_config_router_rule_details(config,vr_route,indent=2)
6351 for vr_gwpool in vr_gwpools:
6352 run_config.running_config_router_gwpool_details(config,vr_gwpool,indent=2)
6353
6354 #
6355 # --------------------------------------------------------------------------------
6356 # switch_port_match
6357 # Return True when the port matches a port specirfication "pattern"
6358 #
6359 # The port specification of an interface rule is a command separated list,
6360 # which can contain a tail of "-<d>". That tail describes a range.
6361 # With a port range, determine whether or not the 'port' parameter
6362 # is a match.
6363 #
6364 def switch_port_match(self, port, pattern):
6365 # XXX validate port is something which ends in a number?
6366 for field in pattern.split(','):
6367 m = re.match(r'^([A-Za-z0-9-]*?)(\d+)-(\d+)$', pattern)
6368 if not m:
6369 if field == port:
6370 return True
6371 continue
6372 prefix = m.group(1)
6373 startport = int(m.group(2))
6374 endport = int(m.group(3))
6375 if not port.startswith(prefix):
6376 continue
6377 tail = port[len(prefix):]
6378 if not self.DIGITS_RE.match(tail):
6379 print self.error_msg("port name tail %s must be all digits" % tail)
6380 continue
6381 m = re.search(r'(\d+)$', port)
6382 if not m:
6383 continue
6384 port = m.group(1)
6385 if int(port) >= int(startport) and int(port) <= int(endport):
6386 return True
6387 else:
6388 print self.error_msg("port %s out of port-spec range %s-%s" %
6389 (port, startport, endport))
6390 return False
6391
6392 #
6393 # ---------------------------------------------------------------------------
6394 # cp_show_event_history
6395 # Command completion for the following command to see
6396 # last <n> events of <name> events
6397 # Used in "show event-history <name> [ last <n> ]"
6398 #
6399 def cp_show_event_history(self, words, text, completion_char):
6400 options = ['attachment-point',
6401 'packet-in',
6402 'topology-link',
6403 'topology-switch',
6404 'topology-cluster',
6405 # Add more event history options above this line
6406 # Add the same item in do_show_event_history() below also
6407 ]
6408 completion_help = ''
6409 for op in options:
6410 ev_help = '%s show %s events\n' % (op, op)
6411 completion_help = completion_help + ev_help
6412
6413 # syntax: show event-history <name> [ last <n> ]
6414 if (len(words) == 1) and text=='':
6415 self.print_completion_help(completion_help)
6416 return options
6417 elif (len(words) == 1):
6418 return [op for op in options if op.startswith(text)]
6419 elif (len(words) == 2):
6420 if text == '':
6421 self.print_completion_help("last Show lastest <n> events\n" +
6422 "<cr> Enter")
6423 return ['last', '<cr>']
6424 elif "last".startswith(text):
6425 return ["last"]
6426 elif (len(words) == 3):
6427 self.print_completion_help("<number> Enter number of latest events to display (1-10000)")
6428 return range(0, 10000)
6429 else:
6430 self.print_completion_help('<cr>')
6431
6432 #
6433 # --------------------------------------------------------------------------
6434 # do_show_event_history
6435 #
6436 # Show the event history of the specified events
6437 # Syntax: show event-history <event-history-name> [last <count>]
6438 # <event-history-name> is words[0], <count> is words[2]
6439 #
6440 def do_show_event_history(self, words):
6441 self.debug_msg("do-show-event-history words: %s" % words)
6442
6443 # Check syntax of the command
6444 syntax = self.syntax_msg('show event-history <name> [ last <n> ]')
6445 options = ['attachment-point',
6446 'packet-in',
6447 'topology-link',
6448 'topology-switch',
6449 'topology-cluster',
6450 # Add more event history options above this line
6451 # Add the same item in do_cp_event_history() above also
6452 ]
6453 if (words[0] not in options):
6454 yield("Event history name %s not found" % words[0])
6455 return
6456 if (len(words) >= 3) and (words[1] != 'last'):
6457 yield(syntax)
6458 return
6459 if (len(words) == 2) or (len(words) > 4):
6460 yield(syntax)
6461 return
6462 if (len(words) >= 3) and (not words[2].isdigit()):
6463 yield(syntax)
6464 return
6465 if (len(words) >= 3) and (words[2].isdigit()):
6466 count = int(words[2])
6467 if (count < 1) or (count > 10000):
6468 yield("Number of events must be between 1 and 10000")
6469 return
6470
6471 last_n_count = 1024 # default
6472 if (len(words) >= 3):
6473 last_n_count = words[2]
6474 ev_hist_name = words[0]
6475 ev_hist_field_name = 'ev-hist-' + ev_hist_name
6476 url = 'http://%s/rest/v1/event-history/%s/%s' % \
6477 (self.controller, ev_hist_name, last_n_count)
6478 ev_hist = self.rest_simple_request_to_dict(url)
6479 #ev_hist - json.dumps(ev_hist)
6480 events = ev_hist['events']
6481 tableData = []
6482 for ev in events:
6483 info = ev['info']
6484 base_info = ev['base_info']
6485 info.update(base_info)
6486 tableData.append(info)
6487 # Prepare the date for CLI display
6488 tableData = self.pp.format_table(tableData, ev_hist_field_name)
6489 yield("\n")
6490 yield(tableData)
6491
6492 #
6493 # -------------------------------------------------------------------------------
6494 # cp_show_vns
6495 # Command completion for the following command
6496 # Used in "show vns { <vns-name> | all } flow [ brief | full-detail | detail | summary ]
6497 #
6498 def cp_show_vns_flow(self, words, text, completion_char):
6499 cmd1 = 'brief show flow information in brief format'
6500 cmd2 = 'detail show detailed flow information'
6501 cmd3 = 'full-detail show full details of flow information'
6502 cmd4 = 'summary show summarized flow information'
6503
6504 syntax = self.syntax_msg('show vns {<vns-name> | all} flow [ brief | detail | full-detail | summary ]')
6505 if (len(words) == 3) and text=='':
6506 self.print_completion_help(cmd1+'\n'+cmd2+'\n'+cmd3+'\n'+cmd4)
6507 return ['brief', 'detail', 'full-detail', 'summary', '<cr>' ]
6508 elif (len(words) == 3) and ('detail'.startswith(text)):
6509 return ['detail']
6510 elif (len(words) == 3) and ('full-detail'.startswith(text)):
6511 return ['full-detail']
6512 elif (len(words) == 3) and ('summary'.startswith(text)):
6513 return ['summary']
6514 elif (len(words) == 3) and ('brief'.startswith(text)):
6515 return ['brief']
6516 elif (len(words) == 4) and (words[3] not in ['brief', 'detail', 'full-detail', 'summary']):
6517 self.print_completion_help(syntax)
6518 elif (len(words) >= 3) and (text != ''):
6519 self.print_completion_help(syntax)
6520 else:
6521 self.print_completion_help('<cr>')
6522
6523 #
6524 # --------------------------------------------------------------------------------
6525 # show_vns_flow_annotated
6526 #
6527 # For a given vns-name, show all the active flows in that vns
6528 # or
6529 # for show active-flows in all vnses, categorized by vns
6530 # Used in "show vns { <vns-name> | all } flow [ brief | full-detail | detail | summary ]
6531 # flow records are annotated in the sdnplatform with vns name
6532 #
6533 def show_vns_flow_annotated(self, words):
6534
6535 syntax = self.syntax_msg('show vns {<vns-name> | all} flow [ brief | detail | full-detail |summary ]')
6536 if (len(words) == 3) and (words[2] not in ['brief', 'details', 'full-detail', 'summary']):
6537 print syntax
6538 return
6539
6540 if (len(words) > 3):
6541 print syntax
6542 return
6543
6544 # all = False is this used anywhere
6545
6546 vns = words[0]
6547
6548 option = 'none'
6549 if (len(words) == 3):
6550 option = words[2]
6551
6552 # Get all the flows in the network
6553 annotated_flow_data = {}
6554 url = 'http://%s/rest/v1/vns/realtimestats/flow/%s' % (self.controller, vns)
6555 annotated_flow_data = self.rest_simple_request_to_dict(url)
6556 #print "Data=%s" % json.dumps(annotated_flow_data, sort_keys=True, indent=4)
6557 # annotated_flow_data response is in the following format:
6558 # Data={
6559 # "vnsCount": 1,
6560 # "vnsFlowMap": {
6561 # "two": {
6562 # "flowCount": 10,
6563 # "flowList": [
6564 # {
6565 # "dpid": "00:00:00:00:00:00:00:0f",
6566 # "flowEntry": {
6567 # "actions": [
6568 # {
6569
6570 # vnsCount = annotated_flow_data["vnsCount"]
6571 vnsFlowMap = annotated_flow_data["vnsFlowMap"]
6572 vnsAddressSpaceMap = annotated_flow_data["vnsAddressSpaceMap"]
6573
6574 if (option == "brief"):
6575 table_field_ordering = "vns_flow"
6576 elif (option == "detail"):
6577 table_field_ordering = "details"
6578 elif (option == "full-detail"):
6579 table_field_ordering = "undefined" # prints everything!
6580 elif (option == "summary"):
6581 table_field_ordering = "summary"
6582 else:
6583 table_field_ordering = "default"
6584
6585 summaryTable = [] # used for holding the data for "summary" option
6586 for vnsName in vnsFlowMap:
6587 if (option == 'brief') or (option == "summary"):
6588 flow_tuple_list = []
6589 briefFlowCnt = 0
6590 # Table Data will hold all the flow entries, one list element per row of output
6591 # It is reinitilized for each VNS
6592 tableData = []
6593 vnsFlowCnt = vnsFlowMap[vnsName]["flowCount"]
6594 vnsFlowList = vnsFlowMap[vnsName]["flowList"]
6595 for vnsFlowEntry in vnsFlowList:
6596 flowEntry = vnsFlowEntry["flowEntry"]
6597 dpid = vnsFlowEntry["dpid"]
6598 if (option == "brief") or (option == "summary"):
6599 src_mac = flowEntry['match']['dataLayerDestination']
6600 dst_mac = flowEntry['match']['dataLayerSource']
6601 vlan = flowEntry['match']['dataLayerVirtualLan']
6602 etherType = flowEntry['match']['dataLayerType']
6603 flowList = []
6604 flowList.append(src_mac)
6605 flowList.append(dst_mac)
6606 flowList.append(vlan)
6607 flowList.append(etherType)
6608 if flowList in flow_tuple_list:
6609 # duplicate flow (due to same flow entry on multiple switches along the path,
6610 # skip it if the option if brief or summary
6611 continue
6612 else:
6613 flow_tuple_list.append(flowList)
6614 briefFlowCnt += 1
6615 #print "DPID = %s" % dpid
6616 #print " New Data = %s" % json.dumps(flowEntry, indent=4)
6617 if (option != "summary"):
6618 tableEntry = {}
6619 tableEntry[dpid] = []
6620 tableEntry[dpid].append(flowEntry)
6621 tableEntry = self.add_dpid_to_data(tableEntry)
6622 tableEntry = self.fix_realtime_flows(tableEntry)
6623 #print "Table Entry : %s" % json.dumps(tableEntry, indent=4)
6624 tableData.append(tableEntry[0])
6625 #print "*** Table Data: %s" % json.dumps(tableData[0], indent=4)
6626 #printf tenant + vnsname
6627 names=vnsName.split('|')
6628 tenant=names[0]
6629 vns=names[1]
6630 if (option == "brief"):
6631 yield("\nTenant %s VNS %s (address-space %s) flows (count: %s)" % (tenant, vns, vnsAddressSpaceMap[vnsName], briefFlowCnt))
6632 elif (option != "summary"):
6633 yield("\nTenant %s VNS %s (address-space %s) flow entries (count: %s)" % (tenant, vns, vnsAddressSpaceMap[vnsName], vnsFlowCnt))
6634 # Print flow data for one vns, unless option is summary
6635 if (option == "summary"):
6636 summaryData = dict()
6637# summaryData['tenant'] =tenant
6638 summaryData["vnsName"] = vns
6639 summaryData["flowEntryCnt"] = vnsFlowCnt
6640 summaryData["vnsFlowCnt"] = briefFlowCnt
6641 summaryTable.append(summaryData)
6642 else:
6643 yield("\n")
6644 # print "*** TD = %s" % tableData
6645 tableData = self.pp.format_table(tableData, "realtime_flow", table_field_ordering)
6646 yield(tableData)
6647
6648 if (option == "summary"):
6649 # print "*** ST %s" % summaryTable
6650 yield("\n")
6651 summaryTable = self.pp.format_table(summaryTable, "realtime_flow", table_field_ordering)
6652 yield(summaryTable)
6653 print(" ")
6654 return
6655
6656 #
6657 # --------------------------------------------------------------------------------
6658 # show_vns_switch_ports
6659 # For some vns-name, collect all the interfaces, and from that,
6660 # collect all the attachment points. The objective here is to
6661 # name all the switch's and ports associated with a vns
6662 #
6663 def show_vns_switch_ports(self, words):
6664 if len(words) < 1:
6665 return
6666
6667 # words: [ vns_name, 'switch', switch_name ]
6668 vns_name = words[0]
6669 switch = None
6670 if len(words) > 2:
6671 switch = convert_alias_to_object_key('switch', words[2])
6672 port = None
6673 if len(words) > 3:
6674 port = words[3]
6675
6676 vns_interface_key = mi.pk('vns-interface')
6677 if vns_name == 'all':
6678 vns_ifs = create_obj_type_dict('vns-interface',
6679 vns_interface_key,
6680 'id')
6681 vns_rules = create_obj_type_dict('vns-interface-rule',
6682 mi.pk('vns-interface-rule'),
6683 'id',)
6684 else:
6685 vns_ifs = create_obj_type_dict('vns-interface',
6686 vns_interface_key,
6687 'vns',
6688 vns_name)
6689 vns_rules = create_obj_type_dict('vns-interface-rule',
6690 mi.pk('vns-interface-rule'),
6691 'vns',
6692 vns_name)
6693 host_dap_dict = self.create_attachment_dict()
6694
6695 bsp = {} # _b_vs _s_witch _p_ort
6696 for ifs in vns_ifs:
6697 fields = ifs.split('|')
6698 # all these fields must have two parts.
6699 if len(fields) != 3:
6700 continue
6701 # choose between switch based rules and mac/ip based rules
6702 rule = 'default'
6703 vns_name = vns_ifs[ifs][0]['vns']
6704 switch_rule = None
6705 if fields[1] != 'default':
6706 rule = vns_ifs[ifs][0]['rule']
6707 if not rule in vns_rules:
6708 continue
6709 if_rule = vns_rules[rule][0]
6710 if 'switch' in if_rule:
6711 switch_rule = if_rule['switch']
6712 else:
6713 if_rule = { 'mac' : 'defaultLie' } # for default rules
6714
6715 # There's two major classes of rules: host and switch
6716 # Use existance of a 'switch' to differentiate between the two.
6717 if switch_rule:
6718 # if_rule/switch are both set, part after the '/' is the port
6719 if fields[2].find('/') < 0:
6720 # skip Eth<n> general interface
6721 continue
6722 #
6723 # Prefix this port with a plus sign, which will be recognized
6724 # in the printing code as already describing an openflow port name
6725 port = '+' + fields[2].split('/')[1]
6726 tenant_vns=vns_name.split('|')
6727 tenant_name=tenant_vns[0]
6728 vns_real_name=tenant_vns[1]
6729 key = "%s|%s|%s|%s" % (tenant_name,vns_real_name, switch, port)
6730
6731 if not key in bsp:
6732 bsp[key] = {
6733 'tenant' : tenant_name,
6734 'vns' : vns_real_name,
6735 'switch' : switch_rule,
6736 'port' : port,
6737 'reason' : ["Rule:%s" % rule.split('|')[-1], fields[1]],
6738 }
6739 else:
6740 pass # should only be one.
6741 else:
6742 # the second part (fields[1]) is the interface-long name,
6743 items = fields[2].split('/')
6744 if len(items) == 1:
6745 if items[0].startswith('VEth'):
6746 if not 'mac' in rule:
6747 continue
6748 mac = if_rule['mac']
6749 elif items[0].startswith('Eth'):
6750 self.debug_msg('should have been a switch in if_rule')
6751 continue
6752 else:
6753 self.debug_msg('item length of 1')
6754 continue
6755 else:
6756 mac = items[1]
6757 if not re.match(r'^(([A-Fa-f\d]){2}:?){5}[A-Fa-f\d]{2}$', mac):
6758 print 'Not a mac address: %s' % mac
6759 # currently just use the mac to find the attachment points.
6760 if mac in host_dap_dict:
6761 for attachment_point in host_dap_dict[mac]:
6762 # if switch is named, skip any which don't match
6763 if switch and attachment_point['switch'] != switch:
6764 continue
6765 if port and attachment_point['ingress-port'] != port:
6766 continue
6767
6768 tenant_vns=vns_name.split('|')
6769 tenant_name=tenant_vns[0]
6770 vns_real_name=tenant_vns[1]
6771 key = "%s|%s|%s|%s" % (tenant_name,
6772 vns_real_name,
6773 attachment_point['switch'],
6774 attachment_point['ingress-port'])
6775 if not key in bsp:
6776 bsp[key] = {
6777 'tenant' : tenant_name,
6778 'vns' : vns_real_name,
6779 'switch' : attachment_point['switch'],
6780 'port' : utif.try_int(attachment_point['ingress-port']),
6781 'reason' : ["Rule:%s" % rule.split('|')[-1], mac]
6782 }
6783 else:
6784 self.append_when_missing(bsp[key]['reason'],
6785 "Rule:%s" % rule.split('|')[-1])
6786 self.append_when_missing(bsp[key]['reason'], mac)
6787
6788 sort = [bsp[x] for x in sorted(bsp.keys(),
6789 cmp=lambda a,b: cmp(utif.try_int(a.split('|')[2]),
6790 utif.try_int(b.split('|')[2]))
6791 if a.split('|')[1] == b.split('|')[1]
6792 else cmp(b.split('|')[1],a.split('|')[1]))]
6793
6794 return self.display_obj_type_rows('vns-switch-ports', sort)
6795
6796
6797 #
6798 # --------------------------------------------------------------------------------
6799 # show_switch_ports_vns
6800 #
6801 def show_switch_ports_vns(self, words):
6802 switch = None
6803 if len(words) > 1 and words[0] != 'all':
6804 switch = words[0]
6805 #
6806 # switch alias conversion
6807 value = alias_lookup('switch-alias', switch)
6808 if value:
6809 switch = value
6810
6811 #
6812 # dictionary from the vns side, indexed by host.
6813 vns_ifs_dict = create_obj_type_dict('vns-interface',
6814 mi.pk('vns-interface'))
6815 #
6816 # dictionary of the interface-rules, by rule
6817 vns_rules = create_obj_type_dict('vns-interface-rule',
6818 mi.pk('vns-interface-rule'))
6819 #
6820 # from the list of hosts, identify the attachment points
6821 host_dap_dict = self.create_attachment_dict()
6822
6823 #
6824 # there can be multiple attachment points for each of the
6825 # hosts. iterate over the hosts, find all the attachment points,
6826 # manage an association for the entries
6827 spb = {} # _s_witch _p_ort _b_vs
6828 for ifs in vns_ifs_dict:
6829 fields = vns_ifs_dict[ifs][0]['id'].split('|')
6830 # all these fields must have two parts.
6831 if len(fields) != 2:
6832 continue
6833 # id parts are vns|interface
6834 vns_name = fields[0]
6835
6836 # choose between switch based rules and mac/ip based rules
6837 rule = 'default'
6838
6839 switch_rule = None
6840 mac_rule = None
6841 rule_fields = [rule]
6842 if vns_name == 'default':
6843 if fields[1].find('/') >= 0:
6844 mac_rule = fields[1].split('/')[1]
6845 else:
6846 if not 'rule' in vns_ifs_dict[ifs][0]:
6847 continue
6848 rule = vns_ifs_dict[ifs][0]['rule']
6849 rule_fields = rule.split('|')
6850 if_rule = vns_rules[rule][0]
6851 if 'switch' in if_rule:
6852 if switch and if_rule['switch'] != switch:
6853 continue
6854 switch_rule = if_rule['switch'] # switch may be None.
6855 elif 'mac' in if_rule:
6856 mac_rule = if_rule['mac']
6857 elif 'ip-subnet' in if_rule:
6858 mac_rule = if_rule['ip-subnet']
6859 elif 'tags' in if_rule:
6860 mac_rule = if_rule['tags']
6861 elif 'vlans' in if_rule:
6862 mac_rule = if_rule['vlans']
6863 if mac_rule:
6864 if not mac_rule in host_dap_dict:
6865 self.debug_msg("Unknown attachment point for %s" % mac_rule)
6866 continue
6867
6868 for attachment_point in host_dap_dict[mac_rule]:
6869 key = "%s|%s" % (attachment_point['switch'],
6870 attachment_point['ingress-port'])
6871 if switch and attachment_point['switch'] != switch:
6872 continue
6873
6874 if not key in spb:
6875 spb[key] = {
6876 'switch' : attachment_point['switch'],
6877 'port' : utif.try_int(attachment_point['ingress-port']),
6878 'vns' : {vns_name : 1},
6879 'reason' : ["Rule:%s" % rule_fields[-1], mac_rule]
6880 }
6881 else:
6882 if vns_name in spb[key]['vns']:
6883 spb[key]['vns'][vns_name] += 1
6884 else:
6885 spb[key]['vns'][vns_name] = 1
6886 self.append_when_missing(spb[key]['reason'],
6887 "Rule:%s" % rule_fields[-1])
6888 self.append_when_missing(spb[key]['reason'], mac_rule)
6889 if switch_rule:
6890 if fields[1].find('/') >= 0:
6891 #
6892 # Prefix this port with a plus sign, which will be recognized
6893 # in the printing code as already describing an openflow port name
6894 port = '+' + fields[1].split('/')[1]
6895 key = "%s|%s" % (switch_rule, port)
6896
6897 if not key in spb:
6898 spb[key] = {
6899 'switch' : switch_rule,
6900 'port' : port,
6901 'vns' : {vns_name : 1},
6902 'reason' : ["Rule:%s" % rule_fields[-1], fields[1]]
6903 }
6904
6905 sort = [spb[x] for x in sorted(spb.keys(),
6906 cmp=lambda a,b: cmp(utif.try_int(a.split('|')[1]),
6907 utif.try_int(b.split('|')[1]))
6908 if a.split('|')[0] == b.split('|')[0]
6909 else cmp(b.split('|')[0],a.split('|')[0]))]
6910
6911 return self.display_obj_type_rows('switch-ports-vns', sort)
6912 # [spb[x] for x in sorted(spb.keys())])
6913
6914 #
6915 # --------------------------------------------------------------------------------
6916 # do_show_vns
6917 #
6918 def do_show_vns(self, words):
6919 choices = ['interface', 'mac-address-table',
6920 'interface-rules', 'access-lists', 'running-config', 'switch', 'flow' ]
6921 if words == None or len(words) > 2:
6922 if words[1] not in ['switch', 'flow']:
6923 return self.syntax_msg('show vns <vns-name> [ %s ] ' %
6924 ' | '.join(choices))
6925 if len(words) in [3, 4]:
6926 if words[1] == 'switch':
6927 return self.show_vns_switch_ports(words)
6928 elif words[1] == 'flow':
6929 return self.show_vns_flow_annotated(words)
6930 else:
6931 return self.syntax_msg('show vns <vns-name> [ %s ] ' %
6932 ' | '.join(choices))
6933 elif len(words) == 2:
6934 # words[0] ought to an existing vns
6935 # Allow show vns all flow [detail]
6936 if (not self.vns_find_name(words[0])) and (words[1] != 'flow'):
6937 return self.error_msg("show vns '%s': vns Not Found" % words[0])
6938
6939 vns_key = self.prefix_search_key([words[0]])
6940 selection = utif.full_word_from_choices(words[1], choices)
6941 if not selection:
6942 return self.syntax_msg('show vns <vns-name> [ %s ' %
6943 ' | '.join(choices))
6944 if selection == 'interface':
6945 return self.find_and_display_vns_interface(words[0], words[2:])
6946 elif selection == 'mac-address-table':
6947 # the id for the host-vns-interface table has the host as the 'prefix',
6948 # preventing searching based on the prefix. the vns is not even a foreign
6949 # key, which means the complete table must be read, then the vns association
6950 # must be determined, and then matched
6951 return self.display_vns_mac_address_table(words[0], words[2:])
6952 elif selection == 'interface-rules':
6953 return self.do_show_object(['vns-interface-rule', vns_key], "<no_key>")
6954 elif selection == 'access-lists':
6955 return self.do_show_object(['vns-access-list-entry', vns_key], "<no_key>")
6956 elif selection == 'running-config':
6957 return self.show_vns_running_config(words[0])
6958 elif selection == 'switch':
6959 return self.show_vns_switch_ports(words)
6960 elif selection == 'flow':
6961 return self.show_vns_flow_annotated(words)
6962 elif len(words) == 1:
6963 return self.do_show_object(["vns-definition", words[0]])
6964 else:
6965 return self.do_show_object(["vns-definition"])
6966
6967 #
6968 # --------------------------------------------------------------------------------
6969 # do_show_vns_interface_rule
6970 # do_show_object is used to construct the output for the table search.
6971 # However, since a vns prefix key search is used to display the table,
6972 # and since do_show_object behaves differently when displaying tables
6973 # depending on whether the table is a key search or not, the 'with_search_key'
6974 # parameter is set to "with_key" when the caller included an additional
6975 # prefix for (key) for the rule, and otherwise '<no_key>' is used when
6976 # the caller wants the complete interface-rule table for a single vns
6977 # (which obvioulsy also implies a key search for 'vns|' prefied rules.
6978 #
6979 def do_show_vns_interface_rule(self, words):
6980 with_search_key = "<no_key>"
6981 if self.vns_name() is None:
6982 if len(words) > 0:
6983 search_object = ["vns-interface-rule", words[0]]
6984 else:
6985 search_object = ["vns-interface-rule"]
6986 elif len(words) > 0:
6987 with_search_key = '-'.join(words)
6988 words.insert(0, self.vns_name())
6989 search_object = ["vns-interface-rule",
6990 self.unique_key_from_non_unique(words)]
6991 else:
6992 search_object = ["vns-interface-rule",
6993 self.prefix_search_key([self.vns_name()]) ]
6994 obj_type = 'vns-interface-rule'
6995
6996 # --- this ought to be promoted to be used more ..
6997 s_dict = {}
6998 # for all foreign keys in this obj_type
6999 mode_dict = self.mode_stack_to_rest_dict({})
7000
7001 # for fk in mi.obj_type_foreign_keys(obj_type):
7002 for kf in mi.compound_key_fields(obj_type, mi.pk(obj_type)):
7003 if mi.is_foreign_key(obj_type, kf):
7004 (ref_ot, ref_fn) = mi.foreign_key_references(obj_type, kf)
7005 if ref_ot in mode_dict:
7006 s_dict[kf] = mode_dict[ref_ot]
7007
7008 # for (n,v) in self.mode_stack_to_rest_dict({}).items():
7009 # for (fk_ot, fk_fn) in mi.foreign_key_xref[n][mi.pk(n)]:
7010 # print n, fk_ot, fk_fn
7011 # if fk_ot == obj_type:
7012 # s_dict[fk_fn] = v
7013 entries = self.store.rest_query_objects(obj_type, s_dict)
7014 return self.display_obj_type_rows(obj_type, entries, with_search_key)
7015 return self.do_show_object(search_object, with_search_key)
7016
7017 #
7018 # --------------------------------------------------------------------------------
7019 # cp_is_alias
7020 # Used in cp_no to determine if the particular request is an alias completion
7021 #
7022 def cp_is_alias(self, words, text):
7023 if not self.in_config_submode() or len(words) != 2:
7024 return False
7025 obj_type = self.get_current_mode_obj_type()
7026 # LOOK! robv: Tweaked this from the commented out version below
7027 return obj_type in mi.alias_obj_type_xref
7028
7029
7030 #
7031 # --------------------------------------------------------------------------------
7032 # cascade_delete
7033 # Delete interior objects' rows related via foreign keys
7034 #
7035 def cascade_delete(self, obj_type, id_value):
7036 global modi
7037
7038 obj_key = mi.pk(obj_type)
7039 if obj_type in mi.foreign_key_xref and \
7040 obj_key in mi.foreign_key_xref[obj_type]:
7041 for (fk_obj_type, fk_name) in mi.foreign_key_xref[obj_type][obj_key]:
7042 if mi.is_cascade_delete_enabled(fk_obj_type):
7043 #
7044 # find any affected rows
7045 try:
7046 rows = self.get_table_from_store(fk_obj_type,
7047 fk_name,
7048 id_value,
7049 "exact")
7050 except Exception, e:
7051 if self.debug or self.debug_backtrace:
7052 errors = self.rest_error_to_dict(e, fk_obj_type)
7053 print self.rest_error_dict_to_message(errors)
7054 rows = []
7055
7056 # determine whether the foreign key can have a null
7057 # value, in which case the row doesn't have to be deleted,
7058 # just updated the foreign key to `None'.
7059 if mi.is_force_delete_enabled(fk_obj_type):
7060 delete = True
7061 elif mi.is_null_allowed(fk_obj_type, fk_name):
7062 delete = False
7063 else:
7064 delete = True
7065
7066 key = mi.pk(fk_obj_type)
7067 for row in rows:
7068 try:
7069 if delete:
7070 self.rest_delete_object(fk_obj_type, row[key])
7071 else: # update
7072 self.rest_update_object(fk_obj_type, key, row[key],
7073 { fk_name : None } )
7074 errors = None
7075 except Exception, e:
7076 errors = self.rest_error_to_dict(e,
7077 fk_obj_type + " "
7078 + row[key])
7079 if errors:
7080 return self.rest_error_dict_to_message(errors)
7081 self.debug_msg("cascade delete: %s: %s" % \
7082 (fk_obj_type, ', '.join(row[key].split('|'))))
7083 if delete:
7084 self.cascade_delete(fk_obj_type, row[key])
7085
7086 #
7087 # --------------------------------------------------------------------------------
7088 # do_no
7089 # Delete rows from a table, or update a field. For fields which
7090 # have defaults, 'no' referts the field to its default value.
7091 #
7092 def do_no(self, words):
7093 # format is no < obj_type > < id >
7094 if not self.in_config_mode():
7095 if len(words) < 1 or words[0] != 'debug':
7096 return self.error_msg("'no' command only valid in config mode")
7097 obj_type = self.get_current_mode_obj_type()
7098 option = words[0]
7099 #
7100 # manage deletion of complete rows in obj_types'
7101 #
7102 if option in self.obj_types_for_config_mode_starting_with():
7103 if not len(words) > 1:
7104 return self.error_msg("<id> must be specified for the object to delete")
7105 item = convert_alias_to_object_key(option, words[1])
7106
7107 try:
7108 self.rest_delete_object(option, item)
7109 errors = None
7110 except Exception, e:
7111 errors = self.rest_error_to_dict(e, option + " " + item)
7112 #
7113 # cascade delete?
7114 if not errors:
7115 self.cascade_delete(option, item)
7116 #
7117 # manage removal/set-to-default of values in fields of an obj_type
7118 #
7119 elif option in self.fields_for_current_submode_starting_with(option):
7120 #
7121 # replace the obj_type with the alias obj_type when for
7122 # fields called 'alias' when the obj_type has one alias
7123 if obj_type in mi.alias_obj_type_xref:
7124 aliases = mi.alias_obj_type_xref[obj_type]
7125 if option in aliases:
7126 obj_type = option
7127 if len(aliases) == 1 and option == 'alias':
7128 obj_type = aliases[0]
7129 if len(aliases) != 1:
7130 print self.error_msg("Internal 'no' more than one alias choice")
7131 if len(words) < 2:
7132 return "Syntax: no %s <value>" % option
7133 item = words[1]
7134 try:
7135 self.rest_delete_object(obj_type, item)
7136 errors = None
7137 except Exception, e:
7138 errors = self.rest_error_to_dict(e, obj_type + " " + item)
7139 return self.rest_error_dict_to_message(errors)
7140
7141 key = mi.pk(obj_type)
7142 #
7143 # note: field_default_value returns None when no default is
7144 # provided. its not clear whether default values are always
7145 # provided for fields which don't accept null values.
7146 #
7147 default_value = mi.field_default_value(obj_type, option)
7148
7149 if mi.is_null_allowed(obj_type, option) and default_value:
7150 self.warning("'%s' accepts null and had a default "
7151 "value; %s is set to the default value '%s" %
7152 (obj_type, option, default_value))
7153
7154 errors = self.rest_update_object(obj_type,
7155 key,
7156 self.get_current_mode_obj(),
7157 {option:default_value})
7158 # fall through to return
7159 elif self.in_config_vns_acl_mode() and self.ACL_RE.match(option):
7160 return self.do_vns_no(['access-list-entry'] + words)
7161 #elif self.in_config_controller_interface_mode() and 'firewall'.startswith(option):
7162 # return self.do_no_firewall(words)
7163 else:
7164 try:
7165 command_words = ['no'] + words
7166 # Just try to execute the command. It will either succeed or
7167 # throw an exception
7168 return command.do_command(command_words)
7169 except urllib2.HTTPError, e:
7170 raise e
7171 except Exception, e:
7172 if self.debug or self.debug_backtrace:
7173 traceback.print_exc()
7174 return self.error_msg("'%s' must be either a valid object type or a field" %
7175 option)
7176
7177 # when errors == None, rest_error_dict_to_message returns None.
7178 return self.rest_error_dict_to_message(errors)
7179
7180
7181 #
7182 # --------------------------------------------------------------------------------
7183 # implement_ping
7184 # Performs a traceroute to a switch or host.
7185 # Input can either be a DPID, switch alias, or host (IP or domain).
7186 def implement_ping(self, data):
7187
7188 count = '-c %d ' % data.get('count', 5)
7189 if not 'ip-address' in data:
7190 yield('Can\'t determine ping target')
7191 return
7192
7193 ip_host = data['ip-address']
7194 if self.DPID_RE.match(ip_host):
7195 ip_host = self.get_ip_from_switch_dpid_or_alias(ip_host)
7196
7197 cmd = 'ping %s%s' % (count, self.shell_escape(ip_host))
7198 for item in self.generate_subprocess_output(cmd):
7199 yield item
7200
7201
7202 #
7203 # --------------------------------------------------------------------------------
7204 # implement_traceroute
7205 # Performs a traceroute to a switch or host.
7206 # Input can either be a DPID, switch alias, or host (IP or domain).
7207 def implement_traceroute(self, data):
7208
7209 if not 'ip-address' in data:
7210 yield('Can\'t determine traceroute target')
7211 return
7212
7213 ip_host = data['ip-address']
7214 if self.DPID_RE.match(ip_host):
7215 ip_host = self.get_ip_from_switch_dpid_or_alias(ip_host)
7216
7217 cmd = 'traceroute %s' % self.shell_escape(ip_host)
7218 for item in self.generate_subprocess_output(cmd):
7219 yield item
7220
7221
7222 #
7223 # --------------------------------------------------------------------------------
7224 # do_no_firewall
7225 #
7226 #def do_no_firewall(self, words):
7227 # return self.do_firewall(words[1:], True)
7228
7229
7230 #
7231 # --------------------------------------------------------------------------------
7232 # firewall_rule_add_rule_to_entries
7233 #
7234 def firewall_rule_add_rule_to_entries(self, entries):
7235 for entry in entries:
7236 entry['rule'] = run_config.firewall_rule(entry)
7237 return entries
7238
7239
7240 #
7241 # --------------------------------------------------------------------------------
7242 # get_firewall_rules
7243 # Returns a list of strings describing the firewall rules for a particular
7244 # controller-node name.
7245 #
7246 def get_firewall_rules(self, node):
7247 key = mi.pk('firewall-rule')
7248 key_value = self.unique_key_from_non_unique([node])
7249 entries = self.get_table_from_store('firewall-rule', key, key_value)
7250
7251 return self.firewall_rule_add_rule_to_entries(entries)
7252
7253 #
7254 # --------------------------------------------------------------------------------
7255 # do_show_firewall
7256 # Depends on get_firewall_rules to add 'rule' to entries so that
7257 # the 'firewall-rule' table can display the rule's value in a
7258 # easily identified syntax.
7259 #
7260 def do_show_firewall(self, words):
7261 return self.display_obj_type_rows('firewall-rule',
7262 self.get_firewall_rules(self.get_current_mode_obj()))
7263
7264 # --------------------------------------------------------------------------------
7265 # handle_command
7266 #
7267 def handle_command(self, command_word, words):
7268 if type(command_word) == str:
7269 method = self.command_method_from_name(command_word)
7270 if method:
7271 return method(words)
7272 # XXX It would be better to only call do_command if it
7273 # was clear that this command actually existed.
7274 return command.do_command([command_word] + words)
7275
7276
7277 #
7278 # --------------------------------------------------------------------------------
7279 # find_with_quoting
7280 #
7281 # Assumes start_index is not inside a quoted string.
7282 #
7283 @staticmethod
7284 def find_with_quoting(line, find_char, reverse=False, start_index=0):
7285 in_quoted_arg = False
7286 line_length = len(line)
7287 i = start_index
7288 found_index = -1;
7289 while i < line_length:
7290 c = line[i]
7291 if c in "\"'":
7292 if not in_quoted_arg:
7293 quote_char = c
7294 in_quoted_arg = True
7295 elif c == quote_char:
7296 in_quoted_arg = False
7297 # otherwise leave in_quoted_arg True
7298 elif c == "\\" and in_quoted_arg:
7299 i += 1
7300 elif (c == find_char) and not in_quoted_arg:
7301 found_index = i
7302 if not reverse:
7303 break
7304 i += 1
7305
7306 return found_index
7307
7308 #
7309 # --------------------------------------------------------------------------------
7310 # split_with_quoting
7311 #
7312 @staticmethod
7313 def split_with_quoting(line, separators=" \t"):
7314 word_list = []
7315 current_word = ""
7316 in_quoted_arg = False
7317 line_length = len(line)
7318 i = 0
7319 while i < line_length:
7320 c = line[i]
7321 i += 1
7322 if c in "\"'":
7323 if not in_quoted_arg:
7324 in_quoted_arg = True
7325 quote_char = c
7326 elif c == quote_char:
7327 in_quoted_arg = False
7328 word_list.append(current_word)
7329 current_word = ""
7330 else:
7331 current_word += c
7332 elif c == "\\" and in_quoted_arg:
7333 if i < line_length:
7334 c = line[i]
7335 current_word += c
7336 i += 1
7337 elif (c in separators) and not in_quoted_arg:
7338 if current_word:
7339 word_list.append(current_word)
7340 current_word = ""
7341 else:
7342 current_word += c
7343
7344 if current_word:
7345 word_list.append(current_word)
7346
7347 return word_list
7348
7349 #
7350 # --------------------------------------------------------------------------------
7351 # quote_item
7352 # Some of the new model columns available as choices to select have the '|'
7353 # character as a separator. For these choices to word, they need to be
7354 # quoted
7355 #
7356 @staticmethod
7357 def quote_item(obj_type, item):
7358 if item.find("|") >= 0:
7359 return '"' + str(item) + '"'
7360 else:
7361 return str(item)
7362
7363 #
7364 # --------------------------------------------------------------------------------
7365 #
7366 def replay(self, file, verbose = True, command_replay = False):
7367 # Only replay the STR values, since the code ought to
7368 # stuff back the JSON values.
7369 play = open(file)
7370 rest_line_format = re.compile(r'^REST ([^ ]*)( *)([^ ]*)( *)(.*)$')
7371 cmd_line_format = re.compile(r'^COMMAND (.*)$')
7372 skip_command = True
7373 for line in play.read().split('\n'):
7374 # the format ought to be url<space>[STR|JSON]<space> ...
7375 match = rest_line_format.match(line)
7376 if match:
7377 if match.group(3) == 'STR':
7378 if verbose:
7379 print 'REST STR', match.group(1)
7380 url_cache.save_url(match.group(1), match.group(5), 1000000)
7381 elif match.group(3) == 'JSON':
7382 if verbose:
7383 print 'REST JSON', match.group(1)
7384 entries = json.loads(match.group(5))
7385 url_cache.save_url(match.group(1), entries, 1000000)
7386 else:
7387 print 'REST REPLAY NOT STR|JSON'
7388 elif len(line):
7389 match = cmd_line_format.match(line)
7390 if command_replay and match:
7391 # skip the first command since it ought to be the replay enablement
7392 if skip_command:
7393 if verbose:
7394 print 'SKIP COMMAND %s' % match.group(1)
7395 skip_command = False
7396 else:
7397 line = self.split_with_quoting(match.group(1))
7398 if verbose:
7399 print 'COMMAND %s' % line
7400 output = self.handle_multipart_line(line[0])
7401 if output != None:
7402 print output
7403 else:
7404 print 'no MATCH +%s+' % line
7405 play.close()
7406
7407 #
7408 # --------------------------------------------------------------------------------
7409 # handle_single_line
7410 #
7411 def handle_single_line(self, line):
7412 ret_val = None
7413 if len(line) > 0 and line[0]=="!": # skip comments
7414 return
7415 words = self.split_with_quoting(line)
7416 if not words:
7417 return
7418 #
7419 self.completion_reset()
7420
7421 # Look for the replay keyword, use the first two tokens if the replay
7422 # keyword is in the first part of the command.
7423 if self.debug and len(words) >= 2:
7424 if words[0] == 'replay':
7425 # replay the file, remove the first two keywords
7426 self.replay(words[1], command_replay = len(words) == 2)
7427 if len(words) == 2:
7428 return
7429 words = words[2:]
7430
7431 # the first word of a line is either:
7432 # - a command - dependent on mode (show anywhere but configure only in enable)
7433 # - an object type - if we're in a config mode (either config or config submode)
7434 # - a field for an object - if we're in a config submode
7435 matches = [(x, "command") for x in self.commands_for_current_mode_starting_with(words[0])]
7436 matches.extend([(x, "config object") for x in self.obj_types_for_config_mode_starting_with(words[0])])
7437 matches.extend([(x, "field") for x in self.fields_for_current_submode_starting_with(words[0])])
7438 # LOOK!: robv Fix to work with field names where one name is a prefix of another
7439 if len(matches) > 1:
7440 for match_tuple in matches:
7441 if match_tuple[0] == words[0]:
7442 matches = [match_tuple]
7443 break
7444 if len(matches) == 1:
7445 match = matches[0]
7446 # Replace the (possibly) abbreviated argument with the full name.
7447 # This is so that the handlers don't need to all handle abbreviations.
7448 if type(match[0]) == str:
7449 words[0] = match[0]
7450
7451 if match[1] == "field":
7452 ret_val = self.handle_field(words)
7453 elif match[1] == "config object":
7454 ret_val = self.handle_obj_type(words)
7455 else:
7456 ret_val = self.handle_command(words[0], words[1:])
7457 #ret_val = self.handle_command(match[0], words[1:])
7458 elif len(matches) > 1:
7459 ret_val = self.error_msg("%s is ambiguous\n" % words[0])
7460 for m in matches:
7461 ret_val += "%s (%s)\n" % m
7462 else:
7463 ret_val = self.error_msg("Unknown command: %s\n" % words[0])
7464
7465 url_cache.command_finished(words)
7466 return ret_val
7467
7468 #
7469 # --------------------------------------------------------------------------------
7470 # generate_pipe_output
7471 #
7472 def generate_pipe_output(self, p, output):
7473 fl = fcntl.fcntl(p.stdout, fcntl.F_GETFL)
7474 fcntl.fcntl(p.stdout, fcntl.F_SETFL, fl | os.O_NONBLOCK)
7475
7476 for item in output:
7477 try:
7478 p.stdin.write(item)
7479 except IOError:
7480 break
7481
7482 try:
7483 out_item = p.stdout.read()
7484 yield out_item
7485 except IOError:
7486 pass
7487
7488 p.stdin.close()
7489
7490 fcntl.fcntl(p.stdout, fcntl.F_SETFL, fl)
7491 while True:
7492 out_item = p.stdout.read()
7493 if (out_item):
7494 yield out_item
7495 else:
7496 p.stdout.close()
7497 break
7498 p.wait()
7499
7500 #
7501 # --------------------------------------------------------------------------------
7502 # write_to_pipe
7503 #
7504 def write_to_pipe(self, p, output):
7505 for item in output:
7506 try:
7507 p.stdin.write(item)
7508 except IOError:
7509 break
7510 p.stdin.close()
7511 p.wait()
7512
7513 #
7514 # --------------------------------------------------------------------------------
7515 # shell_escape
7516 # Return a string, quoting the complete string, and correctly prefix any
7517 # quotes within the string.
7518 #
7519 def shell_escape(self, arg):
7520 return "'" + arg.replace("'", "'\\''") + "'"
7521
7522 #
7523 # --------------------------------------------------------------------------------
7524 # handle_pipe_and_redirect
7525 #
7526 def handle_pipe_and_redirect(self, pipe_cmds, redirect_target, output):
7527 # if redirect target is tftp/ftp/http/file, then we should actually stick
7528 # curl at the end of the pipe_cmds so it gets handled below
7529 if redirect_target:
7530 if redirect_target.startswith("tftp") or redirect_target.startswith("ftp") or \
7531 redirect_target.startswith("http") or redirect_target.startswith("file"):
7532 redirect_target = self.shell_escape(redirect_target)
7533 # add so it can be used below
7534 if pipe_cmds == None:
7535 pipe_cmds = ""
7536 else:
7537 pipe_cmds += " | "
7538
7539 if redirect_target.startswith("ftp"): # shell_escape added quote
7540 pipe_cmds += " curl -T - %s" % self.shell_escape(redirect_target)
7541 else:
7542 pipe_cmds += " curl -X PUT -d @- %s" % self.shell_escape(redirect_target)
7543
7544 if pipe_cmds:
7545 new_pipe_cmd_list = []
7546 for pipe_cmd in [x.strip() for x in pipe_cmds.split('|')]:
7547 # doing it this way let us handles spaces in the patterns
7548 # as opposed to using split/join which would compress space
7549 new_pipe_cmd = pipe_cmd
7550 m = re.search('^(\w+)(.*)$', pipe_cmd)
7551 if m:
7552 first_tok = m.group(1)
7553 rest_of_cmd = m.group(2).strip()
7554 if first_tok.startswith("in"):
7555 new_pipe_cmd = "grep -e " + rest_of_cmd
7556 elif first_tok.startswith("ex"):
7557 new_pipe_cmd = "grep -v -e" + rest_of_cmd
7558 elif first_tok.startswith("begin"):
7559 new_pipe_cmd = "awk '/%s/,0'" % rest_of_cmd
7560 new_pipe_cmd_list.append(new_pipe_cmd)
7561
7562 new_pipe_cmds = "|".join(new_pipe_cmd_list)
7563 if new_pipe_cmds:
7564 if redirect_target:
7565 p = subprocess.Popen(new_pipe_cmds,
7566 shell=True,
7567 stdin=subprocess.PIPE,
7568 stdout=subprocess.PIPE,
7569 stderr=subprocess.STDOUT)
7570 output = self.generate_pipe_output(p, output)
7571 else:
7572 p = subprocess.Popen(new_pipe_cmds,
7573 shell=True,
7574 stdin=subprocess.PIPE)
7575 self.write_to_pipe(p, output)
7576 output = None
7577
7578 # only handle local file here as http/ftp were handled above via pipe
7579 if redirect_target:
7580 if redirect_target.startswith("config://"):
7581 m = re.search(self.local_name_pattern, redirect_target)
7582 if m:
7583 join_output = ''.join(iter(output))
7584 store_result = self.store.set_user_data_file(m.group(1), join_output)
7585 if store_result:
7586 result = json.loads(store_result)
7587 else:
7588 return self.error_msg("rest store result not json format")
7589 if 'status' in result and result['status'] == 'success':
7590 return None
7591 elif 'message' not in result:
7592 return self.error_msg("rest store result doesn't contain error message")
7593 else:
7594 return self.error_msg(result['message'])
7595 else:
7596 print self.error_msg("invalid name-in-db (%s)\n" % redirect_target)
7597 else:
7598 return output
7599
7600 return None
7601
7602 #
7603 # --------------------------------------------------------------------------------
7604 # generate_command_output
7605 #
7606 @staticmethod
7607 def generate_command_output(ret_val):
7608 if (isinstance(ret_val, str) or \
7609 isinstance(ret_val, buffer) or \
7610 isinstance(ret_val, bytearray) or \
7611 isinstance(ret_val, unicode)):
7612
7613 # yield ret_val
7614 if len(ret_val) and ret_val[-1] == '\n':
7615 ret_val = ret_val[:-1]
7616 for line in ret_val.split('\n'):
7617 yield line + '\n'
7618 elif ret_val != None:
7619 for item in ret_val:
7620 yield item
7621
7622 #
7623 # --------------------------------------------------------------------------------
7624 # generate_line_output
7625 #
7626 # This is a generator that will generate the output of the
7627 # command either as a string, or by iterating over a returned
7628 # iterable. This way, a subcommand can return an iterable to
7629 # lazily evaluate large amounts of output
7630 #
7631 def generate_line_output(self, line, dont_ask):
7632 while line:
7633 subline_index = self.find_with_quoting(line, ';')
7634 if subline_index < 0:
7635 subline_index = len(line)
7636 subline = line[:subline_index]
7637 line = line[subline_index+1:]
7638 ret_val = self.handle_single_line(subline)
7639 cnt = 1
7640 total_cnt = 0
7641
7642 (col_width, screen_length) = self.pp.get_terminal_size()
7643 if type(self.length) == int:
7644 screen_length = self.length
7645
7646 for item in self.generate_command_output(ret_val):
7647 if not dont_ask:
7648 incr = 1 + (max((len(item.rstrip()) - 1), 0) / col_width)
7649 if screen_length and cnt + incr >= screen_length:
7650 raw_input('-- hit return to continue, %s) --' % total_cnt)
7651 cnt = 0
7652 cnt += incr
7653 total_cnt += incr
7654 yield item
7655
7656 #
7657 # --------------------------------------------------------------------------------
7658 # handle_multipart_line
7659 #
7660 # this is the outermost handler that should print
7661 #
7662 def handle_multipart_line(self, line):
7663 pipe_cmds = None
7664 redirect_target = None
7665 output = None
7666
7667 # pattern is:
7668 # single line [; single line]* [| ...] [> {conf|ftp|http}]
7669
7670 # first take off the potential redirect part then the pipe cmds
7671 redirect_index = self.find_with_quoting(line, '>', True)
7672 if redirect_index >= 0:
7673 redirect_target = line[redirect_index+1:].strip()
7674 line = line[:redirect_index].strip()
7675 pipe_index = self.find_with_quoting(line, '|')
7676 if pipe_index >= 0:
7677 pipe_cmds = line[pipe_index+1:].strip()
7678 line = line[:pipe_index].strip()
7679 # remaining text is single lines separated by ';' - handle them
7680 output = self.generate_line_output(line, pipe_cmds or redirect_target)
7681
7682 # now output everything
7683 if pipe_cmds or redirect_target:
7684 output = self.handle_pipe_and_redirect(pipe_cmds, redirect_target, output)
7685
7686 if output != None:
7687 for line in output:
7688 print line.rstrip()
7689
7690 #
7691 # --------------------------------------------------------------------------------
7692 # cp_watch
7693 #
7694 def cp_watch(self, words, text, completion_char):
7695 if completion_char == ord('?'):
7696 if len(words) > 1:
7697 command.do_command_completion_help(words[1:], text)
7698 else:
7699 self.print_completion_help(self.help_splash([], text))
7700 return
7701 if len(words) == 1:
7702 items = self.commands_for_current_mode_starting_with(text)
7703 return utif.add_delim(items, ' ')
7704 else:
7705 return command.do_command_completion(words[1:], text)
7706
7707 #
7708 # --------------------------------------------------------------------------------
7709 # do_watch
7710 # only called to display help
7711 #
7712 def do_watch(self, words):
7713 return 'watch: repeat indicated command after watch keyword'
7714
7715 #
7716 # --------------------------------------------------------------------------------
7717 # handle_watch_command
7718 #
7719 # handle this here because this is a CLI-only command
7720 # LOOK! This could be using curses, but that has some complications with
7721 # potentially messing up the terminal. This is cheap but downside
7722 # is it uses up the scrollbuffer...
7723 #
7724 def handle_watch_command(self, line):
7725 #
7726 words = line.split()
7727 if len(words) == 0:
7728 return self.syntax_msg('watch: command to watch missing')
7729
7730 if len(words) and words[0] == 'watch':
7731 return self.error_msg('watch command not supported for watch')
7732
7733 while True: # must control-C to get out of this
7734 output = self.handle_multipart_line(line)
7735 if output:
7736 os.system("clear")
7737 print "Executing %s " % line
7738 print output
7739 else:
7740 print "Executing %s " % line
7741 time.sleep(2)
7742
7743 #
7744 #
7745 # --------------------------------------------------------------------------------
7746 # loop
7747 # this is just dispatching the command and handling errors
7748 #
7749 def loop(self):
7750 command.action_invoke('wait-for-controller', (5))
7751
7752 if self.controller:
7753 try:
7754 version_url = 'http://%s/rest/v1/system/version' % self.controller
7755 version = self.rest_simple_request_to_dict(version_url)
7756 except Exception, e:
7757 version = [{'controller' : 'REST API FAILURE\n'}]
7758
7759 print "default controller: %s, %s" % (self.controller,
7760 version[0]['controller'])
7761 #
7762 # vns feature enablement.
7763 # when vns is enabled, call a init procedure
7764 #
7765 if onos == 0:
7766 self.netvirt_feature_enabled()
7767
7768 while self.run:
7769 # Get command line - this will use the command completion above
7770 try:
7771 #rest_to_model.validate_switch()
7772 url_cache.clear_cached_urls()
7773 line = raw_input(self.prompt)
7774 if self.batch:
7775 print line
7776 m = re.search('^watch (.*)$', line)
7777 if m:
7778 self.handle_watch_command(m.group(1))
7779 else:
7780 self.handle_multipart_line(line)
7781 except KeyboardInterrupt:
7782 self.completion_reset()
7783 print "\nInterrupt."
7784 except EOFError:
7785 print "\nExiting."
7786 return
7787 except urllib2.HTTPError, e:
7788 errors = self.rest_error_to_dict(e)
7789 print self.error_msg("%s" % self.rest_error_dict_to_message(errors))
7790 except urllib2.URLError, e:
7791 print self.error_msg("communicating with REST API server on %s "
7792 "- Network error: %s" %
7793 (self.controller, e.reason))
7794 except Exception, e:
7795 print "\nError running command '%s'.\n" % line
7796 if self.debug or self.debug_backtrace:
7797 traceback.print_exc()
7798
7799
7800#
7801# --------------------------------------------------------------------------------
7802# Initialization crazyness to make it work across platforms. Many platforms don't include
7803# GNU readline (e.g. mac os x) and we need to compensate for this
7804
7805try:
7806 import readline
7807except ImportError:
7808 try:
7809 import pyreadline as readline
7810 except ImportError:
7811 print "Can't find any readline equivalent - aborting."
7812else:
7813 if 'libedit' in readline.__doc__:
7814 # needed for Mac, please fix Apple
7815 readline.parse_and_bind ("bind ^I rl_complete")
7816 else:
7817 readline.parse_and_bind("tab: complete")
7818 readline.parse_and_bind("?: possible-completions")
7819
7820
7821#
7822# --------------------------------------------------------------------------------
7823# Run the shell
7824
7825def main():
7826 global cli
7827 # Uncomment the next two lines to enable remote debugging with PyDev
7828 # LOOK! Should have logic here that enables/disables the pydevd stuff
7829 # automatically without requiring uncommenting (and, more importantly,
7830 # remembering to recomment before committing).
7831 # (e.g. checking environment variable or something like that)
7832 #python_path = os.environ.get('PYTHONPATH', '')
7833 #if 'pydev.debug' in python_path:
7834 try:
7835 import pydevd
7836 pydevd.settrace()
7837 except Exception, e:
7838 pass
7839
7840 # Process '--init' argument since it gates progress to rest of processing
7841 # as this is a special argument, it is required to be first argument (if used)
7842 check_ready_file = True
7843 if len(sys.argv) > 1:
7844 if sys.argv[1] == '--init':
7845 check_ready_file = False
7846
7847 # Do not start CLI till the systemn is ready
7848 # (allow user to ^C and get to SDNSh for debug etc)
7849 not_ready_file = '/opt/sdnplatform/run/starting'
7850 if check_ready_file:
7851 try:
7852 while True:
7853 if os.path.exists(not_ready_file):
7854 with open(not_ready_file, "r") as f:
7855 message = f.read()
7856 if len(message):
7857 print message,
7858 else:
7859 print 'Controller not yet ready ... waiting 5 sec'
7860 time.sleep(5)
7861 else:
7862 break
7863 except:
7864 if os.path.exists(not_ready_file):
7865 resp = raw_input('Controller is not yet ready. Do you still want to continue to the CLI? [n]')
7866 if resp and "yes".startswith(resp.lower()):
7867 print 'Continuing with CLI despite initialization error ...'
7868 else:
7869 print 'Aborting Controller CLI login.'
7870 time.sleep(1)
7871 return
7872
7873 # Start CLI
7874 cli = SDNSh()
7875 cli.init()
7876 cli.loop()
7877
7878if __name__ == '__main__':
7879 main()