Merge pull request #275 from CanonicalLtd/maas-option
Re-add installpath controller, with added MAAS views.
This commit is contained in:
commit
37d93a1954
|
@ -2,6 +2,8 @@ Welcome:
|
|||
lang: en_US
|
||||
Keyboard:
|
||||
layout: us
|
||||
Installpath:
|
||||
path: ubuntu
|
||||
Network:
|
||||
accept-default: yes
|
||||
Filesystem:
|
||||
|
|
|
@ -18,27 +18,18 @@ import logging
|
|||
import lsb_release
|
||||
|
||||
from subiquitycore.controller import BaseController
|
||||
from subiquitycore.ui.dummy import DummyView
|
||||
|
||||
from subiquity.models.installpath import InstallpathModel
|
||||
from subiquity.ui.views import InstallpathView
|
||||
from subiquity.ui.views import InstallpathView, MAASView
|
||||
|
||||
log = logging.getLogger('subiquity.controller.installpath')
|
||||
|
||||
|
||||
class InstallpathController(BaseController):
|
||||
signals = [
|
||||
('menu:installpath:main', 'installpath'),
|
||||
('installpath:install-ubuntu', 'install_ubuntu'),
|
||||
# ('installpath:maas-region-server', 'install_maas_region_server'),
|
||||
# ('installpath:maas-cluster-server', 'install_maas_cluster_server'),
|
||||
# ('installpath:test-media', 'test_media'),
|
||||
# ('installpath:test-memory', 'test_memory')
|
||||
]
|
||||
|
||||
def __init__(self, common):
|
||||
super().__init__(common)
|
||||
self.model = InstallpathModel()
|
||||
self.model = self.base_model.installpath
|
||||
self.answers = self.all_answers.get("Installpath", {})
|
||||
|
||||
def installpath(self):
|
||||
title = "Ubuntu %s"%(lsb_release.get_distro_information()['RELEASE'],)
|
||||
|
@ -51,25 +42,62 @@ class InstallpathController(BaseController):
|
|||
|
||||
self.ui.set_header(title, excerpt)
|
||||
self.ui.set_footer(footer)
|
||||
self.ui.set_body(InstallpathView(self.model, self.signal))
|
||||
self.ui.set_body(InstallpathView(self.model, self))
|
||||
if 'path' in self.answers:
|
||||
path = self.answers['path']
|
||||
self.model.path = path
|
||||
if path == 'ubuntu':
|
||||
self.install_ubuntu()
|
||||
else:
|
||||
self.model.update(self.answers)
|
||||
self.signal.emit_signal('next-screen')
|
||||
|
||||
default = installpath
|
||||
|
||||
def cancel(self):
|
||||
self.signal.emit_signal('prev-screen')
|
||||
|
||||
def choose_path(self, path):
|
||||
self.model.path = path
|
||||
getattr(self, 'install_' + path)()
|
||||
|
||||
def install_ubuntu(self):
|
||||
log.debug("Installing Ubuntu path chosen.")
|
||||
self.signal.emit_signal('next-screen')
|
||||
|
||||
def install_maas_region_server(self):
|
||||
self.ui.set_body(DummyView(self.signal))
|
||||
def install_maas_region(self):
|
||||
# show region questions, seed model
|
||||
title = "Metal as a Service (MAAS) Regional Controller Setup"
|
||||
excerpt = _(
|
||||
"MAAS runs a software-defined data centre - it turns a "
|
||||
"collection of physical servers and switches into a bare "
|
||||
"metal cloud with full open source IP address management "
|
||||
"(IPAM) and instant provisioning on demand. By choosing "
|
||||
"to install MAAS, a MAAS Region Controller API server and "
|
||||
"PostgreSQL database will be installed."
|
||||
)
|
||||
self.ui.set_header(title, excerpt)
|
||||
self.ui.set_footer("")
|
||||
self.ui.set_body(MAASView(self.model, self))
|
||||
|
||||
def install_maas_cluster_server(self):
|
||||
self.ui.set_body(DummyView(self.signal))
|
||||
def install_maas_rack(self):
|
||||
# show cack questions, seed model
|
||||
title = "Metal as a Service (MAAS) Rack Controller Setup"
|
||||
excerpt = _(
|
||||
"The MAAS rack controller (maas-rackd) provides highly available, fast "
|
||||
"and local broadcast services to the machines provisioned by MAAS. You "
|
||||
"need a MAAS rack controller attached to each fabric (which is a set of "
|
||||
"trunked switches). You can attach multiple rack controllers to these "
|
||||
"physical networks for high availability, with secondary rack controllers "
|
||||
"automatically stepping to provide these services if the primary rack "
|
||||
"controller fails. By choosing to install a MAAS Rack controller, you will "
|
||||
"have to connect it to a Region controller to service your machines."
|
||||
)
|
||||
|
||||
def test_media(self):
|
||||
self.ui.set_body(DummyView(self.signal))
|
||||
self.ui.set_header(title, excerpt)
|
||||
self.ui.set_footer("")
|
||||
self.ui.set_body(MAASView(self.model, self))
|
||||
|
||||
def test_memory(self):
|
||||
self.ui.set_body(DummyView(self.signal))
|
||||
def setup_maas(self, result):
|
||||
self.model.update(result)
|
||||
self.signal.emit_signal('next-screen')
|
||||
|
|
|
@ -33,6 +33,7 @@ class Subiquity(Application):
|
|||
controllers = [
|
||||
"Welcome",
|
||||
"Keyboard",
|
||||
"Installpath",
|
||||
"Network",
|
||||
"Filesystem",
|
||||
"Identity",
|
||||
|
|
|
@ -26,16 +26,39 @@ class InstallpathModel(object):
|
|||
('UI Text seen by user', <signal name>, <callback function string>)
|
||||
"""
|
||||
|
||||
def _refresh_install_paths(self):
|
||||
# TODO: Re-enable once available
|
||||
self.install_paths = [
|
||||
(_('Install Ubuntu'), 'installpath:install-ubuntu'),
|
||||
# ('Install MAAS Region Server', 'installpath:maas-region-server'),
|
||||
# ('Install MAAS Cluster Server', 'installpath:maas-cluster-server'),
|
||||
# ('Test installation media', 'installpath:test-media'),
|
||||
# ('Test machine memory', 'installpath:test-memory')
|
||||
path = None
|
||||
packages = {}
|
||||
debconf = {}
|
||||
|
||||
@property
|
||||
def paths(self):
|
||||
return [
|
||||
(_('Install Ubuntu'), 'ubuntu'),
|
||||
(_('Install MAAS Region Controller'), 'maas_region'),
|
||||
(_('Install MAAS Rack Controller'), 'maas_rack'),
|
||||
]
|
||||
|
||||
def get_menu(self):
|
||||
self._refresh_install_paths()
|
||||
return self.install_paths
|
||||
def update(self, results):
|
||||
if self.path == 'ubuntu':
|
||||
self.packages = {}
|
||||
self.debconf = {}
|
||||
elif self.path == 'maas_region':
|
||||
self.packages = {'packages': ['maas']}
|
||||
self.debconf['debconf_selections'] = {
|
||||
'maas-username': 'maas-region-controller maas/username string %s' % results['username'],
|
||||
'maas-password': 'maas-region-controller maas/password password %s' % results['password'],
|
||||
}
|
||||
elif self.path == 'maas_rack':
|
||||
self.packages = {'packages': ['maas-rack-controller']}
|
||||
self.debconf['debconf_selections'] = {
|
||||
'maas-url': 'maas-rack-controller maas-rack-controller/maas-url string %s' % results['url'],
|
||||
'maas-secret': 'maas-rack-controller maas-rack-controller/shared-secret password %s' % results['secret'],
|
||||
}
|
||||
else:
|
||||
raise ValueError("invalid Installpath %s" % self.path)
|
||||
|
||||
def render(self):
|
||||
return self.debconf
|
||||
|
||||
def render_cloudinit(self):
|
||||
return self.packages
|
||||
|
|
|
@ -22,6 +22,7 @@ from subiquitycore.models.identity import IdentityModel
|
|||
from subiquitycore.models.network import NetworkModel
|
||||
|
||||
from .filesystem import FilesystemModel
|
||||
from .installpath import InstallpathModel
|
||||
from .keyboard import KeyboardModel
|
||||
from .locale import LocaleModel
|
||||
|
||||
|
@ -43,6 +44,7 @@ class SubiquityModel:
|
|||
root = os.path.abspath(".subiquity")
|
||||
self.locale = LocaleModel(common['signal'])
|
||||
self.keyboard = KeyboardModel(root)
|
||||
self.installpath = InstallpathModel()
|
||||
self.network = NetworkModel()
|
||||
self.filesystem = FilesystemModel(common['prober'])
|
||||
self.identity = IdentityModel()
|
||||
|
@ -66,10 +68,12 @@ class SubiquityModel:
|
|||
if user.ssh_import_id is not None:
|
||||
user_info['ssh_import_id'] = [user.ssh_import_id]
|
||||
# XXX this should set up the locale too.
|
||||
return {
|
||||
config = {
|
||||
'users': [user_info],
|
||||
'hostname': self.identity.hostname,
|
||||
}
|
||||
config.update(self.installpath.render_cloudinit())
|
||||
return config
|
||||
|
||||
def _cloud_init_files(self):
|
||||
# TODO, this should be moved to the in-target cloud-config seed so on first
|
||||
|
@ -130,5 +134,6 @@ class SubiquityModel:
|
|||
}
|
||||
|
||||
config.update(self.network.render())
|
||||
config.update(self.installpath.render())
|
||||
|
||||
return config
|
||||
|
|
|
@ -26,7 +26,7 @@ from .ceph import CephDiskView # NOQA
|
|||
from .iscsi import IscsiDiskView # NOQA
|
||||
from .lvm import LVMVolumeGroupView # NOQA
|
||||
from .identity import IdentityView # NOQA
|
||||
from .installpath import InstallpathView # NOQA
|
||||
from .installpath import InstallpathView, MAASView # NOQA
|
||||
from .installprogress import ProgressView # NOQA
|
||||
from .keyboard import KeyboardView
|
||||
from .welcome import WelcomeView
|
||||
|
|
|
@ -19,48 +19,143 @@ Provides high level options for Ubuntu install
|
|||
|
||||
"""
|
||||
import logging
|
||||
from urwid import BoxAdapter
|
||||
import re
|
||||
from urwid import connect_signal, BoxAdapter, Text
|
||||
|
||||
from subiquitycore.ui.lists import SimpleList
|
||||
from subiquitycore.ui.buttons import back_btn, menu_btn
|
||||
from subiquitycore.ui.interactive import StringEditor
|
||||
from subiquitycore.ui.utils import Padding, button_pile
|
||||
from subiquitycore.ui.container import ListBox, Pile
|
||||
from subiquitycore.view import BaseView
|
||||
from subiquity.ui.views.identity import UsernameField, PasswordField, USERNAME_MAXLEN
|
||||
from subiquitycore.ui.form import (
|
||||
simple_field,
|
||||
Form,
|
||||
WantsToKnowFormField,
|
||||
)
|
||||
|
||||
|
||||
log = logging.getLogger('subiquity.installpath')
|
||||
|
||||
|
||||
class InstallpathView(BaseView):
|
||||
def __init__(self, model, signal):
|
||||
def __init__(self, model, controller):
|
||||
self.model = model
|
||||
self.signal = signal
|
||||
self.controller = controller
|
||||
self.items = []
|
||||
back = back_btn(_("Back"), on_press=self.cancel)
|
||||
self.body = [
|
||||
Padding.center_79(self._build_model_inputs()),
|
||||
Padding.line_break(""),
|
||||
self._build_buttons(),
|
||||
('pack', Text("")),
|
||||
Padding.center_79(self._build_choices()),
|
||||
('pack', Text("")),
|
||||
('pack', button_pile([back])),
|
||||
('pack', Text("")),
|
||||
]
|
||||
super().__init__(ListBox(self.body))
|
||||
super().__init__(Pile(self.body))
|
||||
|
||||
def _build_buttons(self):
|
||||
self.buttons = [
|
||||
back_btn(on_press=self.cancel),
|
||||
]
|
||||
return button_pile(self.buttons)
|
||||
|
||||
def _build_model_inputs(self):
|
||||
sl = []
|
||||
for ipath, sig in self.model.get_menu():
|
||||
log.debug("Building inputs: {}".format(ipath))
|
||||
sl.append(
|
||||
def _build_choices(self):
|
||||
choices = []
|
||||
for label, path in self.model.paths:
|
||||
log.debug("Building inputs: {}".format(path))
|
||||
choices.append(
|
||||
menu_btn(
|
||||
label=ipath, on_press=self.confirm, user_arg=sig))
|
||||
label=label, on_press=self.confirm, user_arg=path))
|
||||
return ListBox(choices)
|
||||
|
||||
return BoxAdapter(SimpleList(sl),
|
||||
height=len(sl))
|
||||
|
||||
def confirm(self, result, sig):
|
||||
self.signal.emit_signal(sig)
|
||||
def confirm(self, sender, path):
|
||||
self.controller.choose_path(path)
|
||||
|
||||
def cancel(self, button=None):
|
||||
self.signal.emit_signal('prev-screen')
|
||||
self.controller.cancel()
|
||||
|
||||
class URLEditor(StringEditor, WantsToKnowFormField):
|
||||
pass
|
||||
|
||||
URLField = simple_field(URLEditor)
|
||||
|
||||
class RegionForm(Form):
|
||||
|
||||
username = UsernameField(
|
||||
_("Pick a MAAS username:"),
|
||||
help=_("MAAS requires an administrative account to be created before you can use MAAS."))
|
||||
password = PasswordField(
|
||||
_("Choose a password:"),
|
||||
help=_("Please enter the password for the MAAS administrator's account."))
|
||||
|
||||
def validate_username(self):
|
||||
if len(self.username.value) < 1:
|
||||
return _("Username missing")
|
||||
|
||||
if len(self.username.value) > USERNAME_MAXLEN:
|
||||
return _("Username too long, must be < ") + str(USERNAME_MAXLEN)
|
||||
|
||||
if not re.match(r'[a-z_][a-z0-9_-]*', self.username.value):
|
||||
return _("Username must match NAME_REGEX, i.e. [a-z_][a-z0-9_-]*")
|
||||
|
||||
def validate_password(self):
|
||||
if len(self.password.value) < 1:
|
||||
return _("Password must be set")
|
||||
|
||||
|
||||
class RackForm(Form):
|
||||
|
||||
url = URLField(
|
||||
_("Ubuntu MAAS Region API address:"),
|
||||
help=_(
|
||||
"The MAAS rack controller and nodes need to contact "
|
||||
"the MAAS region controller API. Set the URL at which "
|
||||
"they can reach the MAAS API remotely, e.g. \"http://192.168.1.1:5240/MAAS\" "
|
||||
"Since nodes must be able to access this URL, localhost or 127.0.0.1 are not "
|
||||
"useful values here."))
|
||||
secret = PasswordField(
|
||||
_("MAAS Rack Controller shared secret:"),
|
||||
help=_(
|
||||
"The MAAS rack controller needs to contact the MAAS region "
|
||||
"controller with the shared secret found in /var/lib/maas/secret "
|
||||
"on the region controller."))
|
||||
|
||||
def validate_url(self):
|
||||
if len(self.url.value) < 1:
|
||||
return _("API address must be set")
|
||||
|
||||
def validate_secret(self):
|
||||
if len(self.secret.value) < 1:
|
||||
return _("Secret must be set")
|
||||
|
||||
|
||||
class MAASView(BaseView):
|
||||
|
||||
def __init__(self, model, controller):
|
||||
self.model = model
|
||||
self.controller = controller
|
||||
self.signal = controller.signal
|
||||
self.items = []
|
||||
|
||||
if self.model.path == "maas_region":
|
||||
self.form = RegionForm()
|
||||
elif self.model.path == "maas_rack":
|
||||
self.form = RackForm()
|
||||
else:
|
||||
raise ValueError("invalid MAAS form %s" % self.model.path)
|
||||
|
||||
connect_signal(self.form, 'submit', self.done)
|
||||
connect_signal(self.form, 'cancel', self.cancel)
|
||||
|
||||
body = Pile([
|
||||
('pack', Text("")),
|
||||
Padding.center_90(ListBox([self.form.as_rows(self)])),
|
||||
('pack', Pile([
|
||||
('pack', Text("")),
|
||||
self.form.buttons,
|
||||
('pack', Text("")),
|
||||
], focus_item=1)),
|
||||
])
|
||||
super().__init__(body)
|
||||
|
||||
def done(self, result):
|
||||
log.debug("User input: {}".format(result.as_data()))
|
||||
self.controller.setup_maas(result.as_data())
|
||||
|
||||
def cancel(self, result=None):
|
||||
self.controller.default()
|
||||
|
|
Loading…
Reference in New Issue