2015-07-21 15:55:02 +00:00
|
|
|
# Copyright 2015 Canonical, Ltd.
|
|
|
|
#
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU Affero General Public License as
|
|
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
|
|
# License, or (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU Affero General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
2019-12-19 23:04:38 +00:00
|
|
|
import asyncio
|
2019-03-06 02:52:13 +00:00
|
|
|
import json
|
2015-07-21 15:55:02 +00:00
|
|
|
import logging
|
2018-02-07 21:37:22 +00:00
|
|
|
import os
|
2017-01-17 01:26:07 +00:00
|
|
|
|
2019-12-19 03:11:12 +00:00
|
|
|
from subiquitycore.context import (
|
|
|
|
Context,
|
|
|
|
)
|
2020-07-30 03:13:48 +00:00
|
|
|
from subiquitycore.controllerset import ControllerSet
|
2019-09-09 02:28:48 +00:00
|
|
|
from subiquitycore.prober import Prober
|
2020-07-27 11:20:41 +00:00
|
|
|
from subiquitycore.signals import Signal
|
2015-07-21 15:55:02 +00:00
|
|
|
|
2016-06-30 18:17:01 +00:00
|
|
|
log = logging.getLogger('subiquitycore.core')
|
2015-07-21 15:55:02 +00:00
|
|
|
|
|
|
|
|
2019-11-04 10:51:47 +00:00
|
|
|
class Application:
|
2016-07-25 00:38:19 +00:00
|
|
|
|
2016-07-25 00:51:39 +00:00
|
|
|
# A concrete subclass must set project and controllers attributes, e.g.:
|
|
|
|
#
|
|
|
|
# project = "subiquity"
|
2016-09-27 02:33:54 +00:00
|
|
|
# controllers = [
|
|
|
|
# "Welcome",
|
|
|
|
# "Network",
|
|
|
|
# "Filesystem",
|
|
|
|
# "Identity",
|
|
|
|
# "InstallProgress",
|
|
|
|
# ]
|
2020-04-07 13:35:31 +00:00
|
|
|
# The 'next_screen' and 'prev_screen' methods move through the list of
|
2019-11-18 00:55:43 +00:00
|
|
|
# controllers in order, calling the start_ui method on the controller
|
2016-09-29 01:06:09 +00:00
|
|
|
# instance.
|
2016-06-30 18:50:21 +00:00
|
|
|
|
2019-09-04 23:58:20 +00:00
|
|
|
def __init__(self, opts):
|
2020-07-31 00:50:59 +00:00
|
|
|
self._exc = None
|
2019-12-14 20:06:13 +00:00
|
|
|
self.debug_flags = ()
|
|
|
|
if opts.dry_run:
|
|
|
|
# Recognized flags are:
|
|
|
|
# - install-fail: makes curtin install fail by replaying curtin
|
|
|
|
# events from a failed installation, see
|
|
|
|
# subiquity/controllers/installprogress.py
|
|
|
|
# - bpfail-full, bpfail-restricted: makes block probing fail, see
|
|
|
|
# subiquitycore/prober.py
|
|
|
|
# - copy-logs-fail: makes post-install copying of logs fail, see
|
|
|
|
# subiquity/controllers/installprogress.py
|
|
|
|
self.debug_flags = os.environ.get('SUBIQUITY_DEBUG', '').split(',')
|
|
|
|
|
|
|
|
prober = Prober(opts.machine_config, self.debug_flags)
|
2015-10-23 15:03:04 +00:00
|
|
|
|
2019-08-06 02:11:57 +00:00
|
|
|
self.opts = opts
|
2017-03-16 09:52:05 +00:00
|
|
|
opts.project = self.project
|
|
|
|
|
2019-03-07 02:05:13 +00:00
|
|
|
self.root = '/'
|
2019-03-06 02:52:13 +00:00
|
|
|
if opts.dry_run:
|
2019-03-07 02:05:13 +00:00
|
|
|
self.root = '.subiquity'
|
|
|
|
self.state_dir = os.path.join(self.root, 'run', self.project)
|
2020-04-29 04:41:27 +00:00
|
|
|
os.makedirs(self.state_path('states'), exist_ok=True)
|
2019-03-06 02:52:13 +00:00
|
|
|
|
2019-08-06 02:11:57 +00:00
|
|
|
self.scale_factor = float(
|
|
|
|
os.environ.get('SUBIQUITY_REPLAY_TIMESCALE', "1"))
|
2020-04-29 04:41:27 +00:00
|
|
|
self.updated = os.path.exists(self.state_path('updating'))
|
2019-08-06 02:11:57 +00:00
|
|
|
self.signal = Signal()
|
|
|
|
self.prober = prober
|
2019-12-20 01:27:41 +00:00
|
|
|
self.new_event_loop()
|
2020-07-30 03:13:48 +00:00
|
|
|
self.controllers = ControllerSet(
|
2020-07-30 21:35:07 +00:00
|
|
|
self.controllers_mod, self.controllers, init_args=(self,))
|
2020-04-24 02:45:20 +00:00
|
|
|
self.context = Context.new(self)
|
2019-09-03 01:04:47 +00:00
|
|
|
|
2020-07-31 00:50:59 +00:00
|
|
|
def _exception_handler(self, loop, context):
|
|
|
|
exc = context.get('exception')
|
|
|
|
if exc:
|
|
|
|
loop.stop()
|
|
|
|
self._exc = exc
|
|
|
|
else:
|
|
|
|
loop.default_exception_handler(context)
|
|
|
|
|
2019-12-20 01:27:41 +00:00
|
|
|
def new_event_loop(self):
|
|
|
|
new_loop = asyncio.new_event_loop()
|
2020-07-31 00:50:59 +00:00
|
|
|
new_loop.set_exception_handler(self._exception_handler)
|
2019-12-20 01:27:41 +00:00
|
|
|
asyncio.set_event_loop(new_loop)
|
|
|
|
self.aio_loop = new_loop
|
|
|
|
|
2015-08-18 16:29:56 +00:00
|
|
|
def _connect_base_signals(self):
|
2019-09-09 02:52:36 +00:00
|
|
|
"""Connect signals used in the core controller."""
|
2015-08-18 16:29:56 +00:00
|
|
|
# Registers signals from each controller
|
2019-11-18 00:55:43 +00:00
|
|
|
for controller in self.controllers.instances:
|
|
|
|
controller.register_signals()
|
2019-09-09 02:52:36 +00:00
|
|
|
log.debug("known signals: %s", self.signal.known_signals)
|
2015-07-21 20:34:46 +00:00
|
|
|
|
2020-04-29 04:41:27 +00:00
|
|
|
def state_path(self, *parts):
|
|
|
|
return os.path.join(self.state_dir, *parts)
|
|
|
|
|
2019-03-06 02:52:13 +00:00
|
|
|
def save_state(self):
|
2019-11-18 00:55:43 +00:00
|
|
|
cur = self.controllers.cur
|
|
|
|
if cur is None:
|
2019-03-06 02:52:13 +00:00
|
|
|
return
|
2020-04-29 04:41:27 +00:00
|
|
|
with open(self.state_path('states', cur.name), 'w') as fp:
|
2019-11-18 00:55:43 +00:00
|
|
|
json.dump(cur.serialize(), fp)
|
|
|
|
|
2020-04-06 04:24:53 +00:00
|
|
|
def report_start_event(self, context, description):
|
|
|
|
log = logging.getLogger(context.full_name())
|
|
|
|
level = getattr(logging, context.level)
|
2019-12-19 03:11:12 +00:00
|
|
|
log.log(level, "start: %s", description)
|
|
|
|
|
2020-04-06 04:24:53 +00:00
|
|
|
def report_finish_event(self, context, description, status):
|
|
|
|
log = logging.getLogger(context.full_name())
|
|
|
|
level = getattr(logging, context.level)
|
2019-12-19 03:11:12 +00:00
|
|
|
log.log(level, "finish: %s %s", description, status.name)
|
|
|
|
|
2015-07-21 15:55:02 +00:00
|
|
|
# EventLoop -------------------------------------------------------------------
|
|
|
|
|
2020-03-16 03:37:16 +00:00
|
|
|
def exit(self):
|
2019-12-20 00:54:34 +00:00
|
|
|
self.aio_loop.stop()
|
2015-07-21 15:55:02 +00:00
|
|
|
|
2019-10-31 01:34:10 +00:00
|
|
|
def start_controllers(self):
|
|
|
|
log.debug("starting controllers")
|
2019-11-18 00:55:43 +00:00
|
|
|
for controller in self.controllers.instances:
|
|
|
|
controller.start()
|
2019-10-31 01:34:10 +00:00
|
|
|
log.debug("controllers started")
|
|
|
|
|
2019-09-03 00:58:57 +00:00
|
|
|
def load_serialized_state(self):
|
2019-11-18 00:55:43 +00:00
|
|
|
for controller in self.controllers.instances:
|
2020-04-29 04:41:27 +00:00
|
|
|
state_path = self.state_path('states', controller.name)
|
2019-09-03 00:58:57 +00:00
|
|
|
if not os.path.exists(state_path):
|
|
|
|
continue
|
|
|
|
with open(state_path) as fp:
|
2019-11-18 00:55:43 +00:00
|
|
|
controller.deserialize(json.load(fp))
|
2019-09-03 00:58:57 +00:00
|
|
|
|
2020-07-31 00:50:59 +00:00
|
|
|
def run(self):
|
2019-08-06 02:11:57 +00:00
|
|
|
self.base_model = self.make_model()
|
2015-07-21 15:55:02 +00:00
|
|
|
try:
|
2019-12-19 23:43:42 +00:00
|
|
|
self.controllers.load_all()
|
2020-07-30 22:48:17 +00:00
|
|
|
self.load_serialized_state()
|
2015-08-31 15:55:46 +00:00
|
|
|
self._connect_base_signals()
|
2019-10-31 01:34:10 +00:00
|
|
|
self.start_controllers()
|
2020-07-31 00:50:59 +00:00
|
|
|
self.aio_loop.run_forever()
|
|
|
|
finally:
|
|
|
|
self.aio_loop.run_until_complete(
|
|
|
|
self.aio_loop.shutdown_asyncgens())
|
|
|
|
if self._exc:
|
|
|
|
exc, self._exc = self._exc, None
|
|
|
|
raise exc
|