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