source: ReferenceDesigns/w3_802.11/python/wlan_exp/transport/config.py

Last change on this file was 6320, checked in by chunter, 5 years ago

1.8.0 release wlan-exp

File size: 28.8 KB
Line 
1# -*- coding: utf-8 -*-
2"""
3------------------------------------------------------------------------------
4Mango 802.11 Reference Design Experiments Framework
5    - Network / Node Configurations
6------------------------------------------------------------------------------
7License:   Copyright 2019 Mango Communications, Inc. All rights reserved.
8           Use and distribution subject to terms in LICENSE.txt
9------------------------------------------------------------------------------
10
11This module provides class definitions to manage the Network and Nodes
12configurations.
13
14Functions (see below for more information):
15    NetworkConfiguration() -- Specifies Network information for setup
16    NodesConfiguration()   -- Specifies Node information for setup
17
18"""
19
20import os
21
22try:                 # Python 3
23    import configparser
24except ImportError:  # Python 2
25    import ConfigParser as configparser
26
27
28from . import defaults
29from . import util
30from . import exception as ex
31
32
33__all__ = ['NetworkConfiguration', 'NodesConfiguration']
34
35
36
37class 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
162class 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
Note: See TracBrowser for help on using the repository browser.