refactor controller code and signal handling
Signed-off-by: Adam Stokes <adam.stokes@ubuntu.com>
This commit is contained in:
parent
c156c741cb
commit
f429372d3c
|
@ -19,7 +19,7 @@ import sys
|
|||
import logging
|
||||
from subiquity.log import setup_logger
|
||||
from subiquity import __version__ as VERSION
|
||||
from subiquity.controllers import BaseController as Subiquity
|
||||
from subiquity.core import Controller as Subiquity
|
||||
from subiquity.ui.frame import SubiquityUI
|
||||
|
||||
|
||||
|
|
|
@ -1,113 +0,0 @@
|
|||
# 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/>.
|
||||
|
||||
import logging
|
||||
import urwid
|
||||
import urwid.curses_display
|
||||
from subiquity.routes import Routes
|
||||
from subiquity.palette import STYLES, STYLES_MONO
|
||||
|
||||
|
||||
log = logging.getLogger('subiquity.controller')
|
||||
|
||||
|
||||
class BaseControllerError(Exception):
|
||||
""" Basecontroller exception """
|
||||
pass
|
||||
|
||||
|
||||
class BaseController:
|
||||
def __init__(self, ui, opts):
|
||||
self.ui = ui
|
||||
self.opts = opts
|
||||
|
||||
def next_controller(self, *args, **kwds):
|
||||
controller = Routes.next()
|
||||
controller(self).show(*args, **kwds)
|
||||
|
||||
def prev_controller(self, *args, **kwds):
|
||||
controller = Routes.prev()
|
||||
controller(self).show(*args, **kwds)
|
||||
|
||||
def current_controller(self, *args, **kwds):
|
||||
controller = Routes.current()
|
||||
return controller(self)
|
||||
|
||||
def redraw_screen(self):
|
||||
if hasattr(self, 'loop'):
|
||||
try:
|
||||
self.loop.draw_screen()
|
||||
except AssertionError as e:
|
||||
log.critical("Redraw screen error: {}".format(e))
|
||||
|
||||
def set_alarm_in(self, interval, cb):
|
||||
self.loop.set_alarm_in(interval, cb)
|
||||
return
|
||||
|
||||
def update(self, *args, **kwds):
|
||||
""" Update loop """
|
||||
pass
|
||||
|
||||
def exit(self):
|
||||
raise urwid.ExitMainLoop()
|
||||
|
||||
def header_hotkeys(self, key):
|
||||
if key in ['esc'] and Routes.current_idx() != 0:
|
||||
self.prev_controller()
|
||||
if key in ['q', 'Q', 'ctrl c']:
|
||||
self.exit()
|
||||
|
||||
def set_body(self, w):
|
||||
self.ui.set_body(w)
|
||||
self.redraw_screen()
|
||||
|
||||
def set_header(self, title=None, excerpt=None):
|
||||
self.ui.set_header(title, excerpt)
|
||||
self.redraw_screen()
|
||||
|
||||
def set_footer(self, message):
|
||||
self.ui.set_footer(message)
|
||||
self.redraw_screen()
|
||||
|
||||
def run(self):
|
||||
if not hasattr(self, 'loop'):
|
||||
palette = STYLES
|
||||
additional_opts = {
|
||||
'screen': urwid.raw_display.Screen(),
|
||||
'unhandled_input': self.header_hotkeys,
|
||||
'handle_mouse': False
|
||||
}
|
||||
if self.opts.run_on_serial:
|
||||
palette = STYLES_MONO
|
||||
additional_opts['screen'] = urwid.curses_display.Screen()
|
||||
else:
|
||||
additional_opts['screen'].set_terminal_properties(colors=256)
|
||||
additional_opts['screen'].reset_default_terminal_palette()
|
||||
|
||||
self.loop = urwid.MainLoop(
|
||||
self.ui, palette, **additional_opts)
|
||||
|
||||
try:
|
||||
self.set_alarm_in(0.05, self.begin)
|
||||
self.loop.run()
|
||||
except:
|
||||
log.exception("Exception in controller.run():")
|
||||
raise
|
||||
|
||||
def begin(self, *args, **kwargs):
|
||||
""" Initializes the first controller for installation """
|
||||
Routes.reset()
|
||||
initial_controller = Routes.first()
|
||||
initial_controller(self).show()
|
|
@ -1,120 +0,0 @@
|
|||
# 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/>.
|
||||
|
||||
from subiquity.views.filesystem import (FilesystemView,
|
||||
DiskPartitionView,
|
||||
AddPartitionView)
|
||||
from subiquity.models.filesystem import FilesystemModel
|
||||
from subiquity.curtin import curtin_write_storage_actions
|
||||
|
||||
from urwid import connect_signal
|
||||
import logging
|
||||
import subprocess
|
||||
|
||||
|
||||
log = logging.getLogger('subiquity.filesystemController')
|
||||
|
||||
|
||||
class FilesystemController:
|
||||
""" Filesystem Controller """
|
||||
fs_model = FilesystemModel()
|
||||
|
||||
def __init__(self, ui):
|
||||
self.ui = ui
|
||||
|
||||
# Filesystem actions
|
||||
def show(self, *args, **kwds):
|
||||
title = "Filesystem setup"
|
||||
footer = ("Select available disks to format and mount")
|
||||
|
||||
fs_view = FilesystemView(self.fs_model)
|
||||
connect_signal(fs_view, 'fs:done', self.finish)
|
||||
connect_signal(fs_view, 'fs:dp:view', self.show_disk_partition_view)
|
||||
|
||||
self.ui.set_header(title)
|
||||
self.ui.set_footer(footer)
|
||||
self.ui.set_body(fs_view)
|
||||
return
|
||||
|
||||
def finish(self, reset=False, actions=None):
|
||||
"""
|
||||
:param bool reset: Reset model options
|
||||
:param actions: storage actions
|
||||
|
||||
Signal:
|
||||
key: 'fs:done'
|
||||
usage: emit_signal(self, 'fs:done', (reset, actions))
|
||||
"""
|
||||
if actions is None and reset is False:
|
||||
return self.ui.prev_controller()
|
||||
|
||||
log.info("Rendering curtin config from user choices")
|
||||
curtin_write_storage_actions(actions=actions)
|
||||
if self.ui.opts.dry_run:
|
||||
log.debug("filesystem: this is a dry-run")
|
||||
print("\033c")
|
||||
print("**** DRY_RUN ****")
|
||||
print('NOT calling: '
|
||||
'subprocess.check_call("/usr/local/bin/curtin_wrap.sh")')
|
||||
print("**** DRY_RUN ****")
|
||||
else:
|
||||
log.debug("filesystem: this is the *real* thing")
|
||||
print("\033c")
|
||||
print("**** Calling curtin installer ****")
|
||||
subprocess.check_call("/usr/local/bin/curtin_wrap.sh")
|
||||
|
||||
return self.ui.exit()
|
||||
|
||||
# DISK Partitioning actions
|
||||
def show_disk_partition_view(self, disk):
|
||||
log.debug("In disk partition view, using {} as the disk.".format(disk))
|
||||
title = ("Paritition, format, and mount {}".format(disk))
|
||||
footer = ("Paritition the disk, or format the entire device "
|
||||
"without partitions.")
|
||||
self.ui.set_header(title)
|
||||
self.ui.set_footer(footer)
|
||||
dp_view = DiskPartitionView(self.fs_model,
|
||||
disk)
|
||||
|
||||
connect_signal(dp_view, 'fs:dp:done', self.finish_disk_paritition_view)
|
||||
connect_signal(dp_view, 'fs:show-add-partition',
|
||||
self.show_add_disk_partition_view)
|
||||
|
||||
self.ui.set_body(dp_view)
|
||||
return
|
||||
|
||||
def finish_disk_paritition_view(self, result):
|
||||
log.debug("Finish disk-p-v: {}".format(result))
|
||||
return self.ui.exit()
|
||||
|
||||
# ADD Partitioning actions
|
||||
def show_add_disk_partition_view(self, disk):
|
||||
adp_view = AddPartitionView(self.fs_model,
|
||||
disk)
|
||||
connect_signal(adp_view,
|
||||
'fs:add-partition:done',
|
||||
self.finish_add_disk_paritition_view)
|
||||
self.ui.set_body(adp_view)
|
||||
return
|
||||
|
||||
def finish_add_disk_partition_view(self, partition_spec):
|
||||
if not partition_spec:
|
||||
log.debug("New partition: {}".format(partition_spec))
|
||||
else:
|
||||
log.debug("Empty partition spec, should go back one.")
|
||||
return self.ui.exit()
|
||||
|
||||
|
||||
__controller_class__ = FilesystemController
|
|
@ -1,49 +0,0 @@
|
|||
# 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/>.
|
||||
|
||||
from subiquity.controllers.policy import ControllerPolicy
|
||||
from subiquity.views.installpath import InstallpathView
|
||||
from subiquity.models.installpath import InstallpathModel
|
||||
import logging
|
||||
|
||||
log = logging.getLogger('subiquity.installpath')
|
||||
|
||||
|
||||
class InstallpathController(ControllerPolicy):
|
||||
"""InstallpathController"""
|
||||
|
||||
title = "15.10"
|
||||
excerpt = ("Welcome to Ubuntu! The world's favourite platform "
|
||||
"for clouds, clusters and amazing internet things. "
|
||||
"This is the installer for Ubuntu on servers and "
|
||||
"internet devices.")
|
||||
footer = ("Use UP, DOWN arrow keys, and ENTER, to "
|
||||
"navigate options")
|
||||
|
||||
def show(self, *args, **kwds):
|
||||
log.debug("Loading install path controller")
|
||||
self.ui.set_header(self.title, self.excerpt)
|
||||
self.ui.set_footer(self.footer)
|
||||
model = InstallpathModel()
|
||||
self.ui.set_body(InstallpathView(model, self.finish))
|
||||
return
|
||||
|
||||
def finish(self, install_selection=None):
|
||||
log.debug("installpath cb selection: {}".format(install_selection))
|
||||
if install_selection is None:
|
||||
return self.ui.prev_controller()
|
||||
return self.ui.next_controller()
|
||||
|
||||
__controller_class__ = InstallpathController
|
|
@ -1,47 +0,0 @@
|
|||
# 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/>.
|
||||
|
||||
from subiquity.controllers.policy import ControllerPolicy
|
||||
from subiquity.views.network import NetworkView
|
||||
from subiquity.models.network import NetworkModel
|
||||
import logging
|
||||
|
||||
log = logging.getLogger('subiquity.network')
|
||||
|
||||
|
||||
class NetworkController(ControllerPolicy):
|
||||
"""InstallpathController"""
|
||||
|
||||
title = "Network connections"
|
||||
excerpt = ("Configure at least the main interface this server will "
|
||||
"use to talk to other machines, and preferably provide "
|
||||
"sufficient access for updates.")
|
||||
|
||||
footer = ("Additional networking info here")
|
||||
|
||||
def show(self, *args, **kwds):
|
||||
self.model = NetworkModel()
|
||||
self.ui.set_header(self.title, self.excerpt)
|
||||
self.ui.set_footer(self.footer)
|
||||
self.ui.set_body(NetworkView(self.model, self.finish))
|
||||
return
|
||||
|
||||
def finish(self, interface=None):
|
||||
if interface is None:
|
||||
return self.ui.prev_controller()
|
||||
log.info("Network Interface choosen: {}".format(interface))
|
||||
return self.ui.next_controller()
|
||||
|
||||
__controller_class__ = NetworkController
|
|
@ -1,45 +0,0 @@
|
|||
# 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/>.
|
||||
|
||||
""" Controller policy """
|
||||
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
|
||||
class ControllerPolicy(metaclass=ABCMeta):
|
||||
""" Policy class for controller specifics
|
||||
"""
|
||||
signals = ('done', 'cancel', 'reset')
|
||||
|
||||
def __init__(self, ui):
|
||||
self.ui = ui
|
||||
|
||||
@abstractmethod
|
||||
def show(self, *args, **kwds):
|
||||
""" Implements show action for the controller
|
||||
|
||||
This is the entrypoint for all initial controller
|
||||
views.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def finish(self):
|
||||
""" Implements finish action for controller.
|
||||
|
||||
This handles any callback data/procedures required
|
||||
to move to the next controller or end the install.
|
||||
"""
|
||||
pass
|
|
@ -1,46 +0,0 @@
|
|||
# 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/>.
|
||||
|
||||
from subiquity.controllers.policy import ControllerPolicy
|
||||
from subiquity.views.welcome import WelcomeView
|
||||
from subiquity.models.welcome import WelcomeModel
|
||||
import logging
|
||||
|
||||
|
||||
log = logging.getLogger('subiquity.controllers.welcome')
|
||||
|
||||
|
||||
class WelcomeController(ControllerPolicy):
|
||||
"""WelcomeController"""
|
||||
title = "Wilkommen! Bienvenue! Welcome! Zdrastvutie! Welkom!"
|
||||
excerpt = "Please choose your preferred language"
|
||||
footer = ("Use UP, DOWN arrow keys, and ENTER, to "
|
||||
"select your language.")
|
||||
|
||||
def show(self, *args, **kwds):
|
||||
self.ui.set_header(self.title, self.excerpt)
|
||||
self.ui.set_footer(self.footer)
|
||||
self.ui.set_body(WelcomeView(WelcomeModel, self.finish))
|
||||
return
|
||||
|
||||
def finish(self, language=None):
|
||||
if language is None:
|
||||
raise SystemExit("No language selected, exiting as there are no "
|
||||
"more previous controllers to render.")
|
||||
WelcomeModel.selected_language = language
|
||||
log.debug("Welcome Model: {}".format(WelcomeModel()))
|
||||
return self.ui.next_controller()
|
||||
|
||||
__controller_class__ = WelcomeController
|
|
@ -0,0 +1,253 @@
|
|||
# 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/>.
|
||||
|
||||
import logging
|
||||
import urwid
|
||||
import urwid.curses_display
|
||||
import subprocess
|
||||
from subiquity.signals import Signal
|
||||
from subiquity.palette import STYLES, STYLES_MONO
|
||||
from subiquity.curtin import curtin_write_storage_actions
|
||||
|
||||
# Modes import ----------------------------------------------------------------
|
||||
from subiquity.welcome import WelcomeView, WelcomeModel
|
||||
from subiquity.network import NetworkView, NetworkModel
|
||||
from subiquity.installpath import InstallpathView, InstallpathModel
|
||||
from subiquity.filesystem import (FilesystemView,
|
||||
DiskPartitionView,
|
||||
AddPartitionView,
|
||||
FilesystemModel)
|
||||
|
||||
log = logging.getLogger('subiquity.core')
|
||||
|
||||
|
||||
class CoreControllerError(Exception):
|
||||
""" Basecontroller exception """
|
||||
pass
|
||||
|
||||
|
||||
class Controller:
|
||||
def __init__(self, ui, opts):
|
||||
self.ui = ui
|
||||
self.opts = opts
|
||||
self.signal = Signal()
|
||||
self.signal.register_signals()
|
||||
|
||||
# EventLoop -------------------------------------------------------------------
|
||||
def redraw_screen(self):
|
||||
if hasattr(self, 'loop'):
|
||||
try:
|
||||
self.loop.draw_screen()
|
||||
except AssertionError as e:
|
||||
log.critical("Redraw screen error: {}".format(e))
|
||||
|
||||
def set_alarm_in(self, interval, cb):
|
||||
self.loop.set_alarm_in(interval, cb)
|
||||
return
|
||||
|
||||
def update(self, *args, **kwds):
|
||||
""" Update loop """
|
||||
pass
|
||||
|
||||
def exit(self):
|
||||
raise urwid.ExitMainLoop()
|
||||
|
||||
def header_hotkeys(self, key):
|
||||
if key in ['q', 'Q', 'ctrl c']:
|
||||
self.exit()
|
||||
|
||||
def run(self):
|
||||
if not hasattr(self, 'loop'):
|
||||
palette = STYLES
|
||||
additional_opts = {
|
||||
'screen': urwid.raw_display.Screen(),
|
||||
'unhandled_input': self.header_hotkeys,
|
||||
'handle_mouse': False
|
||||
}
|
||||
if self.opts.run_on_serial:
|
||||
palette = STYLES_MONO
|
||||
additional_opts['screen'] = urwid.curses_display.Screen()
|
||||
else:
|
||||
additional_opts['screen'].set_terminal_properties(colors=256)
|
||||
additional_opts['screen'].reset_default_terminal_palette()
|
||||
|
||||
self.loop = urwid.MainLoop(
|
||||
self.ui, palette, **additional_opts)
|
||||
|
||||
try:
|
||||
self.set_alarm_in(0.05, self.welcome)
|
||||
self.loop.run()
|
||||
except:
|
||||
log.exception("Exception in controller.run():")
|
||||
raise
|
||||
|
||||
# Base UI Actions -------------------------------------------------------------
|
||||
def set_body(self, w):
|
||||
self.ui.set_body(w)
|
||||
self.redraw_screen()
|
||||
|
||||
def set_header(self, title=None, excerpt=None):
|
||||
self.ui.set_header(title, excerpt)
|
||||
self.redraw_screen()
|
||||
|
||||
def set_footer(self, message):
|
||||
self.ui.set_footer(message)
|
||||
self.redraw_screen()
|
||||
|
||||
|
||||
# Modes ----------------------------------------------------------------------
|
||||
|
||||
# Welcome -----------------------------------------------------------------
|
||||
def welcome(self, *args, **kwargs):
|
||||
title = "Wilkommen! Bienvenue! Welcome! Zdrastvutie! Welkom!"
|
||||
excerpt = "Please choose your preferred language"
|
||||
footer = ("Use UP, DOWN arrow keys, and ENTER, to "
|
||||
"select your language.")
|
||||
self.ui.set_header(title, excerpt)
|
||||
self.ui.set_footer(footer)
|
||||
model = WelcomeModel()
|
||||
view = WelcomeView(model, self.signal)
|
||||
urwid.connect_signal(self.signal, 'welcome:finish',
|
||||
self.welcome_handler)
|
||||
urwid.connect_signal(self.signal, 'installpath:show', self.installpath)
|
||||
self.ui.set_body(view)
|
||||
self.welcome_model = WelcomeModel()
|
||||
|
||||
def welcome_handler(self, language=None):
|
||||
log.debug("Welcome handler")
|
||||
if language is None:
|
||||
raise SystemExit("No language selected, exiting as there are no "
|
||||
"more previous controllers to render.")
|
||||
self.welcome_model.selected_language = language
|
||||
log.debug("Welcome Model: {}".format(self.welcome_model))
|
||||
urwid.emit_signal(self.signal, 'installpath:show')
|
||||
|
||||
# InstallPath -------------------------------------------------------------
|
||||
def installpath(self):
|
||||
title = "15.10"
|
||||
excerpt = ("Welcome to Ubuntu! The world's favourite platform "
|
||||
"for clouds, clusters and amazing internet things. "
|
||||
"This is the installer for Ubuntu on servers and "
|
||||
"internet devices.")
|
||||
footer = ("Use UP, DOWN arrow keys, and ENTER, to "
|
||||
"navigate options")
|
||||
|
||||
self.ui.set_header(title, excerpt)
|
||||
self.ui.set_footer(footer)
|
||||
self.installpath_model = InstallpathModel()
|
||||
urwid.connect_signal(self.signal, 'installpath:finish',
|
||||
self.installpath_handler)
|
||||
urwid.connect_signal(self.signal, 'welcome:show', self.welcome)
|
||||
self.ui.set_body(InstallpathView(self.installpath_model, self.signal))
|
||||
|
||||
def installpath_handler(self, install_selection=None):
|
||||
if install_selection is None:
|
||||
urwid.emit_signal(self.signal, 'welcome:show', [])
|
||||
urwid.emit_signal(self.signal, 'network:show', [])
|
||||
|
||||
# Network -----------------------------------------------------------------
|
||||
def network(self):
|
||||
title = "Network connections"
|
||||
excerpt = ("Configure at least the main interface this server will "
|
||||
"use to talk to other machines, and preferably provide "
|
||||
"sufficient access for updates.")
|
||||
footer = ("Additional networking info here")
|
||||
self.network_model = NetworkModel()
|
||||
self.ui.set_header(title, excerpt)
|
||||
self.ui.set_footer(footer)
|
||||
urwid.connect_signal(self.signal, 'network:finish',
|
||||
self.network_handler)
|
||||
urwid.connect_signal(self.signal, 'installpath:show', self.installpath)
|
||||
urwid.connect_signal(self.signal, 'filesystem:show', self.filesystem)
|
||||
self.ui.set_body(NetworkView(self.network_model))
|
||||
|
||||
def network_handler(self, interface=None):
|
||||
log.info("Network Interface choosen: {}".format(interface))
|
||||
if interface is None:
|
||||
urwid.emit_signal(self.signal, 'installpath:show', [])
|
||||
urwid.emit_signal(self.signal, 'filesystem:show', [])
|
||||
|
||||
# Filesystem --------------------------------------------------------------
|
||||
def filesystem(self):
|
||||
title = "Filesystem setup"
|
||||
footer = ("Select available disks to format and mount")
|
||||
self.ui.set_header(title)
|
||||
self.ui.set_footer(footer)
|
||||
|
||||
self.fs_model = FilesystemModel()
|
||||
urwid.connect_signal(self.signal, 'filesystem:finish',
|
||||
self.filesystem_handler)
|
||||
urwid.connect_signal(self.signal, 'filesystem:show-disk-partition',
|
||||
self.disk_partition)
|
||||
self.ui.set_body(FilesystemView(self.fs_model))
|
||||
|
||||
def filesystem_handler(self, reset=False, actions=None):
|
||||
if actions is None and reset is False:
|
||||
urwid.emit_signal(self.signal, 'network:show', [])
|
||||
|
||||
log.info("Rendering curtin config from user choices")
|
||||
curtin_write_storage_actions(actions=actions)
|
||||
if self.opts.dry_run:
|
||||
log.debug("filesystem: this is a dry-run")
|
||||
print("\033c")
|
||||
print("**** DRY_RUN ****")
|
||||
print('NOT calling: '
|
||||
'subprocess.check_call("/usr/local/bin/curtin_wrap.sh")')
|
||||
print("**** DRY_RUN ****")
|
||||
else:
|
||||
log.debug("filesystem: this is the *real* thing")
|
||||
print("\033c")
|
||||
print("**** Calling curtin installer ****")
|
||||
subprocess.check_call("/usr/local/bin/curtin_wrap.sh")
|
||||
return self.ui.exit()
|
||||
|
||||
# Filesystem/Disk partition -----------------------------------------------
|
||||
def disk_partition(self, disk):
|
||||
log.debug("In disk partition view, using {} as the disk.".format(disk))
|
||||
title = ("Paritition, format, and mount {}".format(disk))
|
||||
footer = ("Paritition the disk, or format the entire device "
|
||||
"without partitions.")
|
||||
self.ui.set_header(title)
|
||||
self.ui.set_footer(footer)
|
||||
dp_view = DiskPartitionView(self.fs_model,
|
||||
disk)
|
||||
|
||||
urwid.connect_signal(self.signal, 'filesystem:finish-disk-partition',
|
||||
self.disk_paritition_handler)
|
||||
urwid.connect_signal(self.signal, 'filesystem:add-disk-partition',
|
||||
self.add_disk_partition)
|
||||
|
||||
self.ui.set_body(dp_view)
|
||||
|
||||
def disk_partition_handler(self, spec=None):
|
||||
log.debug("Disk partition: {}".format(spec))
|
||||
if spec is None:
|
||||
urwid.emit_signal(self.signal, 'filesystem:show', [])
|
||||
urwid.emit_signal(self.signal, 'filesystem:show-disk-partition', [])
|
||||
|
||||
def add_disk_partition(self, disk):
|
||||
adp_view = AddPartitionView(self.fs_model,
|
||||
disk)
|
||||
urwid.connect_signal(self.signal,
|
||||
'filesystem:finish-add-disk-partition',
|
||||
self.add_disk_paritition_handler)
|
||||
self.ui.set_body(adp_view)
|
||||
|
||||
def add_disk_partition_handler(self, partition_spec):
|
||||
self.exit()
|
||||
if not partition_spec:
|
||||
log.debug("New partition: {}".format(partition_spec))
|
||||
else:
|
||||
log.debug("Empty partition spec, should go back one.")
|
|
@ -13,17 +13,114 @@
|
|||
# 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/>.
|
||||
|
||||
""" Filesystem
|
||||
|
||||
Provides storage device selection and additional storage
|
||||
configuration.
|
||||
|
||||
"""
|
||||
import logging
|
||||
import json
|
||||
import argparse
|
||||
|
||||
from subiquity.filesystem.blockdev import Blockdev
|
||||
from probert import prober
|
||||
from probert.storage import StorageInfo
|
||||
import math
|
||||
from urwid import (WidgetWrap, ListBox, Pile, BoxAdapter,
|
||||
Text, Columns, LineBox, Edit, RadioButton)
|
||||
from subiquity.ui.lists import SimpleList
|
||||
from subiquity.ui.buttons import done_btn, reset_btn, cancel_btn
|
||||
from subiquity.ui.utils import Padding, Color
|
||||
from subiquity.signals import emit_signal
|
||||
|
||||
log = logging.getLogger('subiquity.filesystem')
|
||||
|
||||
|
||||
log = logging.getLogger('subiquity.filesystemView')
|
||||
class FilesystemModel:
|
||||
""" Model representing storage options
|
||||
"""
|
||||
|
||||
fs_menu = [
|
||||
'Connect iSCSI network disk',
|
||||
'Connect Ceph network disk',
|
||||
'Create volume group (LVM2)',
|
||||
'Create software RAID (MD)',
|
||||
'Setup hierarchichal storage (bcache)'
|
||||
]
|
||||
|
||||
partition_menu = [
|
||||
'Add first GPT partition',
|
||||
'Format or create swap on entire device (unusual, advanced)'
|
||||
]
|
||||
|
||||
supported_filesystems = [
|
||||
'ext4',
|
||||
'xfs',
|
||||
'btrfs',
|
||||
'swap',
|
||||
'bcache cache',
|
||||
'bcache store',
|
||||
'leave unformatted'
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
self.storage = {}
|
||||
self.info = {}
|
||||
self.devices = {}
|
||||
self.options = argparse.Namespace(probe_storage=True,
|
||||
probe_network=False)
|
||||
self.prober = prober.Prober(self.options)
|
||||
self.probe_storage()
|
||||
|
||||
def probe_storage(self):
|
||||
self.prober.probe()
|
||||
self.storage = self.prober.get_results().get('storage')
|
||||
log.debug('storage probe data:\n{}'.format(
|
||||
json.dumps(self.storage, indent=4, sort_keys=True)))
|
||||
|
||||
# TODO: replace this with Storage.get_device_by_match()
|
||||
# which takes a lambda fn for matching
|
||||
VALID_MAJORS = ['8', '253']
|
||||
for disk in self.storage.keys():
|
||||
if self.storage[disk]['DEVTYPE'] == 'disk' and \
|
||||
self.storage[disk]['MAJOR'] in VALID_MAJORS:
|
||||
log.debug('disk={}\n{}'.format(disk,
|
||||
json.dumps(self.storage[disk], indent=4,
|
||||
sort_keys=True)))
|
||||
self.info[disk] = StorageInfo({disk: self.storage[disk]})
|
||||
|
||||
def get_disk(self, disk):
|
||||
if disk not in self.devices:
|
||||
self.devices[disk] = Blockdev(disk, self.info[disk].serial)
|
||||
return self.devices[disk]
|
||||
|
||||
def get_partitions(self):
|
||||
partitions = []
|
||||
for dev in self.devices.values():
|
||||
partnames = [part.path for part in dev.disk.partitions]
|
||||
partitions += partnames
|
||||
|
||||
sorted(partitions)
|
||||
return partitions
|
||||
|
||||
def get_available_disks(self):
|
||||
return sorted(self.info.keys())
|
||||
|
||||
def get_used_disks(self):
|
||||
return [dev.disk.path for dev in self.devices.values()
|
||||
if dev.available is False]
|
||||
|
||||
def get_disk_info(self, disk):
|
||||
return self.info[disk]
|
||||
|
||||
def get_disk_action(self, disk):
|
||||
return self.devices[disk].get_actions()
|
||||
|
||||
def get_actions(self):
|
||||
actions = []
|
||||
for dev in self.devices.values():
|
||||
actions += dev.get_actions()
|
||||
return actions
|
||||
|
||||
|
||||
def _humanize_size(size):
|
|
@ -17,7 +17,7 @@ import parted
|
|||
import yaml
|
||||
from itertools import count
|
||||
|
||||
from subiquity.models.actions import (
|
||||
from subiquity.filesystem.actions import (
|
||||
Action,
|
||||
PartitionAction,
|
||||
FormatAction,
|
|
@ -13,21 +13,37 @@
|
|||
# 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/>.
|
||||
|
||||
""" Install Path
|
||||
|
||||
Provides high level options for Ubuntu install
|
||||
|
||||
"""
|
||||
import logging
|
||||
from urwid import (WidgetWrap, ListBox, Pile, BoxAdapter)
|
||||
from urwid import (WidgetWrap, ListBox, Pile, BoxAdapter, emit_signal)
|
||||
from subiquity.ui.lists import SimpleList
|
||||
from subiquity.ui.buttons import confirm_btn, cancel_btn
|
||||
from subiquity.ui.utils import Padding, Color
|
||||
|
||||
|
||||
log = logging.getLogger('subiquity.installpathView')
|
||||
|
||||
|
||||
class InstallpathModel:
|
||||
""" Model representing install options
|
||||
"""
|
||||
|
||||
install_paths = ['Install Ubuntu',
|
||||
'Install MAAS Region Server',
|
||||
'Install MAAS Cluster Server',
|
||||
'Test installation media',
|
||||
'Test machine memory']
|
||||
selected_path = None
|
||||
|
||||
|
||||
class InstallpathView(WidgetWrap):
|
||||
def __init__(self, model, cb):
|
||||
def __init__(self, model, signal):
|
||||
log.debug("In install path view")
|
||||
self.model = model
|
||||
self.cb = cb
|
||||
self.signal = signal
|
||||
self.items = []
|
||||
self.body = [
|
||||
Padding.center_79(self._build_model_inputs()),
|
||||
|
@ -54,7 +70,7 @@ class InstallpathView(WidgetWrap):
|
|||
height=len(sl))
|
||||
|
||||
def confirm(self, button):
|
||||
return self.cb(button.label)
|
||||
emit_signal(self.signal, 'installpath:finish', button.label)
|
||||
|
||||
def cancel(self, button):
|
||||
return self.cb(None)
|
||||
emit_signal(self.signal, 'welcome:show', None)
|
|
@ -1,68 +0,0 @@
|
|||
# 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/>.
|
||||
|
||||
""" Model Classes
|
||||
|
||||
Model's represent the stateful data bound from
|
||||
input from the user.
|
||||
"""
|
||||
|
||||
import json
|
||||
import yaml
|
||||
|
||||
|
||||
class Model:
|
||||
"""Base model"""
|
||||
|
||||
fields = []
|
||||
|
||||
def to_json(self):
|
||||
"""Marshals the model to json"""
|
||||
return json.dumps(self.__dict__)
|
||||
|
||||
def to_yaml(self):
|
||||
"""Marshals the model to yaml"""
|
||||
return yaml.dump(self.__dict__)
|
||||
|
||||
|
||||
class Field:
|
||||
"""Base field class
|
||||
|
||||
New field types inherit this class, provides access to
|
||||
validation checks and type definitions.
|
||||
"""
|
||||
default_error_messages = {
|
||||
'invalid_choice': ('Value %(value)r is not a valid choice.'),
|
||||
'blank': ('This field cannot be blank.')
|
||||
}
|
||||
|
||||
def __init__(self, name=None, blank=False):
|
||||
self.name = name
|
||||
self.blank = blank
|
||||
|
||||
|
||||
class ChoiceField(Field):
|
||||
""" Choices Field
|
||||
|
||||
Provide a list of known options
|
||||
|
||||
:param list options: list of options to choose from
|
||||
"""
|
||||
|
||||
def __init__(self, options):
|
||||
self.options = options
|
||||
|
||||
def list_options(cls):
|
||||
return cls.options
|
|
@ -1,117 +0,0 @@
|
|||
# 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/>.
|
||||
|
||||
""" Filesystem Model
|
||||
|
||||
Provides storage device selection and additional storage
|
||||
configuration.
|
||||
|
||||
"""
|
||||
import logging
|
||||
import json
|
||||
import argparse
|
||||
|
||||
from subiquity import models
|
||||
from subiquity.models.blockdev import Blockdev
|
||||
from probert import prober
|
||||
from probert.storage import StorageInfo
|
||||
log = logging.getLogger('subiquity.filesystemModel')
|
||||
|
||||
|
||||
class FilesystemModel(models.Model):
|
||||
""" Model representing storage options
|
||||
"""
|
||||
|
||||
fs_menu = [
|
||||
'Connect iSCSI network disk',
|
||||
'Connect Ceph network disk',
|
||||
'Create volume group (LVM2)',
|
||||
'Create software RAID (MD)',
|
||||
'Setup hierarchichal storage (bcache)'
|
||||
]
|
||||
|
||||
partition_menu = [
|
||||
'Add first GPT partition',
|
||||
'Format or create swap on entire device (unusual, advanced)'
|
||||
]
|
||||
|
||||
supported_filesystems = [
|
||||
'ext4',
|
||||
'xfs',
|
||||
'btrfs',
|
||||
'swap',
|
||||
'bcache cache',
|
||||
'bcache store',
|
||||
'leave unformatted'
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
self.storage = {}
|
||||
self.info = {}
|
||||
self.devices = {}
|
||||
self.options = argparse.Namespace(probe_storage=True,
|
||||
probe_network=False)
|
||||
self.prober = prober.Prober(self.options)
|
||||
self.probe_storage()
|
||||
|
||||
def probe_storage(self):
|
||||
self.prober.probe()
|
||||
self.storage = self.prober.get_results().get('storage')
|
||||
log.debug('storage probe data:\n{}'.format(
|
||||
json.dumps(self.storage, indent=4, sort_keys=True)))
|
||||
|
||||
# TODO: replace this with Storage.get_device_by_match()
|
||||
# which takes a lambda fn for matching
|
||||
VALID_MAJORS = ['8', '253']
|
||||
for disk in self.storage.keys():
|
||||
if self.storage[disk]['DEVTYPE'] == 'disk' and \
|
||||
self.storage[disk]['MAJOR'] in VALID_MAJORS:
|
||||
log.debug('disk={}\n{}'.format(disk,
|
||||
json.dumps(self.storage[disk], indent=4,
|
||||
sort_keys=True)))
|
||||
self.info[disk] = StorageInfo({disk: self.storage[disk]})
|
||||
|
||||
def get_disk(self, disk):
|
||||
if disk not in self.devices:
|
||||
self.devices[disk] = Blockdev(disk, self.info[disk].serial)
|
||||
return self.devices[disk]
|
||||
|
||||
def get_partitions(self):
|
||||
partitions = []
|
||||
for dev in self.devices.values():
|
||||
partnames = [part.path for part in dev.disk.partitions]
|
||||
partitions += partnames
|
||||
|
||||
sorted(partitions)
|
||||
return partitions
|
||||
|
||||
def get_available_disks(self):
|
||||
return sorted(self.info.keys())
|
||||
|
||||
def get_used_disks(self):
|
||||
return [dev.disk.path for dev in self.devices.values()
|
||||
if dev.available is False]
|
||||
|
||||
def get_disk_info(self, disk):
|
||||
return self.info[disk]
|
||||
|
||||
def get_disk_action(self, disk):
|
||||
return self.devices[disk].get_actions()
|
||||
|
||||
def get_actions(self):
|
||||
actions = []
|
||||
for dev in self.devices.values():
|
||||
actions += dev.get_actions()
|
||||
return actions
|
|
@ -1,31 +0,0 @@
|
|||
# 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/>.
|
||||
|
||||
""" Identity Model
|
||||
|
||||
Represents information related to identification, for example,
|
||||
User's first and last name, timezone, country, language preferences.
|
||||
"""
|
||||
|
||||
from subiquity import models
|
||||
|
||||
|
||||
class UserModel(models.Model):
|
||||
""" User class to support personal information
|
||||
"""
|
||||
username = None
|
||||
language = None
|
||||
keyboard = None
|
||||
timezone = None
|
|
@ -1,34 +0,0 @@
|
|||
# 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/>.
|
||||
|
||||
""" Install Path Model
|
||||
|
||||
Provides high level options for Ubuntu install
|
||||
|
||||
"""
|
||||
|
||||
from subiquity import models
|
||||
|
||||
|
||||
class InstallpathModel(models.Model):
|
||||
""" Model representing install options
|
||||
"""
|
||||
|
||||
install_paths = ['Install Ubuntu',
|
||||
'Install MAAS Region Server',
|
||||
'Install MAAS Cluster Server',
|
||||
'Test installation media',
|
||||
'Test machine memory']
|
||||
selected_path = None
|
|
@ -1,103 +0,0 @@
|
|||
# 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/>.
|
||||
|
||||
""" Network Model
|
||||
|
||||
Provides network device listings and extended network information
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
from subiquity import models
|
||||
import argparse
|
||||
from probert import prober
|
||||
|
||||
log = logging.getLogger('subiquity.networkModel')
|
||||
|
||||
|
||||
class SimpleInterface:
|
||||
""" A simple interface class to encapsulate network information for
|
||||
particular interface
|
||||
"""
|
||||
def __init__(self, attrs):
|
||||
self.attrs = attrs
|
||||
for i in self.attrs.keys():
|
||||
if self.attrs[i] is None:
|
||||
setattr(self, i, "Unknown")
|
||||
else:
|
||||
setattr(self, i, self.attrs[i])
|
||||
|
||||
|
||||
class NetworkModel(models.Model):
|
||||
""" Model representing network interfaces
|
||||
"""
|
||||
|
||||
additional_options = ['Set default route',
|
||||
'Bond interfaces',
|
||||
'Install network driver']
|
||||
|
||||
def __init__(self):
|
||||
self.network = {}
|
||||
self.options = argparse.Namespace(probe_storage=False,
|
||||
probe_network=True)
|
||||
self.prober = prober.Prober(self.options)
|
||||
|
||||
def probe_network(self):
|
||||
self.prober.probe()
|
||||
self.network = self.prober.get_results().get('network')
|
||||
|
||||
def get_interfaces(self):
|
||||
return [iface for iface in self.network.keys()
|
||||
if self.network[iface]['type'] == 'eth' and
|
||||
not self.network[iface]['hardware']['DEVPATH'].startswith(
|
||||
'/devices/virtual/net')]
|
||||
|
||||
def get_vendor(self, iface):
|
||||
hwinfo = self.network[iface]['hardware']
|
||||
vendor_keys = [
|
||||
'ID_VENDOR_FROM_DATABASE',
|
||||
'ID_VENDOR',
|
||||
'ID_VENDOR_ID'
|
||||
]
|
||||
for key in vendor_keys:
|
||||
try:
|
||||
return hwinfo[key]
|
||||
except KeyError:
|
||||
log.warn('Failed to get key '
|
||||
'{} from interface {}'.format(key, iface))
|
||||
pass
|
||||
|
||||
return 'Unknown Vendor'
|
||||
|
||||
def get_model(self, iface):
|
||||
hwinfo = self.network[iface]['hardware']
|
||||
model_keys = [
|
||||
'ID_MODEL_FROM_DATABASE',
|
||||
'ID_MODEL',
|
||||
'ID_MODEL_ID'
|
||||
]
|
||||
for key in model_keys:
|
||||
try:
|
||||
return hwinfo[key]
|
||||
except KeyError:
|
||||
log.warn('Failed to get key '
|
||||
'{} from interface {}'.format(key, iface))
|
||||
pass
|
||||
|
||||
return 'Unknown Model'
|
||||
|
||||
def get_iface_info(self, iface):
|
||||
ipinfo = SimpleInterface(self.network[iface]['ip'])
|
||||
return (ipinfo, self.get_vendor(iface), self.get_model(iface))
|
|
@ -1,33 +0,0 @@
|
|||
# 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/>.
|
||||
|
||||
""" Welcome Model
|
||||
|
||||
Welcome model provides user with language selection
|
||||
|
||||
"""
|
||||
|
||||
from subiquity import models
|
||||
|
||||
|
||||
class WelcomeModel(models.Model):
|
||||
""" Model representing language selection
|
||||
"""
|
||||
|
||||
supported_languages = ['English', 'Belgian', 'German', 'Italian']
|
||||
selected_language = None
|
||||
|
||||
def __repr__(self):
|
||||
return "<Selected: {}>".format(self.selected_language)
|
|
@ -13,14 +13,98 @@
|
|||
# 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/>.
|
||||
|
||||
""" Network Model
|
||||
|
||||
Provides network device listings and extended network information
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
import argparse
|
||||
from probert import prober
|
||||
from urwid import (WidgetWrap, ListBox, Pile, BoxAdapter, Text, Columns)
|
||||
from subiquity.ui.lists import SimpleList
|
||||
from subiquity.ui.buttons import confirm_btn, cancel_btn
|
||||
from subiquity.ui.utils import Padding, Color
|
||||
|
||||
|
||||
log = logging.getLogger('subiquity.networkView')
|
||||
log = logging.getLogger('subiquity.network')
|
||||
|
||||
|
||||
class SimpleInterface:
|
||||
""" A simple interface class to encapsulate network information for
|
||||
particular interface
|
||||
"""
|
||||
def __init__(self, attrs):
|
||||
self.attrs = attrs
|
||||
for i in self.attrs.keys():
|
||||
if self.attrs[i] is None:
|
||||
setattr(self, i, "Unknown")
|
||||
else:
|
||||
setattr(self, i, self.attrs[i])
|
||||
|
||||
|
||||
class NetworkModel:
|
||||
""" Model representing network interfaces
|
||||
"""
|
||||
|
||||
additional_options = ['Set default route',
|
||||
'Bond interfaces',
|
||||
'Install network driver']
|
||||
|
||||
def __init__(self):
|
||||
self.network = {}
|
||||
self.options = argparse.Namespace(probe_storage=False,
|
||||
probe_network=True)
|
||||
self.prober = prober.Prober(self.options)
|
||||
|
||||
def probe_network(self):
|
||||
self.prober.probe()
|
||||
self.network = self.prober.get_results().get('network')
|
||||
|
||||
def get_interfaces(self):
|
||||
return [iface for iface in self.network.keys()
|
||||
if self.network[iface]['type'] == 'eth' and
|
||||
not self.network[iface]['hardware']['DEVPATH'].startswith(
|
||||
'/devices/virtual/net')]
|
||||
|
||||
def get_vendor(self, iface):
|
||||
hwinfo = self.network[iface]['hardware']
|
||||
vendor_keys = [
|
||||
'ID_VENDOR_FROM_DATABASE',
|
||||
'ID_VENDOR',
|
||||
'ID_VENDOR_ID'
|
||||
]
|
||||
for key in vendor_keys:
|
||||
try:
|
||||
return hwinfo[key]
|
||||
except KeyError:
|
||||
log.warn('Failed to get key '
|
||||
'{} from interface {}'.format(key, iface))
|
||||
pass
|
||||
|
||||
return 'Unknown Vendor'
|
||||
|
||||
def get_model(self, iface):
|
||||
hwinfo = self.network[iface]['hardware']
|
||||
model_keys = [
|
||||
'ID_MODEL_FROM_DATABASE',
|
||||
'ID_MODEL',
|
||||
'ID_MODEL_ID'
|
||||
]
|
||||
for key in model_keys:
|
||||
try:
|
||||
return hwinfo[key]
|
||||
except KeyError:
|
||||
log.warn('Failed to get key '
|
||||
'{} from interface {}'.format(key, iface))
|
||||
pass
|
||||
|
||||
return 'Unknown Model'
|
||||
|
||||
def get_iface_info(self, iface):
|
||||
ipinfo = SimpleInterface(self.network[iface]['ip'])
|
||||
return (ipinfo, self.get_vendor(iface), self.get_model(iface))
|
||||
|
||||
|
||||
class NetworkView(WidgetWrap):
|
|
@ -1,81 +0,0 @@
|
|||
# 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/>.
|
||||
|
||||
from subiquity.controllers.welcome import WelcomeController
|
||||
from subiquity.controllers.installpath import InstallpathController
|
||||
from subiquity.controllers.network import NetworkController
|
||||
from subiquity.controllers.filesystem import FilesystemController
|
||||
|
||||
|
||||
class RoutesError(Exception):
|
||||
""" Error in routes """
|
||||
pass
|
||||
|
||||
|
||||
class Routes:
|
||||
""" Defines application routes and maps to their controller
|
||||
|
||||
Routes are inserted top down from start to finish. Maintaining
|
||||
this order is required for routing to work.
|
||||
"""
|
||||
routes = [WelcomeController,
|
||||
InstallpathController,
|
||||
NetworkController,
|
||||
FilesystemController]
|
||||
current_route_idx = 0
|
||||
|
||||
@classmethod
|
||||
def route(cls, idx):
|
||||
""" Include route listing in controllers """
|
||||
try:
|
||||
_route = cls.routes[idx]
|
||||
except IndexError:
|
||||
raise RoutesError("Failed to load Route at index: {}".format(idx))
|
||||
return _route
|
||||
|
||||
@classmethod
|
||||
def current_idx(cls):
|
||||
""" Returns current route index """
|
||||
return cls.current_route_idx
|
||||
|
||||
@classmethod
|
||||
def reset(cls):
|
||||
""" Resets current route """
|
||||
cls.current_route_idx = 0
|
||||
|
||||
@classmethod
|
||||
def first(cls):
|
||||
""" first controller/start of install """
|
||||
return cls.route(0)
|
||||
|
||||
@classmethod
|
||||
def last(cls):
|
||||
""" end of install, last controller """
|
||||
return cls.route(-1)
|
||||
|
||||
@classmethod
|
||||
def current(cls):
|
||||
""" return current route's controller """
|
||||
return cls.route(cls.current_route_idx)
|
||||
|
||||
@classmethod
|
||||
def next(cls):
|
||||
cls.current_route_idx = cls.current_route_idx + 1
|
||||
return cls.route(cls.current_route_idx)
|
||||
|
||||
@classmethod
|
||||
def prev(cls):
|
||||
cls.current_route_idx = cls.current_route_idx - 1
|
||||
return cls.route(cls.current_route_idx)
|
|
@ -15,17 +15,19 @@
|
|||
|
||||
import urwid
|
||||
|
||||
SIGNALS = {}
|
||||
|
||||
class Signal:
|
||||
signals = [
|
||||
'welcome:show',
|
||||
'welcome:finish',
|
||||
'installpath:show',
|
||||
'installpath:finish',
|
||||
'filesystem:show',
|
||||
'filesystem:show-disk-partition',
|
||||
'filesystem:finish-disk-partition',
|
||||
'filesystem:finish',
|
||||
'filesystem:add-disk-partition'
|
||||
]
|
||||
|
||||
def register_signal(obj, name):
|
||||
if obj.__class__ not in SIGNALS:
|
||||
SIGNALS[obj.__class__] = []
|
||||
if name not in SIGNALS[obj.__class__]:
|
||||
SIGNALS[obj.__class__].append(name)
|
||||
urwid.register_signal(obj.__class__, SIGNALS[obj.__class__])
|
||||
|
||||
|
||||
def emit_signal(obj, name, args):
|
||||
register_signal(obj, name)
|
||||
urwid.emit_signal(obj, name, args)
|
||||
def register_signals(self):
|
||||
urwid.register_signal(Signal, self.signals)
|
||||
|
|
|
@ -15,9 +15,8 @@
|
|||
|
||||
""" Base Frame Widget """
|
||||
|
||||
from urwid import Frame, WidgetWrap
|
||||
from urwid import Frame, WidgetWrap, register_signal
|
||||
from subiquity.ui.anchors import Header, Footer, Body
|
||||
from subiquity.signals import register_signal
|
||||
import logging
|
||||
|
||||
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
# 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/>.
|
|
@ -13,16 +13,35 @@
|
|||
# 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/>.
|
||||
|
||||
from urwid import (WidgetWrap, ListBox, Pile, BoxAdapter)
|
||||
""" Welcome
|
||||
|
||||
Welcome provides user with language selection
|
||||
|
||||
"""
|
||||
import logging
|
||||
from urwid import (WidgetWrap, ListBox, Pile, BoxAdapter, emit_signal)
|
||||
from subiquity.ui.lists import SimpleList
|
||||
from subiquity.ui.buttons import confirm_btn, cancel_btn
|
||||
from subiquity.ui.utils import Padding, Color
|
||||
|
||||
log = logging.getLogger('subiquity.welcome')
|
||||
|
||||
|
||||
class WelcomeModel:
|
||||
""" Model representing language selection
|
||||
"""
|
||||
|
||||
supported_languages = ['English', 'Belgian', 'German', 'Italian']
|
||||
selected_language = None
|
||||
|
||||
def __repr__(self):
|
||||
return "<Selected: {}>".format(self.selected_language)
|
||||
|
||||
|
||||
class WelcomeView(WidgetWrap):
|
||||
def __init__(self, model, cb):
|
||||
def __init__(self, model, signal):
|
||||
self.model = model
|
||||
self.cb = cb
|
||||
self.signal = signal
|
||||
self.items = []
|
||||
self.body = [
|
||||
Padding.center_79(self._build_model_inputs()),
|
||||
|
@ -49,7 +68,7 @@ class WelcomeView(WidgetWrap):
|
|||
height=len(sl))
|
||||
|
||||
def confirm(self, button):
|
||||
return self.cb(button.label)
|
||||
emit_signal(self.signal, 'welcome:finish', button.label)
|
||||
|
||||
def cancel(self, button):
|
||||
return self.cb(None)
|
||||
emit_signal(self.signal, 'welcome:finish', None)
|
Loading…
Reference in New Issue