Srikanth Vavilapalli | 1725e49 | 2014-12-01 17:50:52 -0800 | [diff] [blame] | 1 | #!/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 | |
| 151 | import subprocess |
| 152 | import os, atexit, stat |
| 153 | import sys, traceback # traceback.print_exc() |
| 154 | from optparse import OptionParser |
| 155 | from types import StringType |
| 156 | import collections |
| 157 | import datetime |
| 158 | import json |
| 159 | import re |
| 160 | import time |
| 161 | import urllib2 |
| 162 | import httplib # provides error processing for isinstance |
| 163 | import socket |
| 164 | import select |
| 165 | import fcntl |
| 166 | import posixpath |
| 167 | import random |
| 168 | import copy |
| 169 | import utif |
| 170 | import fmtcnv |
| 171 | import run_config |
| 172 | import imp |
| 173 | import locale |
| 174 | |
| 175 | from pkg_resources import resource_filename |
| 176 | from modi import Modi |
| 177 | from midw import * |
| 178 | from vnsw import * |
| 179 | from prettyprint import PrettyPrinter |
| 180 | from storeclient import StoreClient |
| 181 | from climodelinfo import CliModelInfo |
| 182 | from vendor import VendorDB |
| 183 | import error |
| 184 | import command |
| 185 | import rest_to_model |
| 186 | import tech_support |
| 187 | import url_cache |
| 188 | import doc |
| 189 | import sdndb |
| 190 | |
| 191 | # |
| 192 | # -------------------------------------------------------------------------------- |
| 193 | # |
| 194 | |
| 195 | class ParamException(Exception): |
| 196 | def __init__(self, value): |
| 197 | self.value = value |
| 198 | def __str__(self): |
| 199 | return repr(self.value) |
| 200 | |
| 201 | class TooManyVNSException(Exception): |
| 202 | def __init__(self, value): |
| 203 | self.value = value |
| 204 | def __str__(self): |
| 205 | return repr(self.value) |
| 206 | |
| 207 | TIMESTAMP_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. |
| 224 | cli = None |
| 225 | |
| 226 | # |
| 227 | # Constants for packet tracing |
| 228 | # |
| 229 | SESSIONID = 'sessionId' |
| 230 | FT_VNS = 'vns' |
| 231 | FT_PORT = 'port' |
| 232 | FT_PORT_DPID = 'dpid' |
| 233 | FT_PORT_PORT = 'port' |
| 234 | FT_OUTPUT = 'output' |
| 235 | FT_DIRECTION = 'direction' |
| 236 | FT_PERIOD = 'period' |
| 237 | FT_TIMEOUT = "FilterTimeout" |
| 238 | FT_PERIOD_DEFAULT = 300 |
| 239 | |
| 240 | onos=1 |
| 241 | # |
| 242 | # -------------------------------------------------------------------------------- |
| 243 | # |
| 244 | class 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 | |
| 256 | def 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 | |
| 267 | def 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 | |
| 282 | class 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 Vavilapalli | 193322d | 2014-12-02 11:28:24 -0800 | [diff] [blame] | 300 | |
| 301 | known_sdn_platforms = ["127.0.0.1:8080"] |
| 302 | sdn_controller_paltform = None |
Srikanth Vavilapalli | 1725e49 | 2014-12-01 17:50:52 -0800 | [diff] [blame] | 303 | |
| 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 Vavilapalli | 193322d | 2014-12-02 11:28:24 -0800 | [diff] [blame] | 786 | metavar="CONTROLLER", default=self.sdn_controller_paltform) |
Srikanth Vavilapalli | 1725e49 | 2014-12-01 17:50:52 -0800 | [diff] [blame] | 787 | 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 Vavilapalli | 193322d | 2014-12-02 11:28:24 -0800 | [diff] [blame] | 805 | if self.controller is None: |
Srikanth Vavilapalli | 1725e49 | 2014-12-01 17:50:52 -0800 | [diff] [blame] | 806 | self.controller = "127.0.0.1:8000" |
Srikanth Vavilapalli | 193322d | 2014-12-02 11:28:24 -0800 | [diff] [blame] | 807 | 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 Vavilapalli | 1725e49 | 2014-12-01 17:50:52 -0800 | [diff] [blame] | 810 | 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 Vavilapalli | 193322d | 2014-12-02 11:28:24 -0800 | [diff] [blame] | 834 | self.store.set_sdn_controller_platform_rest_if(self.sdn_controller_paltform) |
Srikanth Vavilapalli | 1725e49 | 2014-12-01 17:50:52 -0800 | [diff] [blame] | 835 | |
| 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 | |
| 7811 | try: |
| 7812 | import readline |
| 7813 | except ImportError: |
| 7814 | try: |
| 7815 | import pyreadline as readline |
| 7816 | except ImportError: |
| 7817 | print "Can't find any readline equivalent - aborting." |
| 7818 | else: |
| 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 | |
| 7831 | def 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 | |
| 7884 | if __name__ == '__main__': |
| 7885 | main() |