Test Failed
Push — master ( ef441d...0acd10 )
by Heiko 'riot'
04:41 queued 10s
created

isomer.launcher.Core._on_signal()   A

Complexity

Conditions 2

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nop 3
dl 0
loc 6
rs 10
c 0
b 0
f 0
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
from isomer.misc.path import set_instance, get_path
52
from isomer.component import handler, ConfigurableComponent, ComponentDisabled, BaseMeta
53
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
89
    pass
90
91
92
class cli_components(Event):
93
    """List registered and running components"""
94
95
    pass
96
97
98
class cli_reload_db(Event):
99
    """Reload database and schemata (Dangerous!) WiP - does nothing right now"""
100
101
    pass
102
103
104
class cli_reload(Event):
105
    """Reload all components and data models"""
106
107
    pass
108
109
110
class cli_info(Event):
111
    """Provide information about the running instance"""
112
113
    verbose = False
114
115
116
class cli_quit(Event):
117
    """Stop this instance
118
119
    Uses sys.exit() to quit.
120
    """
121
122
    pass
123
124
125
class cli_drop_privileges(Event):
126
    """Try to drop possible root privileges"""
127
128
    pass
129
130
131
class cli_check_provisions(Event):
132
    """Check current provisioning state and trigger new provisioning"""
133
134
    pass
135
136
137
class FrontendHandler(pyinotify.ProcessEvent):
138
    def __init__(self, launcher, *args, **kwargs):
139
        """Initialize the frontend handler"""
140
        super(FrontendHandler, self).__init__(*args, **kwargs)
141
        self.launcher = launcher
142
143
    def process_IN_CLOSE_WRITE(self, event):
144
        isolog("Frontend change:", event, emitter="FRONTENDHANDLER")
145
        install_frontend(install=False, development=True)
146
147
148 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...
149
    """Attempt to drop privileges and change user to 'isomer' user/group"""
150
151
    if os.getuid() != 0:
152
        isolog("Not root, cannot drop privileges", lvl=warn, emitter="CORE")
153
        return
154
155
    try:
156
        # Get the uid/gid from the name
157
        running_uid = pwd.getpwnam(uid_name).pw_uid
158
        running_gid = grp.getgrnam(gid_name).gr_gid
159
160
        # Remove group privileges
161
        os.setgroups([])
162
163
        # Try setting the new uid/gid
164
        os.setgid(running_gid)
165
        os.setuid(running_uid)
166
167
        # Ensure a very conservative umask
168
        # old_umask = os.umask(22)
169
        isolog("Privileges dropped", emitter="CORE")
170
    except Exception as e:
171
        isolog(
172
            "Could not drop privileges:",
173
            e,
174
            type(e),
175
            exc=True,
176
            lvl=error,
177
            emitter="CORE",
178
        )
179
180
181
class Core(ConfigurableComponent):
182
    """Isomer Core Backend Application"""
183
184
    # TODO: Move most of this stuff over to a new FrontendBuilder
185
186
    configprops = {
187
        "enabled": {
188
            "type": "array",
189
            "title": "Available modules",
190
            "description": "Modules found and activatable by the system.",
191
            "default": [],
192
            "items": {"type": "string"},
193
        },
194
        "components": {
195
            "type": "object",
196
            "title": "Components",
197
            "description": "Component metadata",
198
            "default": {},
199
        },
200
        "frontendenabled": {
201
            "type": "boolean",
202
            "title": "Frontend enabled",
203
            "description": "Option to toggle frontend activation",
204
            "default": True,
205
        },
206
    }
207
208
    def __init__(self, name, instance, **kwargs):
209
        super(Core, self).__init__("CORE", **kwargs)
210
        self.log("Starting system (channel ", self.channel, ")")
211
212
        self.insecure = kwargs["insecure"]
213
        self.development = kwargs["dev"]
214
215
        self.instance = name
216
217
        host = kwargs.get("web_address", None)
218
        port = kwargs.get("web_port", None)
219
220
        # self.log(instance, pretty=True, lvl=verbose)
221
222
        self.host = instance["web_address"] if host is None else host
223
        self.port = instance["web_port"] if port is None else port
224
225
        self.log("Web configuration: %s:%i" % (self.host, int(self.port)), lvl=debug)
226
227
        self.certificate = certificate = (
228
            instance["web_certificate"] if instance["web_certificate"] != "" else None
229
        )
230
231
        if certificate:
232
            if not os.path.exists(certificate):
233
                self.log(
234
                    "SSL certificate usage requested but certificate "
235
                    "cannot be found!",
236
                    lvl=error,
237
                )
238
                abort(EXIT_NO_CERTIFICATE)
239
240
        # TODO: Find a way to synchronize this with the paths in i.u.builder
241 View Code Duplication
        if self.development:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
242
            self.frontend_root = os.path.abspath(
243
                os.path.dirname(os.path.realpath(__file__)) + "/../frontend"
244
            )
245
            self.frontend_target = get_path("lib", "frontend-dev")
246
            self.module_root = os.path.abspath(
247
                os.path.dirname(os.path.realpath(__file__)) + "/../modules"
248
            )
249
        else:
250
            self.frontend_root = get_path("lib", "repository/frontend")
251
            self.frontend_target = get_path("lib", "frontend")
252
            self.module_root = ""
253
254
        self.log(
255
            "Frontend & module paths:",
256
            self.frontend_root,
257
            self.frontend_target,
258
            self.module_root,
259
            lvl=verbose,
260
        )
261
262
        self.modules_loaded = {}
263
        self.loadable_components = {}
264
        self.loaded_components = {}
265
266
        self.frontend_running = False
267
        self.frontend_watcher = None
268
        self.frontend_watch_manager = None
269
270
        self.static = None
271
        self.websocket = None
272
273
        self.component_blacklist = instance["environments"][instance["environment"]][
274
            "blacklist"
275
        ]
276
277
        self.component_blacklist += list(kwargs.get("blacklist", []))
278
279
        self._check_provisions()
280
        self.update_components()
281
        self._write_config()
282
283
        self.server = None
284
285
        if self.insecure:
286
            self.log("Not dropping privileges - this may be insecure!", lvl=warn)
287
288
    @handler("started", channel="*")
289
    def ready(self, source):
290
        """All components have initialized, set up the component
291
        configuration schema-store, run the local server and drop privileges"""
292
293
        from isomer.schemastore import configschemastore
294
295
        configschemastore[self.name] = self.configschema
296
297
        self._start_server()
298
299
        if not self.insecure:
300
            self._drop_privileges()
301
302
        self.fireEvent(cli_register_event("components", cli_components))
303
        self.fireEvent(cli_register_event("drop_privileges", cli_drop_privileges))
304
        self.fireEvent(cli_register_event("check_provisions", cli_check_provisions))
305
        self.fireEvent(cli_register_event("reload_db", cli_reload_db))
306
        self.fireEvent(cli_register_event("reload", cli_reload))
307
        self.fireEvent(cli_register_event("quit", cli_quit))
308
        self.fireEvent(cli_register_event("info", cli_info))
309
310
        self.fireEvent(boot(), "*")
311
312
    @handler("frontendbuildrequest", channel="setup")
313
    def trigger_frontend_build(self, event):
314
        """Event hook to trigger a new frontend build"""
315
316
        install_frontend(
317
            force_rebuild=event.force,
318
            install=event.install,
319
            development=self.development,
320
        )
321
        self.log("Frontend install done")
322
323
    @handler("cli_drop_privileges")
324
    def cli_drop_privileges(self, event):
325
        """Drop possible user privileges"""
326
327
        self.log("Trying to drop privileges", lvl=debug)
328
        self._drop_privileges()
329
330
    @handler("cli_check_provisions")
331
    def cli_check_provisions(self, event):
332
        """Check current provisioning state and trigger new provisioning"""
333
334
        self.log("Checking provisions", lvl=debug)
335
        self._check_provisions()
336
337
    @handler("cli_components")
338
    def cli_components(self, event):
339
        """List all loaded and running unique components"""
340
341
        self.log("Loaded components: ", sorted(self.loaded_components.keys()))
342
        self.log("Running unique components: ", sorted(self.names))
343
344
    @handler("cli_reload_db")
345
    def cli_reload_db(self, event):
346
        """Experimental call to reload the database"""
347
348
        self.log("This command is WiP.")
349
350
        initialize()
351
352
    @handler("cli_reload")
353
    def cli_reload(self, event):
354
        """Experimental call to reload the component tree"""
355
356
        self.log("Reloading all components.")
357
358
        self.update_components(forcereload=True)
359
        initialize()
360
361
        from isomer.debugger import cli_comp_graph
362
363
        self.fireEvent(cli_comp_graph())
364
365
    @handler("cli_quit")
366
    def cli_quit(self, event):
367
        """Stop the instance on cli request"""
368
369
        self.log("Quitting on CLI request.")
370
        if self.frontend_watcher is not None:
371
            self.frontend_watcher.stop()
372
            self.frontend_watcher = None
373
374
        if self.context.params["dev"] is False:
375
            self.fireEvent(system_stop())
376
        else:
377
            self.log("Stopping immediately due to --dev flag", lvl=warn)
378
            self.stop_core(None)
379
380
    @handler("system_stop")
381
    def system_stop(self):
382
        """Stop instance after settling stop events"""
383
384
        self.log("Initiating stop")
385
        Timer(5, Event.create("stop_core")).register(self)
386
387
    @handler("signal", channel="*")
388
    def _on_signal(self, signo, stack):
389
        """Handle abort signals"""
390
        if signo in [2, 15]:
391
            self.log("Initiating stop")
392
            Timer(5, Event.create("stop_core")).register(self)
393
394
    @handler("stop_core")
395
    def stop_core(self, event):
396
        """Stop execution and exit"""
397
398
        self.log("Stopping execution")
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
                mask = (
619
                    pyinotify.IN_DELETE | pyinotify.IN_CREATE | pyinotify.IN_CLOSE_WRITE
620
                )
621
                self.log("Frontend root:", self.frontend_root, lvl=debug)
622
                self.frontend_watch_manager.add_watch(self.module_root, mask, rec=True)
623
624
    def _check_provisions(self):
625
        from isomer.database import objectmodels
626
627
        systemconfig = objectmodels["systemconfig"].find_one({"active": True})
628
629
        if systemconfig is None:
630
            self.log("No system configuration found, trying tp provision", lvl=warn)
631
            provision()
632
        else:
633
            provisioned_packages = set(systemconfig.provisions["packages"])
634
            provision_store = set(build_provision_store().keys())
635
            missing_provisions = provision_store - provisioned_packages
636
            self.log("Provisioned packages:", provisioned_packages, lvl=debug)
637
            self.log("Available provisions:", provision_store, lvl=debug)
638
            if len(missing_provisions) > 0:
639
                self.log("Installing missing provisions:", missing_provisions)
640
                provision(installed=provisioned_packages)
641
642 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...
643
        """Inspect all loadable components and run them"""
644
645
        if clear:
646
            # import objgraph
647
            # from copy import deepcopy
648
            from circuits.tools import kill
649
            from circuits import Component
650
651
            for comp in self.loaded_components.values():
652
                self.log(comp, type(comp), isinstance(comp, Component), pretty=True)
653
                kill(comp)
654
            # removables = deepcopy(list(self.runningcomponents.keys()))
655
            #
656
            # for key in removables:
657
            #     comp = self.runningcomponents[key]
658
            #     self.log(comp)
659
            #     comp.unregister()
660
            #     comp.stop()
661
            #     self.runningcomponents.pop(key)
662
            #
663
            #     objgraph.show_backrefs([comp],
664
            #                            max_depth=5,
665
            #                            filter=lambda x: type(x) not in [list, tuple, set],
666
            #                            highlight=lambda x: type(x) in [ConfigurableComponent],
667
            #                            filename='backref-graph_%s.png' % comp.uniquename)
668
            #     del comp
669
            # del removables
670
            self.loaded_components = {}
671
672
        self.log(
673
            "Not running blacklisted components: ", self.component_blacklist, lvl=debug
674
        )
675
676
        running = set(self.loadable_components.keys()).difference(
677
            self.component_blacklist
678
        )
679
        self.log("Starting components: ", sorted(running))
680
        for name, componentdata in self.loadable_components.items():
681
            if name in self.component_blacklist:
682
                continue
683
            self.log("Running component: ", name, lvl=debug)
684
            try:
685
                if name in self.loaded_components:
686
                    self.log("Component already running: ", name, lvl=warn)
687
                else:
688
                    try:
689
                        runningcomponent = componentdata()
690
                    except ComponentDisabled:
691
                        self.log("Not registering disabled component", lvl=debug)
692
                        continue
693
694
                    runningcomponent.register(self)
695
                    self.loaded_components[name] = runningcomponent
696
            except Exception as e:
697
                self.log(
698
                    "Could not register component: ",
699
                    name,
700
                    e,
701
                    type(e),
702
                    lvl=error,
703
                    exc=True,
704
                )
705
706 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...
707
        """Sets up the application after startup."""
708
709
        self.log("Running.")
710
        self.log("Started event origin: ", component, lvl=verbose)
711
        populate_user_events()
712
713
        from isomer.events.system import AuthorizedEvents
714
715
        self.log(
716
            len(AuthorizedEvents),
717
            "authorized event sources:",
718
            list(AuthorizedEvents.keys()),
719
            lvl=debug,
720
        )
721
722
        self._instantiate_components()
723
        self._start_frontend()
724
        self.fire(ready(), "isomer-web")
725
726
727 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...
728
    """Preliminary Isomer application Launcher"""
729
730
    app = Core(name, instance, **args)
731
732
    # TODO: This should probably be read-only
733
    BaseMeta.context = ctx
734
735
    setup_root(app)
736
737
    if args["debug"]:
738
        from circuits import Debugger
739
740
        isolog("Starting circuits debugger", lvl=warn, emitter="GRAPH")
741
        dbg = Debugger().register(app)
742
        # TODO: Make these configurable from modules, navdata is _very_ noisy
743
        # but should not be listed _here_
744
        dbg.IgnoreEvents.extend(
745
            [
746
                "read",
747
                "_read",
748
                "write",
749
                "_write",
750
                "stream_success",
751
                "stream_complete",
752
                "serial_packet",
753
                "raw_data",
754
                "stream",
755
                "navdatapush",
756
                "referenceframe",
757
                "updateposition",
758
                "updatesubscriptions",
759
                "generatevesseldata",
760
                "generatenavdata",
761
                "sensordata",
762
                "reset_flood_offenders",
763
                "reset_flood_counters",  # Flood counters
764
                "task_success",
765
                "task_done",  # Thread completion
766
                "keepalive",  # IRC Gateway
767
                "peek",  # AVIO and others
768
                "joystickchange",  # AVIO
769
            ]
770
        )
771
772
    isolog("Beginning graph assembly.", emitter="GRAPH")
773
774
    if args["draw_graph"]:
775
        from circuits.tools import graph
776
777
        graph(app)
778
779
    if args["open_gui"]:
780
        import webbrowser
781
782
        # TODO: Fix up that url:
783
        webbrowser.open("http://%s:%i/" % (args["host"], args["port"]))
784
785
    isolog("Graph assembly done.", emitter="GRAPH")
786
787
    return app
788
789
790
@click.command()
791
@click.option(
792
    "--web-port", "-p", help="Define port for UI server", type=int, default=None
793
)
794
@click.option(
795
    "--web-address",
796
    "-a",
797
    help="Define listening address for UI server",
798
    type=str,
799
    default=None,
800
)
801
@click.option(
802
    "--web-certificate", "-c", help="Certificate file path", type=str, default=None
803
)
804
@click.option("--profile", help="Enable profiler", is_flag=True)
805
@click.option(
806
    "--open-gui",
807
    help="Launch web browser for GUI inspection after startup",
808
    is_flag=True,
809
)
810
@click.option(
811
    "--draw-graph",
812
    help="Draw a snapshot of the component graph after construction",
813
    is_flag=True,
814
)
815
@click.option("--live-log", help="Log to in-memory structure as well", is_flag=True)
816
@click.option("--debug", help="Run circuits debugger", is_flag=True)
817
@click.option("--dev", help="Run development server", is_flag=True, default=False)
818
@click.option("--insecure", help="Keep privileges - INSECURE", is_flag=True)
819
@click.option("--no-run", "-n", help="Only assemble system, do not run", is_flag=True)
820
@click.option(
821
    "--blacklist",
822
    "-b",
823
    help="Blacklist a component (can be repeated)",
824
    multiple=True,
825
    default=[],
826
)
827
@click.pass_context
828
def launch(ctx, run=True, **args):
829
    """Assemble and run an Isomer instance"""
830
831
    instance_name = ctx.obj["instance"]
832
    instance = load_instance(instance_name)
833
    environment_name = ctx.obj["environment"]
834
835
    isolog("Launching instance %s - (%s)" % (instance_name, environment_name))
836
837
    database_host = ctx.obj["dbhost"]
838
    database_name = ctx.obj["dbname"]
839
840
    if ctx.params["live_log"] is True:
841
        from isomer import logger
842
843
        logger.live = True
844
845
    if args["web_certificate"] is not None:
846
        isolog(
847
            "Warning! Using SSL on the backend is currently not recommended!",
848
            lvl=critical,
849
            emitter="CORE",
850
        )
851
852
    isolog("Initializing database access", emitter="CORE", lvl=debug)
853
    initialize(database_host, database_name, instance_name)
854
    isolog("Setting instance paths", emitter="CORE", lvl=debug)
855
    set_instance(instance_name, environment_name)
856
857
    server = construct_graph(ctx, instance_name, instance, args)
858
    if run and not args["no_run"]:
859
        server.run()
860
861
    return server
862