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 |
---|