Passed
Push — master ( e44493...373da3 )
by Vinicius
19:41 queued 15:59
created

kytos.core.config.KytosConfig.options_exposed()   A

Complexity

Conditions 2

Size

Total Lines 7
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
nop 1
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
"""Here you can control the config parameters to run Kytos controller.
2
3
Basically you can use a config file (-c option) and use arguments on command
4
line. If you specify a config file, then and option configured inside this file
5
will be overridden by the option on command line.
6
"""
7
8
import json
9
import os
10
import uuid
11
import warnings
12
from argparse import ArgumentParser, RawDescriptionHelpFormatter
13
from configparser import ConfigParser
14
from pathlib import Path
15
16
from jinja2 import Template
17
18
from kytos.core.metadata import __version__
19
20
BASE_ENV = os.environ.get('VIRTUAL_ENV', None) or '/'
21
ETC_KYTOS = 'etc/kytos'
22
TEMPLATE_FILES = ['templates/kytos.conf.template',
23
                  'templates/logging.ini.template']
24
SYSLOG_ARGS = ['/dev/log'] if Path('/dev/log').exists() else []
25
26
27
class KytosConfig():
28
    """Handle settings of Kytos."""
29
30
    def __init__(self):
31
        """Parse the command line.
32
33
        The contructor set defaults parameters that can be used by KytosConfig.
34
        """
35
        self.options = {}
36
        conf_parser = ArgumentParser(add_help=False)
37
38
        conf_parser.add_argument("-c", "--conf",
39
                                 help="Specify a config file",
40
                                 metavar="FILE")
41
42
        parser = ArgumentParser(prog='kytosd',
43
                                parents=[conf_parser],
44
                                formatter_class=RawDescriptionHelpFormatter,
45
                                description=__doc__)
46
47
        parser.add_argument('-v', '--version',
48
                            action='version',
49
                            version=f"kytosd {__version__}")
50
51
        parser.add_argument('-D', '--debug',
52
                            action='store_true',
53
                            help="Run in debug mode")
54
55
        parser.add_argument('-f', '--foreground',
56
                            action='store_true',
57
                            help="Run in foreground (ctrl+c to stop)")
58
59
        parser.add_argument('-l', '--listen',
60
                            action='store',
61
                            help="IP/Interface to be listened")
62
63
        parser.add_argument('-n', '--napps',
64
                            action='store',
65
                            help="Specify the napps directory")
66
67
        parser.add_argument('-P', '--port',
68
                            action='store',
69
                            help="Port to be listened")
70
71
        parser.add_argument('-p', '--pidfile',
72
                            action='store',
73
                            help="Specify the PID file to save.")
74
75
        parser.add_argument('-w', '--workdir',
76
                            action='store',
77
                            help="Specify the working directory")
78
79
        parser.add_argument('-s', '--protocol_name',
80
                            action='store',
81
                            help="Specify the southbound protocol")
82
83
        parser.add_argument('-E', '--enable_entities_by_default',
84
                            action='store_true',
85
                            help="Enable all new Entities by default.")
86
87
        parser.add_argument('-C', '--create-superuser',
88
                            action='store_true',
89
                            help="Create a kytos superuser.")
90
91
        parser.add_argument('-t', '--thread_pool_max_workers',
92
                            action='store',
93
                            help="Maximum number of threads in the pool.")
94
95
        parser.add_argument('-d', '--database',
96
                            action='store',
97
                            help="Database backend.")
98
99
        parser.add_argument('-a', '--apm',
100
                            action='store',
101
                            help="APM backend.")
102
103
        self.conf_parser, self.parser = conf_parser, parser
104
        self.parse_args()
105
106
    def parse_args(self):
107
        """Get the command line options and update kytos settings.
108
109
        When installed via pip, defaults values are:
110
111
        .. code-block:: python
112
113
            defaults = {'pidfile': '/var/run/kytos/kytosd.pid',
114
                        'workdir': '/var/lib/kytos',
115
                        'napps': '/var/lib/kytos/napps/',
116
                        'conf': '/etc/kytos/kytos.conf',
117
                        'logging': '/etc/kytos/logging.ini',
118
                        'listen': '0.0.0.0',
119
                        'port': 6653,
120
                        'foreground': False,
121
                        'protocol_name': '',
122
                        'enable_entities_by_default': False,
123
                        'token_expiration_minutes': 180,
124
                        'thread_pool_max_workers': 256,
125
                        'debug': False}
126
127
        """
128
        defaults = {'pidfile': os.path.join(BASE_ENV,
129
                                            'var/run/kytos/kytosd.pid'),
130
                    'workdir': os.path.join(BASE_ENV, 'var/lib/kytos'),
131
                    'napps': os.path.join(BASE_ENV, 'var/lib/kytos/napps/'),
132
                    'napps_repositories': "['https://napps.kytos.io/repo/']",
133
                    'installed_napps': os.path.join(BASE_ENV,
134
                                                    'var/lib/kytos/napps/',
135
                                                    '.installed'),
136
                    'conf': os.path.join(BASE_ENV, 'etc/kytos/kytos.conf'),
137
                    'logging': os.path.join(BASE_ENV, 'etc/kytos/logging.ini'),
138
                    'logger_decorators':
139
                        ["kytos.core.logger_decorators.queue_decorator"],
140
                    'listen': '0.0.0.0',
141
                    'port': 6653,
142
                    'api_traceback_on_500': True,
143
                    'foreground': False,
144
                    'protocol_name': '',
145
                    'enable_entities_by_default': False,
146
                    'napps_pre_installed': [],
147
                    'authenticate_urls': [],
148
                    'vlan_pool': {},
149
                    'token_expiration_minutes': 180,
150
                    'thread_pool_max_workers': {},
151
                    'database': '',
152
                    'apm': '',
153
                    'connection_timeout': 130,
154
                    'debug': False}
155
156
        options, argv = self.conf_parser.parse_known_args()
157
158
        config = ConfigParser()
159
160
        config_file = options.conf or defaults.get('conf')
161
162
        if not os.path.exists(config_file):
163
            _render_config_templates(TEMPLATE_FILES, BASE_ENV,
164
                                     prefix=BASE_ENV,
165
                                     syslog_args=SYSLOG_ARGS)
166
167
        config.read(config_file)
168
169
        defaults.update(dict(config.items("daemon")))
170
171
        self.parser.set_defaults(**defaults)
172
173
        self.options['daemon'] = self._parse_options(argv)
174
175
    def _parse_options(self, argv):
176
        """Create a Namespace using the given argv.
177
178
        Args:
179
            argv(dict): Python Dict used to create the namespace.
180
181
        Returns:
182
            options(Namespace): Namespace with the args given
183
184
        """
185
        options, unknown = self.parser.parse_known_args(argv)
186
        if unknown:
187
            warnings.warn(f"Unknown arguments: {unknown}")
188
        options.napps_repositories = json.loads(options.napps_repositories)
189
        options.debug = options.debug in ['True', True]
190
        options.daemon = options.daemon in ['True', True]
191
        options.port = int(options.port)
192
        options.api_port = int(options.api_port)
193
        options.api_traceback_on_500 = options.api_traceback_on_500 in ['True',
194
                                                                        True]
195
        options.protocol_name = str(options.protocol_name)
196
        options.token_expiration_minutes = int(options.
197
                                               token_expiration_minutes)
198
        result = options.enable_entities_by_default in ['True', True]
199
        options.enable_entities_by_default = result
200
201
        def _parse_json(value):
202
            """Parse JSON lists and dicts from the config file."""
203
            if isinstance(value, str):
204
                return json.loads(value)
205
            return value
206
207
        options.logger_decorators = _parse_json(options.logger_decorators)
208
        options.napps_pre_installed = _parse_json(options.napps_pre_installed)
209
        options.vlan_pool = _parse_json(options.vlan_pool)
210
        options.authenticate_urls = _parse_json(options.authenticate_urls)
211
        thread_pool_max_workers = options.thread_pool_max_workers
212
        options.thread_pool_max_workers = _parse_json(thread_pool_max_workers)
213
214
        return options
215
216
    @staticmethod
217
    def options_exposed(daemon_options: dict) -> dict:
218
        """Options exposed on API."""
219
        options = dict(daemon_options)
220
        for key in ["jwt_secret"]:
221
            options.pop(key, None)
222
        return options
223
224
225
def _render_config_templates(templates,
226
                             destination=Path(__file__).parent,
227
                             **kwargs):
228
    """Create a config file based on a template file.
229
230
    If no destination is passed, the new conf file will be created on the
231
    directory of the template file.
232
233
    Args:
234
        template (string):    Path of the template file
235
        destination (string): Directory in which the config file will
236
                              be placed.
237
    """
238
    if str(kwargs['prefix']) != '/':
239
        kwargs['prefix'] = Path(str(kwargs['prefix']).rstrip('/'))
240
    kwargs['jwt_secret'] = uuid.uuid4().hex
241
242
    # Create the paths used by Kytos.
243
    directories = [os.path.join(BASE_ENV, ETC_KYTOS)]
244
    for directory in directories:
245
        os.makedirs(directory, exist_ok=True)
246
247
    tmpl_path = Path(os.path.abspath(os.path.dirname(__file__))).parent
248
249
    for tmpl in templates:
250
        path = os.path.join(tmpl_path, tmpl)
251
        with open(path, 'r', encoding='utf-8') as src_file:
252
            content = Template(src_file.read()).render(**kwargs)
253
            tmpl = tmpl.replace('templates', ETC_KYTOS) \
254
                       .replace('.template', '')
255
            dst_path = Path(destination) / tmpl
256
            with open(dst_path, 'w', encoding="utf8") as dst_file:
257
                dst_file.write(content)
258