launcher   F
last analyzed

Complexity

Total Complexity 74

Size/Duplication

Total Lines 867
Duplicated Lines 28.03 %

Importance

Changes 0
Metric Value
eloc 506
dl 243
loc 867
rs 2.48
c 0
b 0
f 0
wmc 74

22 Methods

Rating   Name   Duplication   Size   Complexity  
A Core._drop_privileges() 0 3 1
A Core.trigger_frontend_build() 0 10 1
D Core.update_components() 0 124 11
A Core.stop_core() 0 9 1
B Core._instantiate_components() 62 62 8
C Core.__init__() 12 79 8
A Core.cli_drop_privileges() 0 6 1
A Core.ready() 0 23 2
A Core._on_signal() 0 6 2
A Core.cli_reload() 0 12 1
A Core.started() 19 19 1
A Core.cli_quit() 0 14 3
A FrontendHandler.process_IN_CLOSE_WRITE() 0 3 1
A Core._start_frontend() 22 23 5
A Core.system_stop() 0 6 1
A FrontendHandler.__init__() 0 4 1
A Core.cli_reload_db() 0 7 1
A Core._check_provisions() 0 17 3
A Core.cli_components() 0 6 1
A Core.cli_info() 0 24 2
A Core.cli_check_provisions() 0 6 1
B Core._start_server() 37 37 6

3 Functions

Rating   Name   Duplication   Size   Complexity  
B construct_graph() 61 61 4
B launch() 0 76 5
A drop_privileges() 30 30 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like launcher often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
#!/usr/bin/env python
2
# -*- coding: UTF-8 -*-
3
4
# Isomer - The distributed application framework
5
# ==============================================
6
# Copyright (C) 2011-2020 Heiko 'riot' Weinen <[email protected]> and others.
7
#
8
# This program is free software: you can redistribute it and/or modify
9
# it under the terms of the GNU Affero General Public License as published by
10
# the Free Software Foundation, either version 3 of the License, or
11
# (at your option) any later version.
12
#
13
# This program is distributed in the hope that it will be useful,
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
# GNU Affero General Public License for more details.
17
#
18
# You should have received a copy of the GNU Affero General Public License
19
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
21
"""
22
Isomer - Backend
23
24
Application
25
===========
26
27
See README.rst for Build/Installation and setup details.
28
29
URLs & Contact
30
==============
31
32
Mail: [email protected]
33
IRC: #[email protected]
34
35
Project repository: http://github.com/isomeric/isomer
36
Frontend repository: http://github.com/isomeric/isomer-frontend
37
38
39
"""
40
41
import grp
42
import pwd
43
import sys
44
import os
45
46
import pyinotify
47
import click
48
from circuits import Event, Timer
49
from circuits.web import Server, Static
50
from circuits.web.websockets.dispatcher import WebSocketsDispatcher
51
52
from isomer.misc.path import set_instance, get_path
53
from isomer.component import handler, ConfigurableComponent, ComponentDisabled, BaseMeta
54
# from isomer.schemata.component import ComponentBaseConfigSchema
55
from isomer.database import initialize  # , schemastore
56
from isomer.events.system import populate_user_events, system_stop
57
from isomer.logger import (
58
    isolog,
59
    verbose,
60
    debug,
61
    warn,
62
    error,
63
    critical,
64
    setup_root,
65
)
66
from isomer.debugger import cli_register_event
67
from isomer.ui.builder import install_frontend
68
from isomer.error import abort, EXIT_NO_CERTIFICATE
69
from isomer.tool.etc import load_instance
70
from isomer.provisions import build_provision_store
71
from isomer.provisions.base import provision
72
73
74
# from circuits.web.errors import redirect
75
# from circuits.app.daemon import Daemon
76
77
78
# from pprint import pprint
79
80
81
class ready(Event):
82
    """Event fired to signal completeness of the local node's setup"""
83
84
    pass
85
86
87
class boot(Event):
88
    pass
89
90
91
class cli_components(Event):
92
    """List registered and running components"""
93
94
    pass
95
96
97
class cli_reload_db(Event):
98
    """Reload database and schemata (Dangerous!) WiP - does nothing right now"""
99
100
    pass
101
102
103
class cli_reload(Event):
104
    """Reload all components and data models"""
105
106
    pass
107
108
109
class cli_info(Event):
110
    """Provide information about the running instance"""
111
112
    verbose = False
113
114
115
class cli_quit(Event):
116
    """Stop this instance
117
118
    Uses sys.exit() to quit.
119
    """
120
121
    pass
122
123
124
class cli_drop_privileges(Event):
125
    """Try to drop possible root privileges"""
126
127
    pass
128
129
130
class cli_check_provisions(Event):
131
    """Check current provisioning state and trigger new provisioning"""
132
133
    pass
134
135
136
class FrontendHandler(pyinotify.ProcessEvent):
137
    def __init__(self, launcher, *args, **kwargs):
138
        """Initialize the frontend handler"""
139
        super(FrontendHandler, self).__init__(*args, **kwargs)
140
        self.launcher = launcher
141
142
    def process_IN_CLOSE_WRITE(self, event):
143
        isolog("Frontend change:", event, emitter="FRONTENDHANDLER")
144
        install_frontend(install=False, development=True)
145
146
147 View Code Duplication
def drop_privileges(uid_name="isomer", gid_name="isomer"):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
148
    """Attempt to drop privileges and change user to 'isomer' user/group"""
149
150
    if os.getuid() != 0:
151
        isolog("Not root, cannot drop privileges", lvl=warn, emitter="CORE")
152
        return
153
154
    try:
155
        # Get the uid/gid from the name
156
        running_uid = pwd.getpwnam(uid_name).pw_uid
157
        running_gid = grp.getgrnam(gid_name).gr_gid
158
159
        # Remove group privileges
160
        os.setgroups([])
161
162
        # Try setting the new uid/gid
163
        os.setgid(running_gid)
164
        os.setuid(running_uid)
165
166
        # Ensure a very conservative umask
167
        # old_umask = os.umask(22)
168
        isolog("Privileges dropped", emitter="CORE")
169
    except Exception as e:
170
        isolog(
171
            "Could not drop privileges:",
172
            e,
173
            type(e),
174
            exc=True,
175
            lvl=error,
176
            emitter="CORE",
177
        )
178
179
180
class Core(ConfigurableComponent):
181
    """Isomer Core Backend Application"""
182
183
    # TODO: Move most of this stuff over to a new FrontendBuilder
184
185
    configprops = {
186
        "enabled": {
187
            "type": "array",
188
            "title": "Available modules",
189
            "description": "Modules found and activatable by the system.",
190
            "default": [],
191
            "items": {"type": "string"},
192
        },
193
        "components": {
194
            "type": "object",
195
            "title": "Components",
196
            "description": "Component metadata",
197
            "default": {},
198
        },
199
        "frontendenabled": {
200
            "type": "boolean",
201
            "title": "Frontend enabled",
202
            "description": "Option to toggle frontend activation",
203
            "default": True,
204
        },
205
    }
206
207
    def __init__(self, name, instance, **kwargs):
208
        super(Core, self).__init__("CORE", **kwargs)
209
        self.log("Starting system (channel ", self.channel, ")")
210
211
        self.insecure = kwargs["insecure"]
212
        self.development = kwargs["dev"]
213
214
        self.instance = name
215
216
        host = kwargs.get("web_address", None)
217
        port = kwargs.get("web_port", None)
218
219
        # self.log(instance, pretty=True, lvl=verbose)
220
221
        self.host = instance["web_address"] if host is None else host
222
        self.port = instance["web_port"] if port is None else port
223
224
        self.log("Web configuration: %s:%i" % (self.host, int(self.port)), lvl=debug)
225
226
        self.certificate = certificate = (
227
            instance["web_certificate"] if instance["web_certificate"] != "" else None
228
        )
229
230
        if certificate:
231
            if not os.path.exists(certificate):
232
                self.log(
233
                    "SSL certificate usage requested but certificate "
234
                    "cannot be found!",
235
                    lvl=error,
236
                )
237
                abort(EXIT_NO_CERTIFICATE)
238
239
        # TODO: Find a way to synchronize this with the paths in i.u.builder
240 View Code Duplication
        if self.development:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
241
            self.frontend_root = os.path.abspath(
242
                os.path.dirname(os.path.realpath(__file__)) + "/../frontend"
243
            )
244
            self.frontend_target = get_path("lib", "frontend-dev")
245
            self.module_root = os.path.abspath(
246
                os.path.dirname(os.path.realpath(__file__)) + "/../modules"
247
            )
248
        else:
249
            self.frontend_root = get_path("lib", "repository/frontend")
250
            self.frontend_target = get_path("lib", "frontend")
251
            self.module_root = ""
252
253
        self.log(
254
            "Frontend & module paths:",
255
            self.frontend_root,
256
            self.frontend_target,
257
            self.module_root,
258
            lvl=verbose,
259
        )
260
261
        self.modules_loaded = {}
262
        self.loadable_components = {}
263
        self.loaded_components = {}
264
265
        self.frontend_running = False
266
        self.frontend_watcher = None
267
        self.frontend_watch_manager = None
268
269
        self.static = None
270
        self.websocket = None
271
272
        self.component_blacklist = instance["environments"][instance["environment"]][
273
            "blacklist"
274
        ]
275
276
        self.component_blacklist += list(kwargs.get("blacklist", []))
277
278
        self._check_provisions()
279
        self.update_components()
280
        self._write_config()
281
282
        self.server = None
283
284
        if self.insecure:
285
            self.log("Not dropping privileges - this may be insecure!", lvl=warn)
286
287
    @handler("started", channel="*")
288
    def ready(self, source):
289
        """All components have initialized, set up the component
290
        configuration schema-store, run the local server and drop privileges"""
291
292
        from isomer.schemastore import configschemastore
293
294
        configschemastore[self.name] = self.configschema
295
296
        self._start_server()
297
298
        if not self.insecure:
299
            self._drop_privileges()
300
301
        self.fireEvent(cli_register_event("components", cli_components))
302
        self.fireEvent(cli_register_event("drop_privileges", cli_drop_privileges))
303
        self.fireEvent(cli_register_event("check_provisions", cli_check_provisions))
304
        self.fireEvent(cli_register_event("reload_db", cli_reload_db))
305
        self.fireEvent(cli_register_event("reload", cli_reload))
306
        self.fireEvent(cli_register_event("quit", cli_quit))
307
        self.fireEvent(cli_register_event("info", cli_info))
308
309
        self.fireEvent(boot(), "*")
310
311
    @handler("frontendbuildrequest", channel="setup")
312
    def trigger_frontend_build(self, event):
313
        """Event hook to trigger a new frontend build"""
314
315
        install_frontend(
316
            force_rebuild=event.force,
317
            install=event.install,
318
            development=self.development,
319
        )
320
        self.log("Frontend install done")
321
322
    @handler("cli_drop_privileges")
323
    def cli_drop_privileges(self, event):
324
        """Drop possible user privileges"""
325
326
        self.log("Trying to drop privileges", lvl=debug)
327
        self._drop_privileges()
328
329
    @handler("cli_check_provisions")
330
    def cli_check_provisions(self, event):
331
        """Check current provisioning state and trigger new provisioning"""
332
333
        self.log("Checking provisions", lvl=debug)
334
        self._check_provisions()
335
336
    @handler("cli_components")
337
    def cli_components(self, event):
338
        """List all loaded and running unique components"""
339
340
        self.log("Loaded components: ", sorted(self.loaded_components.keys()))
341
        self.log("Running unique components: ", sorted(self.names))
342
343
    @handler("cli_reload_db")
344
    def cli_reload_db(self, event):
345
        """Experimental call to reload the database"""
346
347
        self.log("This command is WiP.")
348
349
        initialize()
350
351
    @handler("cli_reload")
352
    def cli_reload(self, event):
353
        """Experimental call to reload the component tree"""
354
355
        self.log("Reloading all components.")
356
357
        self.update_components(forcereload=True)
358
        initialize()
359
360
        from isomer.debugger import cli_comp_graph
361
362
        self.fireEvent(cli_comp_graph())
363
364
    @handler("cli_quit")
365
    def cli_quit(self, event):
366
        """Stop the instance on cli request"""
367
368
        self.log("Quitting on CLI request.")
369
        if self.frontend_watcher is not None:
370
            self.frontend_watcher.stop()
371
            self.frontend_watcher = None
372
373
        if self.context.params["dev"] is False:
374
            self.fireEvent(system_stop())
375
        else:
376
            self.log("Stopping immediately due to --dev flag", lvl=warn)
377
            self.stop_core(None)
378
379
    @handler("system_stop")
380
    def system_stop(self):
381
        """Stop instance after settling stop events"""
382
383
        self.log("Initiating stop")
384
        Timer(5, Event.create("stop_core")).register(self)
385
386
    @handler("signal", channel="*")
387
    def _on_signal(self, signo, stack):
388
        """Handle abort signals"""
389
        if signo in [2, 15]:
390
            self.log("Initiating stop")
391
            Timer(5, Event.create("stop_core")).register(self)
392
393
    @handler("stop_core")
394
    def stop_core(self, event):
395
        """Stop execution and exit"""
396
397
        self.log("Stopping execution.")
398
        self.log("Source:", event, lvl=verbose)
399
400
        self.stop()
401
        sys.exit()
402
403
    @handler("cli_info")
404
    def cli_info(self, *args):
405
        """Provides information about the running instance"""
406
407
        from isomer.database import dbname, dbhost, dbport
408
409
        self.log(
410
            "Instance: %s DB: %s Dev: %s Host: %s Port: %s Insecure: %s Frontend: %s\n"
411
            "Modules:"
412
            % (
413
                self.instance,
414
                "%s@%s:%i" % (dbname, dbhost, dbport),
415
                self.development,
416
                self.host,
417
                self.port,
418
                self.insecure,
419
                self.frontend_target,
420
            ),
421
            self.modules_loaded,
422
            pretty=True,
423
        )
424
425
        if "-v" in args:
426
            self.log("Context:", self.context.obj, pretty=True)
427
428 View Code Duplication
    def _start_server(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
429
        """Run the node local server"""
430
431
        self.log("Starting server")
432
        secure = self.certificate is not None
433
        if secure:
434
            self.log("Running SSL server with cert:", self.certificate)
435
        else:
436
            self.log(
437
                "Running insecure server without SSL. Do not use without SSL "
438
                "proxy in production!",
439
                lvl=warn,
440
            )
441
442
        try:
443
            self.server = Server(
444
                (self.host, self.port),
445
                display_banner=False,
446
                secure=secure,
447
                certfile=self.certificate  # ,
448
                # inherit=True
449
            ).register(self)
450
        except PermissionError as e:
451
            if self.port <= 1024:
452
                self.log(
453
                    "Could not open privileged port (%i), check permissions!"
454
                    % self.port,
455
                    e,
456
                    lvl=critical,
457
                )
458
            else:
459
                self.log("Could not open port (%i):" % self.port, e, lvl=critical)
460
        except OSError as e:
461
            if e.errno == 98:
462
                self.log("Port (%i) is already opened!" % self.port, lvl=critical)
463
            else:
464
                self.log("Could not open port (%i):" % self.port, e, lvl=critical)
465
466
    def _drop_privileges(self, *args):
467
        self.log("Dropping privileges", lvl=debug)
468
        drop_privileges()
469
470
    # Moved to manage tool, maybe of interest later, though:
471
    #
472
    # @handler("componentupdaterequest", channel="setup")
473
    # def trigger_component_update(self, event):
474
    #     self.update_components(forcereload=event.force)
475
476
    def update_components(
477
            self, forcereload=False, forcerebuild=False, forcecopy=True, install=False
478
    ):
479
        """Check all known entry points for components. If necessary,
480
        manage configuration updates"""
481
482
        # TODO: See if we can pull out major parts of the component handling.
483
        #  They are also used in the manage-tool to instantiate the
484
        #  component frontend bits.
485
486
        self.log("Updating components")
487
        components = {}
488
        packages = {}
489
490
        try:
491
492
            from pkg_resources import iter_entry_points
493
494
            entry_point_tuple = (
495
                iter_entry_points(group="isomer.base", name=None),
496
                iter_entry_points(group="isomer.sails", name=None),
497
                iter_entry_points(group="isomer.components", name=None),
498
            )
499
            self.log("Entrypoints:", entry_point_tuple, pretty=True, lvl=verbose)
500
            for iterator in entry_point_tuple:
501
                for entry_point in iterator:
502
                    self.log("Entrypoint:", entry_point, pretty=True, lvl=verbose)
503
                    try:
504
                        name = entry_point.name
505
                        package = entry_point.dist.project_name
506
                        version = str(entry_point.dist.parsed_version)
507
                        location = entry_point.dist.location
508
                        loaded = entry_point.load()
509
510
                        self.log(
511
                            "Entry point: ",
512
                            entry_point,
513
                            name,
514
                            entry_point.resolve(),
515
                            lvl=verbose,
516
                        )
517
518
                        module_name = location.split("/")[-1]
519
                        if module_name in self.modules_loaded:
520
                            self.modules_loaded[module_name].append(name)
521
                        else:
522
                            self.modules_loaded[module_name] = [name]
523
524
                        self.log("Loaded: ", loaded, lvl=verbose)
525
                        comp = {
526
                            "package": package,
527
                            "location": location,
528
                            "version": version,
529
                            "description": loaded.__doc__,
530
                        }
531
532
                        components[name] = comp
533
                        self.loadable_components[name] = loaded
534
535
                        packages.setdefault(
536
                            package, {"version": version, "name": package}
537
                        )
538
539
                        self.log("Loaded component:", comp, lvl=verbose)
540
541
                    except Exception as e:
542
                        self.log(
543
                            "Could not inspect entrypoint: ",
544
                            e,
545
                            type(e),
546
                            entry_point,
547
                            iterator,
548
                            lvl=error,
549
                            exc=True,
550
                        )
551
552
                        # for name in components.keys():
553
                        #     try:
554
                        #         self.log(self.loadable_components[name])
555
                        #         configobject = {
556
                        #             'type': 'object',
557
                        #             'properties':
558
                        # self.loadable_components[name].configprops
559
                        #         }
560
                        #         ComponentBaseConfigSchema['schema'][
561
                        # 'properties'][
562
                        #             'settings'][
563
                        #             'oneOf'].append(configobject)
564
                        #     except (KeyError, AttributeError) as e:
565
                        #         self.log('Problematic configuration
566
                        # properties in '
567
                        #                  'component ', name, exc=True)
568
                        #
569
                        # schemastore['component'] = ComponentBaseConfigSchema
570
571
        except Exception as e:
572
            self.log("Component update error: ", e, type(e), lvl=error, exc=True)
573
            return
574
575
        from isomer.database import objectmodels
576
577
        systemconfig = objectmodels["systemconfig"].find_one({"active": True})
578
579
        systemconfig.packages = sorted(list(packages.values()), key=lambda x: x["name"])
580
        systemconfig.save()
581
582
        # self.log(list(packages.values()), lvl=critical)
583
584
        self.log(
585
            "Checking component frontend bits in ", self.frontend_root, lvl=verbose
586
        )
587
588
        # pprint(self.config._fields)
589
        diff = set(components) ^ set(self.config.components)
590
        if diff or forcecopy and self.config.frontendenabled:
591
            self.log("Old component configuration differs:", diff, lvl=debug)
592
            self.log(self.config.components, components, lvl=verbose)
593
            self.config.components = components
594
        else:
595
            self.log("No component configuration change. Proceeding.")
596
597
        if forcereload:
598
            self.log("Restarting all components.", lvl=warn)
599
            self._instantiate_components(clear=True)
600
601 View Code Duplication
    def _start_frontend(self, restart=False):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
602
        """Check if it is enabled and start the frontend http & websocket"""
603
604
        self.log(self.config, self.config.frontendenabled, lvl=verbose)
605
        if self.config.frontendenabled and not self.frontend_running or restart:
606
            self.log("Restarting webfrontend services on", self.frontend_target)
607
608
            self.static = Static("/", docroot=self.frontend_target).register(self)
609
            self.websocket = WebSocketsDispatcher("/websocket").register(self)
610
            self.frontend_running = True
611
612
            if self.development:
613
                self.frontend_watch_manager = pyinotify.WatchManager()
614
                self.frontend_watcher = pyinotify.ThreadedNotifier(
615
                    self.frontend_watch_manager, FrontendHandler(self)
616
                )
617
                self.frontend_watcher.start()
618
                # noinspection PyUnresolvedReferences
619
                mask = (
620
                        pyinotify.IN_DELETE | pyinotify.IN_CREATE | pyinotify.IN_CLOSE_WRITE
621
                )
622
                self.log("Frontend root:", self.frontend_root, lvl=debug)
623
                self.frontend_watch_manager.add_watch(self.module_root, mask, rec=True)
624
625
    def _check_provisions(self):
626
        from isomer.database import objectmodels
627
628
        systemconfig = objectmodels["systemconfig"].find_one({"active": True})
629
630
        if systemconfig is None:
631
            self.log("No system configuration found, trying tp provision", lvl=warn)
632
            provision()
633
        else:
634
            provisioned_packages = set(systemconfig.provisions["packages"])
635
            provision_store = set(build_provision_store().keys())
636
            missing_provisions = provision_store - provisioned_packages
637
            self.log("Provisioned packages:", provisioned_packages, lvl=debug)
638
            self.log("Available provisions:", provision_store, lvl=debug)
639
            if len(missing_provisions) > 0:
640
                self.log("Installing missing provisions:", missing_provisions)
641
                provision(installed=provisioned_packages)
642
643 View Code Duplication
    def _instantiate_components(self, clear=True):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
644
        """Inspect all loadable components and run them"""
645
646
        if clear:
647
            # import objgraph
648
            # from copy import deepcopy
649
            from circuits.tools import kill
650
            from circuits import Component
651
652
            for comp in self.loaded_components.values():
653
                self.log(comp, type(comp), isinstance(comp, Component), pretty=True)
654
                kill(comp)
655
            # removables = deepcopy(list(self.runningcomponents.keys()))
656
            #
657
            # for key in removables:
658
            #     comp = self.runningcomponents[key]
659
            #     self.log(comp)
660
            #     comp.unregister()
661
            #     comp.stop()
662
            #     self.runningcomponents.pop(key)
663
            #
664
            #     objgraph.show_backrefs([comp],
665
            #                            max_depth=5,
666
            #                            filter=lambda x: type(x) not in [list, tuple, set],
667
            #                            highlight=lambda x: type(x) in [ConfigurableComponent],
668
            #                            filename='backref-graph_%s.png' % comp.uniquename)
669
            #     del comp
670
            # del removables
671
            self.loaded_components = {}
672
673
        self.log(
674
            "Not running blacklisted components: ", self.component_blacklist, lvl=debug
675
        )
676
677
        running = set(self.loadable_components.keys()).difference(
678
            self.component_blacklist
679
        )
680
        self.log("Starting components: ", sorted(running))
681
        for name, componentdata in self.loadable_components.items():
682
            if name in self.component_blacklist:
683
                continue
684
            self.log("Running component: ", name, lvl=debug)
685
            try:
686
                if name in self.loaded_components:
687
                    self.log("Component already running: ", name, lvl=warn)
688
                else:
689
                    try:
690
                        runningcomponent = componentdata()
691
                    except ComponentDisabled:
692
                        self.log("Not registering disabled component", lvl=debug)
693
                        continue
694
695
                    runningcomponent.register(self)
696
                    self.loaded_components[name] = runningcomponent
697
            except Exception as e:
698
                self.log(
699
                    "Could not register component: ",
700
                    name,
701
                    e,
702
                    type(e),
703
                    lvl=error,
704
                    exc=True,
705
                )
706
707 View Code Duplication
    def started(self, component):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
708
        """Sets up the application after startup."""
709
710
        self.log("Running.")
711
        self.log("Started event origin: ", component, lvl=verbose)
712
        populate_user_events()
713
714
        from isomer.events.system import AuthorizedEvents
715
716
        self.log(
717
            len(AuthorizedEvents),
718
            "authorized event sources:",
719
            list(AuthorizedEvents.keys()),
720
            lvl=debug,
721
        )
722
723
        self._instantiate_components()
724
        self._start_frontend()
725
        self.fire(ready(), "isomer-web")
726
727
728 View Code Duplication
def construct_graph(ctx, name, instance, args):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
729
    """Preliminary Isomer application Launcher"""
730
731
    app = Core(name, instance, **args)
732
733
    # TODO: This should probably be read-only
734
    BaseMeta.context = ctx
735
736
    setup_root(app)
737
738
    if args["debug"]:
739
        from circuits import Debugger
740
741
        isolog("Starting circuits debugger", lvl=warn, emitter="GRAPH")
742
        dbg = Debugger().register(app)
743
        # TODO: Make these configurable from modules, navdata is _very_ noisy
744
        # but should not be listed _here_
745
        dbg.IgnoreEvents.extend(
746
            [
747
                "read",
748
                "_read",
749
                "write",
750
                "_write",
751
                "stream_success",
752
                "stream_complete",
753
                "serial_packet",
754
                "raw_data",
755
                "stream",
756
                "navdatapush",
757
                "referenceframe",
758
                "updateposition",
759
                "updatesubscriptions",
760
                "generatevesseldata",
761
                "generatenavdata",
762
                "sensordata",
763
                "reset_flood_offenders",
764
                "reset_flood_counters",  # Flood counters
765
                "task_success",
766
                "task_done",  # Thread completion
767
                "keepalive",  # IRC Gateway
768
                "peek",  # AVIO and others
769
                "joystickchange",  # AVIO
770
            ]
771
        )
772
773
    isolog("Beginning graph assembly.", emitter="GRAPH")
774
775
    if args["draw_graph"]:
776
        from circuits.tools import graph
777
778
        graph(app)
779
780
    if args["open_gui"]:
781
        import webbrowser
782
783
        # TODO: Fix up that url:
784
        webbrowser.open("http://%s:%i/" % (args["host"], args["port"]))
785
786
    isolog("Graph assembly done.", emitter="GRAPH")
787
788
    return app
789
790
791
@click.command()
792
@click.option(
793
    "--web-port", "-p", help="Define port for UI server", type=int, default=None
794
)
795
@click.option(
796
    "--web-address",
797
    "-a",
798
    help="Define listening address for UI server",
799
    type=str,
800
    default=None,
801
)
802
@click.option(
803
    "--web-certificate", "-c", help="Certificate file path", type=str, default=None
804
)
805
@click.option("--profile", help="Enable profiler", is_flag=True)
806
@click.option(
807
    "--open-gui",
808
    help="Launch web browser for GUI inspection after startup",
809
    is_flag=True,
810
)
811
@click.option(
812
    "--draw-graph",
813
    help="Draw a snapshot of the component graph after construction",
814
    is_flag=True,
815
)
816
@click.option("--live-log", help="Log to in-memory structure as well", is_flag=True)
817
@click.option("--debug", help="Run circuits debugger", is_flag=True)
818
@click.option("--dev", help="Run development server", is_flag=True, default=False)
819
@click.option("--insecure", help="Keep privileges - INSECURE", is_flag=True)
820
@click.option("--no-run", "-n", help="Only assemble system, do not run", is_flag=True)
821
@click.option(
822
    "--blacklist",
823
    "-b",
824
    help="Blacklist a component (can be repeated)",
825
    multiple=True,
826
    default=[],
827
)
828
@click.pass_context
829
def launch(ctx, run=True, **args):
830
    """Assemble and run an Isomer instance"""
831
832
    instance_name = ctx.obj["instance"]
833
    instance = load_instance(instance_name)
834
    environment_name = ctx.obj["environment"]
835
836
    isolog(
837
        "Launching instance %s - (%s)" % (instance_name, environment_name),
838
        emitter="CORE",
839
        lvl=debug
840
    )
841
842
    database_host = ctx.obj["dbhost"]
843
    database_name = ctx.obj["dbname"]
844
845
    if ctx.params["live_log"] is True:
846
        from isomer import logger
847
848
        logger.live = True
849
850
    if args["web_certificate"] is not None:
851
        isolog(
852
            "Warning! Using SSL on the backend is currently not recommended!",
853
            lvl=critical,
854
            emitter="CORE",
855
        )
856
857
    isolog("Initializing database access", emitter="CORE", lvl=debug)
858
    initialize(database_host, database_name, instance_name)
859
    isolog("Setting instance paths", emitter="CORE", lvl=debug)
860
    set_instance(instance_name, environment_name)
861
862
    server = construct_graph(ctx, instance_name, instance, args)
863
    if run and not args["no_run"]:
864
        server.run()
865
866
    return server
867