[6320] | 1 | # -*- coding: utf-8 -*- |
---|
| 2 | """ |
---|
| 3 | ------------------------------------------------------------------------------ |
---|
| 4 | Mango 802.11 Reference Design Experiments Framework |
---|
| 5 | - Network / Node Configurations |
---|
| 6 | ------------------------------------------------------------------------------ |
---|
| 7 | License: Copyright 2019 Mango Communications, Inc. All rights reserved. |
---|
| 8 | Use and distribution subject to terms in LICENSE.txt |
---|
| 9 | ------------------------------------------------------------------------------ |
---|
| 10 | |
---|
| 11 | This module provides class definitions to manage the Network and Nodes |
---|
| 12 | configurations. |
---|
| 13 | |
---|
| 14 | Functions (see below for more information): |
---|
| 15 | NetworkConfiguration() -- Specifies Network information for setup |
---|
| 16 | NodesConfiguration() -- Specifies Node information for setup |
---|
| 17 | |
---|
| 18 | """ |
---|
| 19 | |
---|
| 20 | import os |
---|
| 21 | |
---|
| 22 | try: # Python 3 |
---|
| 23 | import configparser |
---|
| 24 | except ImportError: # Python 2 |
---|
| 25 | import ConfigParser as configparser |
---|
| 26 | |
---|
| 27 | |
---|
| 28 | from . import defaults |
---|
| 29 | from . import util |
---|
| 30 | from . import exception as ex |
---|
| 31 | |
---|
| 32 | |
---|
| 33 | __all__ = ['NetworkConfiguration', 'NodesConfiguration'] |
---|
| 34 | |
---|
| 35 | |
---|
| 36 | |
---|
| 37 | class NetworkConfiguration(object): |
---|
| 38 | """Class for Network configuration. |
---|
| 39 | |
---|
| 40 | This class contains a network configuration using default values |
---|
| 41 | in defaults.py combined with parameters that are passed in. |
---|
| 42 | |
---|
| 43 | Config Structure: |
---|
| 44 | { 'network' str, |
---|
| 45 | 'host_id' int, |
---|
| 46 | 'unicast_port' int, |
---|
| 47 | 'broadcast_port' int, |
---|
| 48 | 'tx_buf_size' int, |
---|
| 49 | 'rx_buf_size' int, |
---|
| 50 | 'transport_type' str, |
---|
| 51 | 'jumbo_frame_support' bool, |
---|
| 52 | 'mtu' int} |
---|
| 53 | """ |
---|
| 54 | config = None |
---|
| 55 | |
---|
| 56 | def __init__(self, network=None, host_id=None, unicast_port=None, |
---|
| 57 | broadcast_port=None, tx_buffer_size=None, rx_buffer_size=None, |
---|
| 58 | transport_type=None, jumbo_frame_support=None, mtu=None): |
---|
| 59 | |
---|
| 60 | """Initialize a NetworkConfiguration |
---|
| 61 | |
---|
| 62 | Attributes: |
---|
| 63 | network -- Network interface |
---|
| 64 | host_id -- Host ID |
---|
| 65 | unicast_port -- Port for unicast traffic |
---|
| 66 | broadcast_port -- Port for broadcast traffic |
---|
| 67 | tx_buf_size -- TX buffer size |
---|
| 68 | rx_buf_size -- RX buffer size |
---|
| 69 | transport_type -- Transport type |
---|
| 70 | jumbo_frame_support -- Support for Jumbo Ethernet frames |
---|
| 71 | mtu -- Maximum Ethernet payload size supported by host |
---|
| 72 | |
---|
| 73 | The network configuration assumes a netmask of 255.255.255.0 for |
---|
| 74 | all networks. |
---|
| 75 | |
---|
| 76 | """ |
---|
| 77 | |
---|
| 78 | # Set initial config values |
---|
| 79 | self.config = {} |
---|
| 80 | self.config['network'] = defaults.NETWORK |
---|
| 81 | self.config['broadcast_address'] = util._get_broadcast_address(self.config['network']) |
---|
| 82 | self.config['tx_buffer_size'] = defaults.TX_BUFFER_SIZE |
---|
| 83 | self.config['rx_buffer_size'] = defaults.RX_BUFFER_SIZE |
---|
| 84 | self.config['host_id'] = defaults.HOST_ID |
---|
| 85 | self.config['unicast_port'] = defaults.UNICAST_PORT |
---|
| 86 | self.config['broadcast_port'] = defaults.BROADCAST_PORT |
---|
| 87 | self.config['transport_type'] = defaults.TRANSPORT_TYPE |
---|
| 88 | self.config['jumbo_frame_support'] = defaults.JUMBO_FRAME_SUPPORT |
---|
| 89 | |
---|
| 90 | # Process input arguments, override default config as needed |
---|
| 91 | # Cast numeric arguments to ints here so any ValueError exceptions |
---|
| 92 | # assert in this constructor, not deep in the stack when these |
---|
| 93 | # config fields are actually used to handle network I/O |
---|
| 94 | if host_id is not None: self.config['host_id'] = int(host_id) |
---|
| 95 | if unicast_port is not None: self.config['unicast_port'] = int(unicast_port) |
---|
| 96 | if broadcast_port is not None: self.config['broadcast_port'] = int(broadcast_port) |
---|
| 97 | if tx_buffer_size is not None: self.config['tx_buffer_size'] = int(tx_buffer_size) |
---|
| 98 | if rx_buffer_size is not None: self.config['rx_buffer_size'] = int(rx_buffer_size) |
---|
| 99 | if transport_type is not None: self.config['transport_type'] = transport_type |
---|
| 100 | |
---|
| 101 | # Apply and verify the socket buffer sizes via the OS socket interface |
---|
| 102 | # This util method will raise an exception if the requested buffer sizes |
---|
| 103 | # are not supported by the OS |
---|
| 104 | (self.config['tx_buffer_size'], |
---|
| 105 | self.config['rx_buffer_size']) = util._get_os_socket_buffer_size(self.config['tx_buffer_size'], |
---|
| 106 | self.config['rx_buffer_size']) |
---|
| 107 | # Verify the user-supplied network address matches the IP address for |
---|
| 108 | # a NIC on the host system |
---|
| 109 | if network is not None: |
---|
| 110 | self.config['network'] = util._check_network_interface(network) |
---|
| 111 | self.config['broadcast_address'] = util._get_broadcast_address(self.config['network']) |
---|
| 112 | |
---|
| 113 | # Set default MTU based on jumbo boolean argument |
---|
| 114 | if jumbo_frame_support: |
---|
| 115 | self.config['mtu'] = 9000 |
---|
| 116 | else: |
---|
| 117 | self.config['mtu'] = 1500 |
---|
| 118 | |
---|
| 119 | # Override the default MTU if user supplied the mtu argument |
---|
| 120 | if mtu is not None: |
---|
| 121 | # Sanity-check the user-supplied MTU |
---|
| 122 | if(mtu < 500) or (mtu > 9000): |
---|
| 123 | raise Exception('ERROR: NetworkConfiguration mtu argument {0} out of range [500,9000]'.format(mtu)) |
---|
| 124 | |
---|
| 125 | # Adopt the user-supplied MTU |
---|
| 126 | self.config['mtu'] = mtu |
---|
| 127 | |
---|
| 128 | def get_param(self, parameter): |
---|
| 129 | """Returns the value of the parameter within the section.""" |
---|
| 130 | if (parameter in self.config.keys()): |
---|
| 131 | return self.config[parameter] |
---|
| 132 | else: |
---|
| 133 | print("Parameter {0} does not exist.".format(parameter)) |
---|
| 134 | |
---|
| 135 | return None |
---|
| 136 | |
---|
| 137 | |
---|
| 138 | def set_param(self, parameter, value): |
---|
| 139 | """Sets the parameter to the given value.""" |
---|
| 140 | if (parameter in self.config.keys()): |
---|
| 141 | self.config[parameter] = value |
---|
| 142 | else: |
---|
| 143 | print("Parameter {0} does not exist.".format(parameter)) |
---|
| 144 | |
---|
| 145 | |
---|
| 146 | def __str__(self): |
---|
| 147 | msg = "" |
---|
| 148 | if self.config is not None: |
---|
| 149 | msg += "Network Configuration contains: \n" |
---|
| 150 | for parameter in self.config.keys(): |
---|
| 151 | msg += " {0:20s} = ".format(parameter) |
---|
| 152 | msg += "{0}\n".format(self.config[parameter]) |
---|
| 153 | else: |
---|
| 154 | msg += "Network Configuration not intialized.\n" |
---|
| 155 | |
---|
| 156 | return msg |
---|
| 157 | |
---|
| 158 | # End Class |
---|
| 159 | |
---|
| 160 | |
---|
| 161 | |
---|
| 162 | class NodesConfiguration(object): |
---|
| 163 | """Class for Nodes Configuration. |
---|
| 164 | |
---|
| 165 | This class can load and store Nodes configuration |
---|
| 166 | |
---|
| 167 | Attributes of a node: |
---|
| 168 | Node serial number |
---|
| 169 | node_id -- Node ID |
---|
| 170 | node_name -- Node Name |
---|
| 171 | ip_address -- IP address of the Node |
---|
| 172 | unicast_port -- Unicast port of the Node |
---|
| 173 | broadcast_port -- Broadcast port of the Node |
---|
| 174 | use_node -- Is this node part of the network |
---|
| 175 | |
---|
| 176 | Any parameter can be overridden by including it in the INI file. |
---|
| 177 | |
---|
| 178 | When the configuration is read in, both a config and a shadow_config |
---|
| 179 | are created. The config has values that will be written to the INI |
---|
| 180 | file, while the shadow_config has all values populated. If values |
---|
| 181 | are not specified in the INI, they will get auto-populated defaults: |
---|
| 182 | |
---|
| 183 | node_id - Monotonic counter starting at 0 |
---|
| 184 | node_name - None (unless specified by the user) |
---|
| 185 | ip_address - config.ini get_param('network', 'host_address') for |
---|
| 186 | the first three octets and "node_id + 1" for the last |
---|
| 187 | octet |
---|
| 188 | unicast_port - config.ini get_param('network', 'unicast_port') |
---|
| 189 | broadcast_port - config.ini get_param('network', 'broadcast_port') |
---|
| 190 | use_node - "True" |
---|
| 191 | |
---|
| 192 | In order to be as consistent as possible, all nodes in the configuration |
---|
| 193 | file get a node id regardless of whether they are used. Also, while it |
---|
| 194 | seems like the nodes are initialized in the order they appear in the |
---|
| 195 | config, that is not guaranteed. |
---|
| 196 | |
---|
| 197 | """ |
---|
| 198 | network_config = None |
---|
| 199 | config = None |
---|
| 200 | config_file = None |
---|
| 201 | node_id_counter = None |
---|
| 202 | used_node_ids = None |
---|
| 203 | used_node_ips = None |
---|
| 204 | shadow_config = None |
---|
| 205 | |
---|
| 206 | def __init__(self, ini_file=None, serial_numbers=None, network_config=None): |
---|
| 207 | """Initialize a NodesConfiguration |
---|
| 208 | |
---|
| 209 | Attributes: |
---|
| 210 | ini_file -- An INI file name that specified a nodes configuration |
---|
| 211 | serial_numbers -- A list of node serial numbers |
---|
| 212 | network_config -- A NetworkConfiguration |
---|
| 213 | |
---|
| 214 | There are multiple ways to initialize a NodesConfiguration: |
---|
| 215 | 1) List of serial_numbers |
---|
| 216 | 2) INI file name |
---|
| 217 | |
---|
| 218 | Serial numbers are parsed based on the formats defined by each |
---|
| 219 | available waln_exp_platform. |
---|
| 220 | |
---|
| 221 | For an INI file, you can create an INI file using the nodes_setup() |
---|
| 222 | method in transport.util. In the INI file, you can specify as little |
---|
| 223 | as the serial numbers, up to a fully explicit configuration. |
---|
| 224 | |
---|
| 225 | If neither an ini_file nor serial_numbers are provided, the method |
---|
| 226 | will check for the NODES_CONFIG_INI_FILE specified in |
---|
| 227 | transport.defaults |
---|
| 228 | |
---|
| 229 | If both serial_numbers and an ini_file are provided, the ini_file |
---|
| 230 | will be ignored. |
---|
| 231 | |
---|
| 232 | If not provided, the class will use the default NetworkConfiguration |
---|
| 233 | specified in defaults. Since there can be multiple network interfaces |
---|
| 234 | as part of the NetworkConfiguration, the NodesConfiguration will only |
---|
| 235 | auto-populate IP address on the first network interface, ie network |
---|
| 236 | interface 0. Therefore, if only serial_numbers are provided then all |
---|
| 237 | of those nodes will receive IP addresses on network interface 0. |
---|
| 238 | |
---|
| 239 | """ |
---|
| 240 | self.node_id_counter = 0 |
---|
| 241 | self.shadow_config = {} |
---|
| 242 | self.used_node_ids = [] |
---|
| 243 | self.used_node_ips = [] |
---|
| 244 | |
---|
| 245 | # Initialize the Shadow Config from Host data |
---|
| 246 | if network_config is None: |
---|
| 247 | network_config = NetworkConfiguration() |
---|
| 248 | |
---|
| 249 | network_addr = network_config.get_param('network') |
---|
| 250 | u_port = network_config.get_param('unicast_port') |
---|
| 251 | b_port = network_config.get_param('broadcast_port') |
---|
| 252 | |
---|
| 253 | # Compute the base IP address to auto-assign IP addresses |
---|
| 254 | base_ip_address = util._get_ip_address_subnet(network_addr) + ".{0}" |
---|
| 255 | |
---|
| 256 | # Initialize the shadow config |
---|
| 257 | self.init_shadow_config(base_ip_address, u_port, b_port) |
---|
| 258 | |
---|
| 259 | # Initialize the config based on rules documented above. |
---|
| 260 | # - This can raise exceptions if there are issues. |
---|
| 261 | if serial_numbers is None: |
---|
| 262 | if ini_file is None: |
---|
| 263 | ini_file = defaults.NODES_CONFIG_INI_FILE |
---|
| 264 | |
---|
| 265 | self.load_config(ini_file) |
---|
| 266 | |
---|
| 267 | else: |
---|
| 268 | self.set_default_config() |
---|
| 269 | |
---|
| 270 | for sn in serial_numbers: |
---|
| 271 | self.add_node(sn) |
---|
| 272 | # try: |
---|
| 273 | # self.add_node(sn) |
---|
| 274 | # except TypeError as err: |
---|
| 275 | # print(err) |
---|
| 276 | |
---|
| 277 | |
---|
| 278 | |
---|
| 279 | # ------------------------------------------------------------------------- |
---|
| 280 | # Methods for Config |
---|
| 281 | # ------------------------------------------------------------------------- |
---|
| 282 | def set_default_config(self): |
---|
| 283 | """Set the default config.""" |
---|
| 284 | self.config = configparser.ConfigParser() |
---|
| 285 | self.init_used_node_lists() |
---|
| 286 | |
---|
| 287 | |
---|
| 288 | def add_node(self, serial_number, ip_address=None, |
---|
| 289 | node_id=None, unicast_port=None, broadcast_port=None, |
---|
| 290 | node_name=None, use_node=None): |
---|
| 291 | """Add a node to the NodesConfiguration structure. |
---|
| 292 | |
---|
| 293 | Only serial_number and ip_address are required in the ini file. Other |
---|
| 294 | fields will not be populated in the ini file unless they require a |
---|
| 295 | non-default value. |
---|
| 296 | """ |
---|
| 297 | (_, sn_str) = util.get_serial_number(serial_number) |
---|
| 298 | |
---|
| 299 | if (sn_str in self.config.sections()): |
---|
| 300 | print("Node {0} exists. Please use set_param to modify the node.".format(sn_str)) |
---|
| 301 | else: |
---|
| 302 | self.config.add_section(sn_str) |
---|
| 303 | |
---|
| 304 | # Populate optional parameters |
---|
| 305 | if ip_address is not None: self.config.set(sn_str, 'ip_address', ip_address) |
---|
| 306 | if node_id is not None: self.config.set(sn_str, 'node_id', node_id) |
---|
| 307 | if unicast_port is not None: self.config.set(sn_str, 'unicast_port', unicast_port) |
---|
| 308 | if broadcast_port is not None: self.config.set(sn_str, 'broadcast_port', broadcast_port) |
---|
| 309 | if node_name is not None: self.config.set(sn_str, 'node_name', node_name) |
---|
| 310 | if use_node is not None: self.config.set(sn_str, 'use_node', use_node) |
---|
| 311 | |
---|
| 312 | # Add node to shadow_config |
---|
| 313 | self.add_shadow_node(sn_str) |
---|
| 314 | |
---|
| 315 | |
---|
| 316 | def remove_node(self, serial_number): |
---|
| 317 | """Remove a node from the NodesConfiguration structure.""" |
---|
| 318 | (_, sn_str) = util.get_serial_number(serial_number) |
---|
| 319 | |
---|
| 320 | if (not self.config.remove_section(sn_str)): |
---|
| 321 | print("Node {0} not in nodes configuration.".format(sn_str)) |
---|
| 322 | else: |
---|
| 323 | self.remove_shadow_node(sn_str) |
---|
| 324 | |
---|
| 325 | |
---|
| 326 | def get_param(self, section, parameter): |
---|
| 327 | """Returns the value of the parameter within the configuration for the node.""" |
---|
| 328 | (_, sn_str) = util.get_serial_number(section) |
---|
| 329 | |
---|
| 330 | return self.get_param_helper(sn_str, parameter) |
---|
| 331 | |
---|
| 332 | |
---|
| 333 | def get_param_helper(self, section, parameter): |
---|
| 334 | """Returns the value of the parameter within the configuration section.""" |
---|
| 335 | if (section in self.config.sections()): |
---|
| 336 | if (parameter in self.config.options(section)): |
---|
| 337 | return self._get_param_hack(section, parameter) |
---|
| 338 | else: |
---|
| 339 | return self._get_shadow_param(section, parameter) |
---|
| 340 | else: |
---|
| 341 | print("Node '{}' does not exist.".format(section)) |
---|
| 342 | |
---|
| 343 | return "" |
---|
| 344 | |
---|
| 345 | |
---|
| 346 | def set_param(self, section, parameter, value): |
---|
| 347 | """Sets the parameter to the given value.""" |
---|
| 348 | (_, sn_str) = util.get_serial_number(section) |
---|
| 349 | |
---|
| 350 | if (sn_str in self.config.sections()): |
---|
| 351 | if (parameter in self.config.options(sn_str)): |
---|
| 352 | self._set_param_hack(sn_str, parameter, value) |
---|
| 353 | self.update_shadow_config(sn_str, parameter, value) |
---|
| 354 | else: |
---|
| 355 | if (parameter in self.shadow_config['default'].keys()): |
---|
| 356 | self._set_param_hack(sn_str, parameter, value) |
---|
| 357 | self.update_shadow_config(sn_str, parameter, value) |
---|
| 358 | else: |
---|
| 359 | print("Parameter {} does not exist in node '{}'.".format(parameter, sn_str)) |
---|
| 360 | else: |
---|
| 361 | print("Section '{}' does not exist.".format(sn_str)) |
---|
| 362 | |
---|
| 363 | |
---|
| 364 | def remove_param(self, section, parameter): |
---|
| 365 | """Removes the parameter from the config.""" |
---|
| 366 | (_, sn_str) = util.get_serial_number(section) |
---|
| 367 | |
---|
| 368 | if (sn_str in self.config.sections()): |
---|
| 369 | if (parameter in self.config.options(sn_str)): |
---|
| 370 | self.config.remove_option(sn_str, parameter) |
---|
| 371 | |
---|
| 372 | # Re-populate the shadow_config |
---|
| 373 | self.remove_shadow_node(sn_str) |
---|
| 374 | self.add_shadow_node(sn_str) |
---|
| 375 | else: |
---|
| 376 | # Fail silently so there are no issues when a user tries to |
---|
| 377 | # remove a shadow_config parameter |
---|
| 378 | pass |
---|
| 379 | else: |
---|
| 380 | print("Section '{}' does not exist.".format(sn_str)) |
---|
| 381 | |
---|
| 382 | |
---|
| 383 | def get_nodes_dict(self): |
---|
| 384 | """Returns a list of dictionaries that contain the parameters of each |
---|
| 385 | WlanExpTransportNode specified in the config.""" |
---|
| 386 | output = [] |
---|
| 387 | |
---|
| 388 | if not self.config.sections(): |
---|
| 389 | raise ex.ConfigError("No Nodes in {0}".format(self.config_file)) |
---|
| 390 | |
---|
| 391 | for node_config in self.config.sections(): |
---|
| 392 | if (self.get_param_helper(node_config, 'use_node')): |
---|
| 393 | add_node = True |
---|
| 394 | |
---|
| 395 | try: |
---|
| 396 | (_, sn_str) = util.get_serial_number(node_config) |
---|
| 397 | except TypeError as err: |
---|
| 398 | print(err) |
---|
| 399 | add_node = False |
---|
| 400 | |
---|
| 401 | if add_node: |
---|
| 402 | node_dict = { |
---|
| 403 | 'serial_number': sn_str, #PLATFORM-HACK: keep track of canonical sn string as long as possible |
---|
| 404 | 'node_id' : self.get_param_helper(node_config, 'node_id'), |
---|
| 405 | 'node_name' : self.get_param_helper(node_config, 'node_name'), |
---|
| 406 | 'ip_address' : self.get_param_helper(node_config, 'ip_address'), |
---|
| 407 | 'unicast_port' : self.get_param_helper(node_config, 'unicast_port'), |
---|
| 408 | 'broadcast_port' : self.get_param_helper(node_config, 'broadcast_port') } |
---|
| 409 | output.append(node_dict) |
---|
| 410 | |
---|
| 411 | return output |
---|
| 412 | |
---|
| 413 | |
---|
| 414 | def load_config(self, config_file): |
---|
| 415 | """Loads the nodes configuration from the provided file.""" |
---|
| 416 | self.config_file = os.path.normpath(config_file) |
---|
| 417 | |
---|
| 418 | # TODO: allow relative paths |
---|
| 419 | self.clear_shadow_config() |
---|
| 420 | self.config = configparser.ConfigParser() |
---|
| 421 | dataset = self.config.read(self.config_file) |
---|
| 422 | |
---|
| 423 | if len(dataset) != 1: |
---|
| 424 | msg = str("Error reading config file:\n" + self.config_file) |
---|
| 425 | raise ex.ConfigError(msg) |
---|
| 426 | else: |
---|
| 427 | self.init_used_node_lists() |
---|
| 428 | self.load_shadow_config() |
---|
| 429 | |
---|
| 430 | |
---|
| 431 | def save_config(self, config_file=None, output=False): |
---|
| 432 | """Saves the nodes configuration to the provided file.""" |
---|
| 433 | if config_file is not None: |
---|
| 434 | self.config_file = os.path.normpath(config_file) |
---|
| 435 | else: |
---|
| 436 | self.config_file = defaults.NODES_CONFIG_INI_FILE |
---|
| 437 | |
---|
| 438 | if output: |
---|
| 439 | print("Saving config to: \n {0}".format(self.config_file)) |
---|
| 440 | |
---|
| 441 | try: |
---|
| 442 | with open(self.config_file, 'w') as configfile: |
---|
| 443 | self.config.write(configfile) |
---|
| 444 | except IOError as err: |
---|
| 445 | print("Error writing config file: {0}".format(err)) |
---|
| 446 | |
---|
| 447 | |
---|
| 448 | # ------------------------------------------------------------------------- |
---|
| 449 | # Methods for Shadow Config |
---|
| 450 | # ------------------------------------------------------------------------- |
---|
| 451 | def init_shadow_config(self, ip_addr_base, unicast_port, broadcast_port): |
---|
| 452 | """Initialize the 'default' section of the shadow_config.""" |
---|
| 453 | self.shadow_config['default'] = {'node_id' : 'auto', |
---|
| 454 | 'node_name' : None, |
---|
| 455 | 'ip_address' : ip_addr_base, |
---|
| 456 | 'unicast_port' : unicast_port, |
---|
| 457 | 'broadcast_port' : broadcast_port, |
---|
| 458 | 'use_node' : True} |
---|
| 459 | |
---|
| 460 | def init_used_node_lists(self): |
---|
| 461 | """Initialize the lists used to keep track of fields that must be unique.""" |
---|
| 462 | self.used_node_ids = [] |
---|
| 463 | self.used_node_ips = util._get_all_host_ip_addrs() |
---|
| 464 | |
---|
| 465 | if self.config is not None: |
---|
| 466 | for section in self.config.sections(): |
---|
| 467 | if ('node_id' in self.config.options(section)): |
---|
| 468 | self.used_node_ids.append(self._get_param_hack(section, 'node_id')) |
---|
| 469 | |
---|
| 470 | if ('ip_address' in self.config.options(section)): |
---|
| 471 | self.used_node_ips.append(self._get_param_hack(section, 'ip_address')) |
---|
| 472 | |
---|
| 473 | |
---|
| 474 | def clear_shadow_config(self): |
---|
| 475 | """Clear everything in the shadow config except 'default' section.""" |
---|
| 476 | for section in self.shadow_config.keys(): |
---|
| 477 | if (section != 'default'): |
---|
| 478 | del self.shadow_config[section] |
---|
| 479 | |
---|
| 480 | self.init_used_node_lists() |
---|
| 481 | |
---|
| 482 | |
---|
| 483 | def load_shadow_config(self): |
---|
| 484 | """For each node in the config, populate the shadow_config.""" |
---|
| 485 | sections = self.config.sections() |
---|
| 486 | |
---|
| 487 | # Sort the config by serial number so there is consistent numbering |
---|
| 488 | # sections.sort() |
---|
| 489 | |
---|
| 490 | # Mirror any fields in the config and populate any missing fields |
---|
| 491 | # with default values |
---|
| 492 | for section in sections: |
---|
| 493 | my_node_id = self._get_node_id(section) |
---|
| 494 | my_node_name = self._get_node_name(section, my_node_id) |
---|
| 495 | my_ip_address = self._get_ip_address(section, my_node_id) |
---|
| 496 | my_unicast_port = self._get_unicast_port(section) |
---|
| 497 | my_broadcast_port = self._get_broadcast_port(section) |
---|
| 498 | my_use_node = self._get_use_node(section) |
---|
| 499 | |
---|
| 500 | # Set the node in the shadow_config |
---|
| 501 | self.set_shadow_node(section, my_ip_address, my_node_id, |
---|
| 502 | my_unicast_port, my_broadcast_port, |
---|
| 503 | my_node_name, my_use_node) |
---|
| 504 | |
---|
| 505 | # TODO: Sanity check to make sure there a no duplicate Node IDs or IP Addresses |
---|
| 506 | |
---|
| 507 | |
---|
| 508 | def update_shadow_config(self, section, parameter, value): |
---|
| 509 | """Update the shadow_config with the given value.""" |
---|
| 510 | self.shadow_config[section][parameter] = value |
---|
| 511 | |
---|
| 512 | |
---|
| 513 | def add_shadow_node(self, serial_number): |
---|
| 514 | """Add the given node to the shadow_config.""" |
---|
| 515 | my_node_id = self._get_node_id(serial_number) |
---|
| 516 | my_node_name = self._get_node_name(serial_number, my_node_id) |
---|
| 517 | my_ip_address = self._get_ip_address(serial_number, my_node_id) |
---|
| 518 | my_unicast_port = self._get_unicast_port(serial_number) |
---|
| 519 | my_broadcast_port = self._get_broadcast_port(serial_number) |
---|
| 520 | my_use_node = self._get_use_node(serial_number) |
---|
| 521 | |
---|
| 522 | # Set the node in the shadow_config |
---|
| 523 | self.set_shadow_node(serial_number, my_ip_address, my_node_id, |
---|
| 524 | my_unicast_port, my_broadcast_port, |
---|
| 525 | my_node_name, my_use_node) |
---|
| 526 | |
---|
| 527 | |
---|
| 528 | |
---|
| 529 | def set_shadow_node(self, serial_number, ip_address, node_id, |
---|
| 530 | unicast_port, broadcast_port, node_name, use_node): |
---|
| 531 | """Set the given node in the shadow_config.""" |
---|
| 532 | self.shadow_config[serial_number] = { |
---|
| 533 | 'node_id' : node_id, |
---|
| 534 | 'node_name' : node_name, |
---|
| 535 | 'ip_address' : ip_address, |
---|
| 536 | 'unicast_port' : unicast_port, |
---|
| 537 | 'broadcast_port' : broadcast_port, |
---|
| 538 | 'use_node' : use_node} |
---|
| 539 | |
---|
| 540 | self.used_node_ids.append(node_id) |
---|
| 541 | self.used_node_ips.append(ip_address) |
---|
| 542 | |
---|
| 543 | |
---|
| 544 | def remove_shadow_node(self, serial_number): |
---|
| 545 | """Remove the given node from the shadow_config.""" |
---|
| 546 | self.used_node_ids.remove(self._get_shadow_param(serial_number, 'node_id')) |
---|
| 547 | self.used_node_ips.remove(self._get_shadow_param(serial_number, 'ip_address')) |
---|
| 548 | del self.shadow_config[serial_number] |
---|
| 549 | |
---|
| 550 | |
---|
| 551 | # ------------------------------------------------------------------------- |
---|
| 552 | # Internal Methods |
---|
| 553 | # ------------------------------------------------------------------------- |
---|
| 554 | def _get_next_node_id(self): |
---|
| 555 | next_node_id = self.node_id_counter |
---|
| 556 | |
---|
| 557 | while (next_node_id in self.used_node_ids): |
---|
| 558 | self.node_id_counter += 1 |
---|
| 559 | next_node_id = self.node_id_counter |
---|
| 560 | |
---|
| 561 | return next_node_id |
---|
| 562 | |
---|
| 563 | |
---|
| 564 | def _get_next_node_ip(self, node_id): |
---|
| 565 | ip_addr_base = self.shadow_config['default']['ip_address'] |
---|
| 566 | |
---|
| 567 | last_octet = self._inc_node_ip(node_id) |
---|
| 568 | next_ip_addr = ip_addr_base.format(last_octet) |
---|
| 569 | |
---|
| 570 | while (next_ip_addr in self.used_node_ips): |
---|
| 571 | last_octet = self._inc_node_ip(last_octet) |
---|
| 572 | next_ip_addr = ip_addr_base.format(last_octet) |
---|
| 573 | |
---|
| 574 | return next_ip_addr |
---|
| 575 | |
---|
| 576 | |
---|
| 577 | def _inc_node_ip(self, node_ip): |
---|
| 578 | my_node_ip = node_ip + 1 |
---|
| 579 | |
---|
| 580 | if (my_node_ip > 254): |
---|
| 581 | my_node_ip = 1 |
---|
| 582 | |
---|
| 583 | return my_node_ip |
---|
| 584 | |
---|
| 585 | |
---|
| 586 | def _get_node_id(self, section): |
---|
| 587 | if ('node_id' in self.config.options(section)): |
---|
| 588 | return self._get_param_hack(section, 'node_id') |
---|
| 589 | else: |
---|
| 590 | return self._get_next_node_id() |
---|
| 591 | |
---|
| 592 | |
---|
| 593 | def _get_node_name(self, section, node_id): |
---|
| 594 | if ('node_name' in self.config.options(section)): |
---|
| 595 | return self._get_param_hack(section, 'node_name') |
---|
| 596 | else: |
---|
| 597 | # We are not going to give the node a name unless explicitly told to do so |
---|
| 598 | # return "Node {0}".format(node_id) |
---|
| 599 | return None |
---|
| 600 | |
---|
| 601 | |
---|
| 602 | def _get_ip_address(self, section, node_id): |
---|
| 603 | if ('ip_address' in self.config.options(section)): |
---|
| 604 | return self._get_param_hack(section, 'ip_address') |
---|
| 605 | else: |
---|
| 606 | return self._get_next_node_ip(node_id) |
---|
| 607 | |
---|
| 608 | |
---|
| 609 | def _get_unicast_port(self, section): |
---|
| 610 | if ('unicast_port' in self.config.options(section)): |
---|
| 611 | return self._get_param_hack(section, 'unicast_port') |
---|
| 612 | else: |
---|
| 613 | return self.shadow_config['default']['unicast_port'] |
---|
| 614 | |
---|
| 615 | |
---|
| 616 | def _get_broadcast_port(self, section): |
---|
| 617 | if ('broadcast_port' in self.config.options(section)): |
---|
| 618 | return self._get_param_hack(section, 'broadcast_port') |
---|
| 619 | else: |
---|
| 620 | return self.shadow_config['default']['broadcast_port'] |
---|
| 621 | |
---|
| 622 | |
---|
| 623 | def _get_use_node(self, section): |
---|
| 624 | if ('use_node' in self.config.options(section)): |
---|
| 625 | return self._get_param_hack(section, 'use_node') |
---|
| 626 | else: |
---|
| 627 | return True |
---|
| 628 | |
---|
| 629 | |
---|
| 630 | def _get_shadow_param(self, section, parameter): |
---|
| 631 | """Internal method to get shadow parameters. |
---|
| 632 | |
---|
| 633 | This is where to implement any per node defaults. |
---|
| 634 | """ |
---|
| 635 | if (parameter in self.shadow_config[section].keys()): |
---|
| 636 | return self.shadow_config[section][parameter] |
---|
| 637 | else: |
---|
| 638 | print("Parameter {} does not exist in node '{}'.".format(parameter, section)) |
---|
| 639 | return "" |
---|
| 640 | |
---|
| 641 | |
---|
| 642 | def _get_param_hack(self, section, parameter): |
---|
| 643 | """Internal method to work around differences in Python 2 vs 3""" |
---|
| 644 | if ((parameter == 'ip_address') or (parameter == 'node_name')): |
---|
| 645 | return self.config.get(section, parameter) |
---|
| 646 | else: |
---|
| 647 | return eval(self.config.get(section, parameter)) |
---|
| 648 | |
---|
| 649 | |
---|
| 650 | def _set_param_hack(self, section, parameter, value): |
---|
| 651 | """Internal method to work around differences in Python 2 vs 3""" |
---|
| 652 | my_value = str(value) |
---|
| 653 | self.config.set(section, parameter, my_value) |
---|
| 654 | |
---|
| 655 | |
---|
| 656 | # ------------------------------------------------------------------------- |
---|
| 657 | # Printing / Debug Methods |
---|
| 658 | # ------------------------------------------------------------------------- |
---|
| 659 | def print_shadow_config(self): |
---|
| 660 | for section in self.shadow_config.keys(): |
---|
| 661 | print("{0}".format(section)) |
---|
| 662 | for parameter in self.shadow_config[section].keys(): |
---|
| 663 | print(" {0} = {1}".format(parameter, self.shadow_config[section][parameter])) |
---|
| 664 | print("") |
---|
| 665 | |
---|
| 666 | |
---|
| 667 | def print_config(self): |
---|
| 668 | for section in self.config.sections(): |
---|
| 669 | print("{0}".format(section)) |
---|
| 670 | for parameter in self.config.options(section): |
---|
| 671 | print(" {0} = {1}".format(parameter, self.config.get(section, parameter))) |
---|
| 672 | print("") |
---|
| 673 | |
---|
| 674 | |
---|
| 675 | def print_nodes(self): |
---|
| 676 | return_val = {} |
---|
| 677 | print("Current Nodes:") |
---|
| 678 | if (len(self.config.sections()) == 0): |
---|
| 679 | print(" None") |
---|
| 680 | sections = self.config.sections() |
---|
| 681 | # sections.sort() |
---|
| 682 | for idx, val in enumerate(sections): |
---|
| 683 | node_id = self.get_param_helper(val, 'node_id') |
---|
| 684 | ip_addr = self.get_param_helper(val, 'ip_address') |
---|
| 685 | use_node = self.get_param_helper(val, 'use_node') |
---|
| 686 | msg = " [{0}] {1} - Node {2:3d} at {3:10s}".format(idx, val, node_id, ip_addr) |
---|
| 687 | if (use_node): |
---|
| 688 | print(str(msg + " active")) |
---|
| 689 | else: |
---|
| 690 | print(str(msg + " inactive")) |
---|
| 691 | return_val[idx] = val |
---|
| 692 | return return_val |
---|
| 693 | |
---|
| 694 | |
---|
| 695 | def __str__(self): |
---|
| 696 | section_str = "" |
---|
| 697 | if self.config is not None: |
---|
| 698 | section_str += "contains parameters: \n" |
---|
| 699 | for section in self.config.sections(): |
---|
| 700 | section_str = str(section_str + |
---|
| 701 | " Section '" + str(section) + "':\n" + |
---|
| 702 | " " + str(self.config.options(section)) + "\n") |
---|
| 703 | else: |
---|
| 704 | section_str += "not initialized. \n" |
---|
| 705 | |
---|
| 706 | if not self.config_file: |
---|
| 707 | return str("Default config " + section_str) |
---|
| 708 | else: |
---|
| 709 | return str(self.config_file + ": \n" + section_str) |
---|
| 710 | |
---|
| 711 | |
---|
| 712 | # End Class |
---|