Skip to content

Commit

Permalink
Merge pull request #1716 from mwhudson/refactor-install-package-from-…
Browse files Browse the repository at this point in the history
…pool

factor out code to install a package from the pool in the live session
  • Loading branch information
mwhudson authored Jul 12, 2023
2 parents 5af40f0 + cb1e760 commit f8bf110
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 56 deletions.
4 changes: 2 additions & 2 deletions subiquity/client/controllers/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
from subiquity.common.apidef import LinkAction, NetEventAPI
from subiquity.common.types import (
ErrorReportKind,
WLANSupportInstallState,
PackageInstallState,
)

log = logging.getLogger('subiquity.client.controllers.network')
Expand Down Expand Up @@ -91,7 +91,7 @@ async def apply_error_POST(self, stage: str) -> None:
self.view.show_network_error(stage)

async def wlan_support_install_finished_POST(
self, state: WLANSupportInstallState) -> None:
self, state: PackageInstallState) -> None:
if self.view:
self.view.update_for_wlan_support_install_state(state.name)

Expand Down
4 changes: 2 additions & 2 deletions subiquity/common/apidef.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
UPCSInitiateResponse,
UPCSWaitResponse,
UsernameValidation,
WLANSupportInstallState,
PackageInstallState,
ZdevInfo,
WSLConfigurationBase,
WSLConfigurationAdvanced,
Expand Down Expand Up @@ -484,7 +484,7 @@ class LinkAction(enum.Enum):
@api
class NetEventAPI:
class wlan_support_install_finished:
def POST(state: WLANSupportInstallState) -> None: ...
def POST(state: PackageInstallState) -> None: ...

class update_link:
def POST(act: LinkAction, info: Payload[NetDevInfo]) -> None: ...
Expand Down
4 changes: 2 additions & 2 deletions subiquity/common/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ def typeclass(self):
return self.type


class WLANSupportInstallState(enum.Enum):
class PackageInstallState(enum.Enum):
NOT_NEEDED = enum.auto()
NOT_AVAILABLE = enum.auto()
INSTALLING = enum.auto()
Expand All @@ -244,7 +244,7 @@ class WLANSupportInstallState(enum.Enum):
@attr.s(auto_attribs=True)
class NetworkStatus:
devices: List[NetDevInfo]
wlan_support_install_state: WLANSupportInstallState
wlan_support_install_state: PackageInstallState


class ProbeStatus(enum.Enum):
Expand Down
61 changes: 11 additions & 50 deletions subiquity/server/controllers/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,10 @@

import asyncio
import logging
import os
from typing import List, Optional

import aiohttp

import apt

from subiquitycore.async_helpers import (
run_bg_task,
schedule_task,
Expand All @@ -33,7 +30,6 @@
StaticConfig,
WLANConfig,
)
from subiquitycore.utils import arun_command

from subiquity.common.api.client import make_client_for_conn
from subiquity.common.apidef import (
Expand All @@ -44,7 +40,7 @@
from subiquity.common.errorreport import ErrorReportKind
from subiquity.common.types import (
NetworkStatus,
WLANSupportInstallState,
PackageInstallState,
)
from subiquity.server.controller import SubiquityController

Expand Down Expand Up @@ -125,12 +121,7 @@ def maybe_start_install_wpasupplicant(self):
self._install_wpasupplicant())

def wlan_support_install_state(self):
if self.install_wpasupplicant_task is None:
return WLANSupportInstallState.NOT_NEEDED
elif self.install_wpasupplicant_task.done():
return self.install_wpasupplicant_task.result()
else:
return WLANSupportInstallState.INSTALLING
return self.app.package_installer.state_for_pkg('wpasupplicant')

async def _install_wpasupplicant(self):
if self.opts.dry_run:
Expand All @@ -139,47 +130,17 @@ async def _install_wpasupplicant(self):
for k in self.app.debug_flags:
if k.startswith('wlan_install='):
a = k.split('=', 2)[1]
r = getattr(WLANSupportInstallState, a)
r = getattr(PackageInstallState, a)
else:
r = await self._really_install_wpasupplicant()
r = await self.app.package_installer.install_pkg('wpasupplicant')
log.debug("wlan_support_install_finished %s", r)
self._call_clients("wlan_support_install_finished", r)
if r == WLANSupportInstallState.DONE:
if r == PackageInstallState.DONE:
for dev in self.pending_wlan_devices:
self._send_update(LinkAction.NEW, dev)
self.pending_wlan_devices = set()
return r

async def _really_install_wpasupplicant(self):
log.debug('checking if wpasupplicant is available')
cache = apt.Cache()
binpkg = cache.get('wpasupplicant')
if not binpkg:
log.debug('wpasupplicant not found')
return WLANSupportInstallState.NOT_AVAILABLE
if binpkg.installed:
log.debug('wpasupplicant already installed')
return WLANSupportInstallState.DONE
if not binpkg.candidate.uri.startswith('cdrom:'):
log.debug(
'wpasupplicant not available from cdrom (rather %s)',
binpkg.candidate.uri)
return WLANSupportInstallState.NOT_AVAILABLE
env = os.environ.copy()
env['DEBIAN_FRONTEND'] = 'noninteractive'
apt_opts = [
'--quiet', '--assume-yes',
'--option=Dpkg::Options::=--force-unsafe-io',
'--option=Dpkg::Options::=--force-confold',
]
cp = await arun_command(
['apt-get', 'install'] + apt_opts + ['wpasupplicant'], env=env)
log.debug('apt-get install wpasupplicant returned %s', cp)
if cp.returncode == 0:
return WLANSupportInstallState.DONE
else:
return WLANSupportInstallState.FAILED

def load_autoinstall_data(self, data):
if data is not None:
self.ai_data = data
Expand Down Expand Up @@ -284,7 +245,7 @@ async def GET(self) -> NetworkStatus:
self.apply_config(silent=True)
self.view_shown = True
if self.wlan_support_install_state() == \
WLANSupportInstallState.DONE:
PackageInstallState.DONE:
devices = self.model.get_all_netdevs()
else:
devices = [
Expand All @@ -298,7 +259,7 @@ async def GET(self) -> NetworkStatus:
async def configured(self):
self.model.has_network = self.network_event_receiver.has_default_route
self.model.needs_wpasupplicant = (
self.wlan_support_install_state() == WLANSupportInstallState.DONE)
self.wlan_support_install_state() == PackageInstallState.DONE)
await super().configured()

async def POST(self) -> None:
Expand Down Expand Up @@ -374,13 +335,13 @@ def new_link(self, dev):
if dev.type == 'wlan':
self.maybe_start_install_wpasupplicant()
state = self.wlan_support_install_state()
if state == WLANSupportInstallState.INSTALLING:
if state == PackageInstallState.INSTALLING:
self.pending_wlan_devices.add(dev)
return
elif state in [WLANSupportInstallState.FAILED,
WLANSupportInstallState.NOT_AVAILABLE]:
elif state in [PackageInstallState.FAILED,
PackageInstallState.NOT_AVAILABLE]:
return
# WLANSupportInstallState.DONE falls through
# PackageInstallState.DONE falls through
self._send_update(LinkAction.NEW, dev)

def update_link(self, dev):
Expand Down
93 changes: 93 additions & 0 deletions subiquity/server/pkghelper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Copyright 2023 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 asyncio
import logging
import os
from typing import Dict, Optional

import apt

from subiquitycore.utils import arun_command

from subiquity.common.types import PackageInstallState

log = logging.getLogger('subiquity.server.pkghelper')


class PackageInstaller:
"""Install packages from the pool on the ISO in the live session.
Sometimes we need packages from the pool in the live session, for
example to install wpasupplicant when wlan interfaces are detected
by the server installer.
"""

def __init__(self):
self.pkgs: Dict[str, asyncio.Task] = {}
self._cache: Optional[apt.Cache] = None

@property
def cache(self):
if self._cache is None:
self._cache = apt.Cache()
return self._cache

def state_for_pkg(self, pkgname: str) -> PackageInstallState:
t = self.pkgs.get(pkgname)
if t is None:
return PackageInstallState.NOT_NEEDED
if t.done():
return t.result()
else:
return PackageInstallState.INSTALLING

def start_installing_pkg(self, pkgname: str) -> None:
if pkgname not in self.pkgs:
self.pkgs[pkgname] = asyncio.create_task(
self._install_pkg(pkgname))

async def install_pkg(self, pkgname) -> PackageInstallState:
self.start_installing_pkg(pkgname)
return await self.pkgs[pkgname]

async def _install_pkg(self, pkgname: str) -> PackageInstallState:
log.debug('checking if %s is available', pkgname)
binpkg = self.cache.get(pkgname)
if not binpkg:
log.debug('%s not found', pkgname)
return PackageInstallState.NOT_AVAILABLE
if binpkg.installed:
log.debug('%s already installed', pkgname)
return PackageInstallState.DONE
if not binpkg.candidate.uri.startswith('cdrom:'):
log.debug(
'%s not available from cdrom (rather %s)',
pkgname, binpkg.candidate.uri)
return PackageInstallState.NOT_AVAILABLE
env = os.environ.copy()
env['DEBIAN_FRONTEND'] = 'noninteractive'
apt_opts = [
'--quiet', '--assume-yes',
'--option=Dpkg::Options::=--force-unsafe-io',
'--option=Dpkg::Options::=--force-confold',
]
cp = await arun_command(
['apt-get', 'install'] + apt_opts + [pkgname], env=env)
log.debug('apt-get install %s returned %s', pkgname, cp)
if cp.returncode == 0:
return PackageInstallState.DONE
else:
return PackageInstallState.FAILED
2 changes: 2 additions & 0 deletions subiquity/server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
HTTPGeoIPStrategy,
)
from subiquity.server.errors import ErrorController
from subiquity.server.pkghelper import PackageInstaller
from subiquity.server.runner import get_command_runner
from subiquity.server.snapdapi import make_api_client
from subiquity.server.types import InstallerChannels
Expand Down Expand Up @@ -315,6 +316,7 @@ def __init__(self, opts, block_log_dir):
self.event_syslog_id = 'subiquity_event.{}'.format(os.getpid())
self.log_syslog_id = 'subiquity_log.{}'.format(os.getpid())
self.command_runner = get_command_runner(self)
self.package_installer = PackageInstaller()

self.error_reporter = ErrorReporter(
self.context.child("ErrorReporter"), self.opts.dry_run, self.root)
Expand Down

0 comments on commit f8bf110

Please sign in to comment.