From 416ab8a37f1b07dc9f6c0fb3ff1a8ff2036510b5 Mon Sep 17 00:00:00 2001 From: Sebastian Schmelzer Date: Thu, 2 Sep 2010 17:50:49 +0200 Subject: change dir structure --- src/os-plugins/OpenSLX/OSPlugin/Base.pm | 631 ++++++++++++++++++++++ src/os-plugins/OpenSLX/OSPlugin/Engine.pm | 857 ++++++++++++++++++++++++++++++ src/os-plugins/OpenSLX/OSPlugin/Roster.pm | 213 ++++++++ 3 files changed, 1701 insertions(+) create mode 100644 src/os-plugins/OpenSLX/OSPlugin/Base.pm create mode 100644 src/os-plugins/OpenSLX/OSPlugin/Engine.pm create mode 100644 src/os-plugins/OpenSLX/OSPlugin/Roster.pm (limited to 'src/os-plugins/OpenSLX') diff --git a/src/os-plugins/OpenSLX/OSPlugin/Base.pm b/src/os-plugins/OpenSLX/OSPlugin/Base.pm new file mode 100644 index 00000000..2af0f04c --- /dev/null +++ b/src/os-plugins/OpenSLX/OSPlugin/Base.pm @@ -0,0 +1,631 @@ +# Copyright (c) 2006, 2007 - OpenSLX GmbH +# +# This program is free software distributed under the GPL version 2. +# See http://openslx.org/COPYING +# +# If you have any feedback please consult http://openslx.org/feedback and +# send your suggestions, praise, or complaints to feedback@openslx.org +# +# General information about OpenSLX can be found at http://openslx.org/ +# ----------------------------------------------------------------------------- +# Base.pm +# - provides empty base of the OpenSLX OSPlugin API. +# ----------------------------------------------------------------------------- +package OpenSLX::OSPlugin::Base; + +use strict; +use warnings; + +our $VERSION = 1.01; # API-version . implementation-version + +=head1 NAME + +OpenSLX::OSPlugin::Base - the base class for all OpenSLX OS-plugins. + +=head1 DESCRIPTION + +This class defines the OpenSLX API for OS-plugins. + +The general idea behind OS-plugins is to extend any installed vendor-OS with +a specific features. Each feature is implemented as a separate, small software +component in order to make them easy to understand and maintain. + +Since all of these software components are plugged into the OpenSLX system by +means of a common API, we call them Bs. + +This API can be separated into different parts: + +=over + +=item - L (provide info about a plugin) + +=item - L (installing or removing a plugin into/from a +vendor-OS) + +=item - L (integrating a plugin into an initramfs) + +=back + +=head1 MORE INFO + +Please read the user-level introduction on plugins in the OpenSLX-wiki: +L (in German). + +If you'd like to know how a plugin is implemented, please have a look at the +'example' plugin, which contains some explainations and useful hints. + +If you have any questions regarding the concept of OS-plugins and their +implementation, please drop a mail to: ot@openslx.com, or join the IRC-channel +'#openslx' (on freenode). + +=cut + +use Scalar::Util qw( weaken ); + +use OpenSLX::Basics; +use OpenSLX::OSPlugin::Roster; + +=head1 PLUGIN API + +=head2 Declarative Interface + +=over + +=item new() + +Every plugin should provide a new-method and provide it's own name in the +'name' entry of $self. + +Please note that by convention, plugin names are all lowercase! + +=cut + +sub new +{ + confess "Creating OpenSLX::OSPlugin::Base-objects directly makes no sense!"; +} + +=item initialize() + +Initializes basic context for this plugin (esp. a reference to the OSPlugin +engine that drives this plugin. + +=cut + +sub initialize +{ + my $self = shift; + + $self->{'os-plugin-engine'} = shift; + $self->{'distro'} = shift; + + weaken($self->{'os-plugin-engine'}); + # avoid circular reference between plugin and its engine + + return; +} + +=item getInfo() + +Returns a hash-ref with administrative information about this plugin (what does +it do and how does it relate to other plugins). Every plugin needs to provide +this method and return the information about itself. + +The returned hash-ref must include at least the following entries: + +=over + +=item B + +Explains the purpose of this plugins. + +=item B + +Specifies the execution precedence of this plugin with respect to all other +plugins (plugins with lower precedences will be started before the ones with +a higher precedence). + +Valid values range from 0-99. If your plugin does not have any requirements +in this context, just specify the default value '50'. + +=item B + +Specifies the list of plugins that are required by this plugin. + +Before any plugin can be installed, all other plugins that are required by it +must have been installed. + +=back + +=cut + +sub getInfo +{ + my $self = shift; + + return { + # a short (one-liner) description of this plugin + description => '', + }; +} + +=item getAttrInfo() + +Returns a hash-ref with information about all attributes supported by this +specific plugin. + +This default configuration will be added as attributes to the default system, +such that it can be overruled for any specific system by means of B. + +The returned hash-ref must include at least the following entries: + +=over + +=item B::active> + +Indicates whether or not this plugin is active (1 for active, 0 for inactive). + +=back + +=cut + +sub getAttrInfo +{ + my $self = shift; + + # This default configuration will be added as attributes to the default + # system, such that it can be overruled for any specific system by means + # of slxconfig. + return { + # attribute 'active' is mandatory for all plugins + }; +} + +=item getDefaultAttrsForVendorOS() + +Returns a hash-ref with the default attribute values for the given vendor-OS. + +=cut + +sub getDefaultAttrsForVendorOS +{ + my $self = shift; + + # the default implementation does not change the default values at all: + return $self->getAttrInfo(); +} + +=item checkStage3AttrValues() + +Checks if the stage3 values given in B<$stage3Attrs> are allowed and make sense. + +This method returns an array-ref of problems found. If there were no problems, +this methods returns undef. + +Plugins may override this implementation to do checks that for instance look +at the stage1 vendor-OS-attributes given in B<$vendorOSAttrs>. + +N.B.: this method is called while being chrooted into the vendor-OS, so it + may invoke all distro methods that expect to be run in this environment, + too + +=cut + +sub checkStage3AttrValues +{ + my $self = shift; + my $stage3Attrs = shift; + my $vendorOSAttrs = shift; + + # this default implementation does no further checks (thus relying on the + # attributte regex check that is done in the AttributeRoster) + return; +} + +=item dependsOnPlugin() + +=cut + +sub dependsOnPlugin +{ + my $self = shift; + my $otherName = shift; + + if (!defined $self->{dependsOn}) { + my @dependsOn = $self->_determineAllPluginsWeDependOn(); + $self->{dependsOn} = \@dependsOn; + } + + return grep { $_ eq $otherName } @{$self->{dependsOn}}; +} + +=back + +=head2 Vendor-OS Interface + +=over + +=item installationPhase() + +In this method, the plugin should install itself into the given vendor-OS. + +What "installation" means is up to the plugin. Some plugins may just copy +a file from the OpenSLX host installation into the vendor-OS, while others may +need to download files from the internet and/or install packages through the +vendor-OS' meta packager. + +N.B.: This method is invoked while chrooted into the vendor-OS root. + +The hash-ref given in B<$info> contains vital information for the installation +process: + +=over + +=item C + +The folder where the stage1-plugin should store all files required by the +corresponding stage3 runlevel script. + +=item C + +A temporary playground that will be cleaned up automatically. + +=item C + +In order to make the OpenSLX files from the host available, the OpenSLX base +folder (normally /opt/openslx) will be mounted into the chroot. +So if you have to copy any files from the host, fetch them from this path. + +=item C + +In order to make the OpenSLX config files from the host available, the OpenSLX +config folder (normally /etc/opt/openslx) will be mounted into the chroot. +So if you have to copy any config files from the host, fetch them from this +path. + +=item C + +Contains the attributes in effect for the installation of this plugin. + +=back + +=cut + +sub installationPhase +{ + my $self = shift; + my $info = shift; + + return; +} + +=item removalPhase() + +In this method, the plugin should remove itself from the given vendor-OS. + +What "removal" means is up to the plugin. Some plugins may just delete +a file from the vendor-OS, while others may need to uninstall packages through +the vendor-OS' meta packager. + +N.B.: This method is invoked while chrooted into the vendor-OS root. + +The hash-ref given in B<$info> contains vital information for the installation +process: + +=over + +=item C + +The folder where the stage1-plugin should store all files required by the +corresponding stage3 runlevel script. + +=item C + +A temporary playground that will be cleaned up automatically. + +=item C + +In order to make the OpenSLX files from the host available, the OpenSLX base +folder (normally /opt/openslx) will be mounted into the chroot. +So if you have to copy any files from the host, fetch them from this path. + +=item C + +In order to make the OpenSLX config files from the host available, the OpenSLX +config folder (normally /etc/opt/openslx) will be mounted into the chroot. +So if you have to copy any config files from the host, fetch them from this +path. + +=item C + +Contains the attributes in effect for the installation of this plugin. + +=back + +=cut + +sub removalPhase +{ + my $self = shift; + my $info = shift; + + return; +} + +=item preInstallationPhase() + +In this method, any preparations for installation of the plugin into a vendor-OS +should be executed. As this method is being called immediately before the chroot +is entered, this is the last/only chance to copy any files into the chroot that +are required from within (in installationPhase()). + +The given parameters are similar to the ones for installationPhase(), except +that all paths are now relative to the root-fs instead of being relative to the +chroot (i.e. the paths are ready to be used from outside the chroot): + +A "exit 1;" will result in a not installed plugin. + +=over + +=item C + +The folder where the stage1-plugin should store all files required by the +corresponding stage3 runlevel script. + +=item C + +A temporary playground that will be cleaned up automatically. + +If a plugin needs to unpack any archives, these archives should be copied to +this folder (as it will be cleaned automatically). + +=item C + +In order to make the OpenSLX files from the host available, the OpenSLX base +folder (normally /opt/openslx) will be mounted into the chroot. +So if you have to copy any files from the host, fetch them from this path. + +=item C + +In order to make the OpenSLX config files from the host available, the OpenSLX +config folder (normally /etc/opt/openslx) will be mounted into the chroot. +So if you have to copy any config files from the host, fetch them from this +path. + +=item C + +Contains the attributes in effect for the installation of this plugin. + +=item C + +Contains the path to the vendor-OS into which the plugin will be installed. + +=back + +=cut + +sub preInstallationPhase +{ + my $self = shift; + my $info = shift; + + return; +} + +=item postRemovalPhase() + +In this method, any plugin has the chance to do any necessary cleanup that +must be executed outside of the chroot. + +This method is invoked immediately after leaving the chroot into the vendor-OS +root, but before the plugin-temp-path has been cleaned up. So if required, any +files could be copied out of the temp-path somewhere into the root-fs. + +The given parameters are similar to the ones for removalPhase(), except that all +paths are now relative to the root-fs instead of being relative to the chroot +(i.e. the paths are ready to be used from outside the chroot): + +=over + +=item C + +The folder where the stage1-plugin should store all files required by the +corresponding stage3 runlevel script. + +=item C + +A temporary playground that will be cleaned up automatically. + +=item C + +In order to make the OpenSLX files from the host available, the OpenSLX base +folder (normally /opt/openslx) will be mounted into the chroot. +So if you have to copy any files from the host, fetch them from this path. + +=item C + +In order to make the OpenSLX config files from the host available, the OpenSLX +config folder (normally /etc/opt/openslx) will be mounted into the chroot. +So if you have to copy any config files from the host, fetch them from this +path. + +=item C + +Contains the attributes in effect for the installation of this plugin. + +=item C + +Contains the path to the vendor-OS from which the plugin has been removed. + +=back + +=cut + +sub postRemovalPhase +{ + my $self = shift; + my $info = shift; + + return; +} + +=back + +=head2 Initramfs Interface + +All of the following methods are invoked by the config demuxer when it makes an +initramfs for a system that has this plugin activated. Through these methods, +each plugin can integrate itself into that initramfs. + +=over + +=item suggestAdditionalKernelParams() + +Called in order to give the plugin a chance to add any kernel params it +requires. + +In order to do so, the plugin should return a list of additional kernel params +that it would like to see added. + +=cut + +sub suggestAdditionalKernelParams +{ + my $self = shift; + my $makeInitRamFSEngine = shift; + + return; +} + +=item suggestAdditionalKernelModules() + +Called in order to give the plugin a chance to add any kernel modules it +requires. + +In order to do so, the plugin should return the names of additional kernel +modules that it would like to see added. + +=cut + +sub suggestAdditionalKernelModules +{ + my $self = shift; + my $makeInitRamFSEngine = shift; + my $attrs = shift; + + return; +} + +=item copyRequiredFilesIntoInitramfs() + +Called in order to give the plugin a chance to copy all required files from the +vendor-OS into the initramfs. + +N.B.: Only files that are indeed required by the initramfs should be copied +here, i.e. files that are needed *before* the root-fs has been mounted. +All other files should be taken from the root-fs instead! + +=cut + +sub copyRequiredFilesIntoInitramfs +{ + my $self = shift; + my $targetPath = shift; + my $attrs = shift; + my $makeInitRamFSEngine = shift; + + return; +} + +=item setupPluginInInitramfs() + +Called in order to let the plugin setup all the files it requires in the +initramfs. + +Normally, you don't need to override this method in your own plugin, +as it is usually enough to override suggestAdditionalKernelParams(), +suggestAdditionalKernelModules() and maybe copyRequiredFilesIntoInitramfs(). + +=cut + +sub setupPluginInInitramfs +{ + my $self = shift; + my $attrs = shift; + my $makeInitRamFSEngine = shift; + + my $pluginName = $self->{name}; + my $pluginSrcPath = "$openslxConfig{'base-path'}/lib/plugins"; + my $buildPath = $makeInitRamFSEngine->{'build-path'}; + my $pluginInitdPath = "$buildPath/etc/plugin-init.d"; + my $initHooksPath = "$buildPath/etc/init-hooks"; + + # copy runlevel script + my $precedence = sprintf('%02d', $self->getInfo()->{precedence}); + my $scriptName = "$pluginSrcPath/$pluginName/XX_${pluginName}.sh"; + my $targetName = "$pluginInitdPath/${precedence}_${pluginName}.sh"; + if (-e $scriptName) { + $makeInitRamFSEngine->addCMD("cp $scriptName $targetName"); + $makeInitRamFSEngine->addCMD("chmod a+x $targetName"); + } + + # copy init hook scripts, if any + if (-d "$pluginSrcPath/$pluginName/init-hooks") { + my $hookSrcPath = "$pluginSrcPath/$pluginName/init-hooks"; + $makeInitRamFSEngine->addCMD( + "cp -r $hookSrcPath/* $buildPath/etc/init-hooks/" + ); + } + + # invoke hook methods to suggest additional kernel params ... + my @suggestedParams + = $self->suggestAdditionalKernelParams($makeInitRamFSEngine); + if (@suggestedParams) { + my $params = join ' ', @suggestedParams; + vlog(1, "plugin $pluginName suggests these kernel params: $params"); + $makeInitRamFSEngine->addKernelParams(@suggestedParams); + } + + # ... and kernel modules + my @suggestedModules + = $self->suggestAdditionalKernelModules($makeInitRamFSEngine, $attrs); + if (@suggestedModules) { + my $modules = join(',', @suggestedModules); + vlog(1, "plugin $pluginName suggests these kernel modules: $modules"); + $makeInitRamFSEngine->addKernelModules(@suggestedModules); + } + + # invoke hook method to copy any further files that are required in stage3 + # before the root-fs has been mounted + $self->copyRequiredFilesIntoInitramfs( + $buildPath, $attrs, $makeInitRamFSEngine + ); + + return 1; +} + +sub _determineAllPluginsWeDependOn +{ + my $self = shift; + my $seen = shift || {}; + + return if $seen->{$self->{name}}; + $seen->{$self->{name}} = 1; + + my %dependsOn; + if ($self->getInfo()->{required}) { + @dependsOn{@{$self->getInfo()->{required}}} = (); + } + + foreach my $depName (keys %dependsOn) { + my $depPlugin = OpenSLX::OSPlugin::Roster->getPlugin($depName); + my @subDeps = $depPlugin->_determineAllPluginsWeDependOn($seen); + @dependsOn{@subDeps} = (); + } + + return keys %dependsOn; +} + +=back + +1; diff --git a/src/os-plugins/OpenSLX/OSPlugin/Engine.pm b/src/os-plugins/OpenSLX/OSPlugin/Engine.pm new file mode 100644 index 00000000..25827205 --- /dev/null +++ b/src/os-plugins/OpenSLX/OSPlugin/Engine.pm @@ -0,0 +1,857 @@ +# Copyright (c) 2007, 2008 - OpenSLX GmbH +# +# This program is free software distributed under the GPL version 2. +# See http://openslx.org/COPYING +# +# If you have any feedback please consult http://openslx.org/feedback and +# send your suggestions, praise, or complaints to feedback@openslx.org +# +# General information about OpenSLX can be found at http://openslx.org/ +# ----------------------------------------------------------------------------- +# Engine.pm +# - provides driver engine for the OSPlugin API. +# ----------------------------------------------------------------------------- +package OpenSLX::OSPlugin::Engine; + +use strict; +use warnings; + +our $VERSION = 1.01; # API-version . implementation-version + +use Config; +use File::Basename; +use File::Path; +use Storable; + +use OpenSLX::Basics; +use OpenSLX::OSPlugin::Roster; +use OpenSLX::OSSetup::Engine; +use OpenSLX::ScopedResource; +use OpenSLX::Utils; + +=head1 NAME + +OpenSLX::OSPlugin::Engine - driver class for plugin handling. + +=head1 DESCRIPTION + +This class works as a driver for the installation/removal of plugins +into/from a vendor. + +Additionally, it provides the OS-Plugin support interface. + +=head1 PUBLIC METHODS + +=over + +=item new() + +Trivial constructor + +=cut + +sub new +{ + my $class = shift; + + my $self = {}; + + return bless $self, $class; +} + +=item initialize($pluginName, $vendorOSName ) + +Sets up basic data (I<$pluginName> and I<$vendorOSName>) as well as paths and +loads plugin. + +=cut + +sub initialize +{ + my $self = shift; + my $pluginName = shift; + my $vendorOSName = shift; + my $givenAttrs = shift || {}; + + $self->{'vendor-os-name'} = $vendorOSName; + + $self->{'vendor-os-path'} + = "$openslxConfig{'private-path'}/stage1/$vendorOSName"; + vlog(2, "vendor-OS path is '$self->{'vendor-os-path'}'"); + + if ($pluginName) { + $self->{'plugin-name'} = $pluginName; + $self->{'plugin-path'} + = "$openslxConfig{'base-path'}/lib/plugins/$pluginName"; + vlog(1, "plugin path is '$self->{'plugin-path'}'"); + + $self->{'plugin'} = $self->_loadPlugin(); + return if !$self->{'plugin'}; + + $self->{'chrooted-plugin-repo-path'} + = "$openslxConfig{'base-path'}/plugin-repo/$self->{'plugin-name'}"; + $self->{'plugin-repo-path'} + = "$self->{'vendor-os-path'}/$self->{'chrooted-plugin-repo-path'}"; + $self->{'chrooted-plugin-temp-path'} + = "/tmp/slx-plugin/$self->{'plugin-name'}"; + $self->{'plugin-temp-path'} + = "$self->{'vendor-os-path'}/$self->{'chrooted-plugin-temp-path'}"; + $self->{'chrooted-openslx-base-path'} = '/mnt/opt/openslx'; + $self->{'chrooted-openslx-config-path'} = '/mnt/etc/opt/openslx'; + + # merge attributes that were given on cmdline with the ones that + # already exist in the DB and finally with the default values + $self->{'plugin-attrs'} = { %$givenAttrs }; + my $defaultAttrs = $self->{plugin}->getDefaultAttrsForVendorOS( + $vendorOSName + ); + my $dbAttrs = $self->_fetchInstalledPluginAttrs($vendorOSName); + for my $attrName (keys %$defaultAttrs) { + next if exists $givenAttrs->{$attrName}; + $self->{'plugin-attrs'}->{$attrName} + = exists $dbAttrs->{$attrName} + ? $dbAttrs->{$attrName} + : $defaultAttrs->{$attrName}->{default}; + } + $self->{'vendorOS-attrs'} = $dbAttrs; + } + + return 1; +} + +=back + +=head2 Driver Interface + +The following methods are invoked by the slxos-plugin script in order to +install/remove a plugin into/from a vendor-OS: + +=over + +=item installPlugin() + +Invokes the plugin's installer method while chrooted into that vendor-OS. + +=cut + +sub installPlugin +{ + my $self = shift; + + $self->_checkIfRequiredPluginsAreInstalled(); + + # look for unknown attributes + my $attrs = $self->{'plugin-attrs'}; + my $attrInfos = $self->{plugin}->getAttrInfo(); + my @unknownAttrs = grep { !exists $attrInfos->{$_} } keys %$attrs; + if (@unknownAttrs) { + die _tr( + "The plugin '%s' does not support these attributes:\n\t%s", + $self->{'plugin-name'}, join(',', @unknownAttrs) + ); + } + + # check all attr-values against the regex of the attribute (if any) + my @attrProblems; + foreach my $attr (keys %$attrs) { + my $value = $attrs->{$attr}; + next if !defined $value; + my $attrInfo = $attrInfos->{$attr}; + my $regex = $attrInfo->{content_regex}; + if ($regex && $value !~ $regex) { + push @attrProblems, _tr( + "the value '%s' for attribute %s is not allowed.\nAllowed values are: %s", + $value, $attr, $attrInfo->{content_descr} + ); + } + } + + if (@attrProblems) { + my $complaint = join "\n", @attrProblems; + die $complaint; + } + + if ($self->{'vendor-os-name'} ne '<<>>') { + + # as the attrs may be changed by the plugin during installation, we + # have to find a way to pass them back to this process (remember: + # installation takes place in a forked process in order to do a chroot). + # We simply serialize the attributes into a temp file and deserialize + # it in the calling process. + my $serializedAttrsFile + = "$self->{'plugin-temp-path'}/serialized-attrs"; + my $chrootedSerializedAttrsFile + = "$self->{'chrooted-plugin-temp-path'}/serialized-attrs"; + + rmtree([ $self->{'plugin-repo-path'}, $self->{'plugin-temp-path'} ]); + mkpath([ $self->{'plugin-repo-path'}, $self->{'plugin-temp-path'} ]); + + # invoke plugin and let it prepare the installation + $self->{plugin}->preInstallationPhase( { + 'plugin-repo-path' => $self->{'plugin-repo-path'}, + 'plugin-temp-path' => $self->{'plugin-temp-path'}, + 'openslx-base-path' => $openslxConfig{'base-path'}, + 'openslx-config-path' => $openslxConfig{'config-path'}, + 'plugin-attrs' => $self->{'plugin-attrs'}, + 'vendor-os-path' => $self->{'vendor-os-path'}, + } ); + + # HACK: do a dummy serialization here in order to get Storable + # completely loaded (otherwise it will complain in the chroot about + # missing modules). + store $self->{'plugin-attrs'}, $serializedAttrsFile; + + $self->_callChrootedFunctionForPlugin( + sub { + # invoke plugin and let it install itself into vendor-OS + $self->{plugin}->installationPhase( { + 'plugin-repo-path' + => $self->{'chrooted-plugin-repo-path'}, + 'plugin-temp-path' + => $self->{'chrooted-plugin-temp-path'}, + 'openslx-base-path' + => $self->{'chrooted-openslx-base-path'}, + 'openslx-config-path' + => $self->{'chrooted-openslx-config-path'}, + 'plugin-attrs' + => $self->{'plugin-attrs'}, + } ); + + # serialize possibly changed attributes (executed inside chroot) + store $self->{'plugin-attrs'}, $chrootedSerializedAttrsFile; + } + ); + + # now retrieve (deserialize) the current attributes and store them + $self->{'plugin-attrs'} = retrieve $serializedAttrsFile; + + # cleanup temp path + rmtree([ $self->{'plugin-temp-path'} ]); + + # now update the vendorOS-attrs and let the plugin itself check the + # stage3 attrs + $self->{'vendorOS-attrs'} = $self->{'plugin-attrs'}; + $self->checkStage3AttrValues( + $self->{'plugin-attrs'}, \@attrProblems + ); + if (@attrProblems) { + my $complaint = join "\n", @attrProblems; + die $complaint; + } + } + + $self->_addInstalledPluginToDB(); + + return 1; +} + +=item removePlugin() + +Invokes the plugin's removal method while chrooted into that vendor-OS. + +=cut + +sub removePlugin +{ + my $self = shift; + + $self->_checkIfPluginIsRequiredByOthers(); + + if ($self->{'vendor-os-name'} ne '<<>>') { + + mkpath([ $self->{'plugin-repo-path'}, $self->{'plugin-temp-path'} ]); + + $self->_callChrootedFunctionForPlugin( + sub { + $self->{plugin}->removalPhase( { + 'plugin-repo-path' + => $self->{'chrooted-plugin-repo-path'}, + 'plugin-temp-path' + => $self->{'chrooted-plugin-temp-path'}, + 'openslx-base-path' + => $self->{'chrooted-openslx-base-path'}, + 'openslx-config-path' + => $self->{'chrooted-openslx-config-path'}, + 'plugin-attrs' + => $self->{'plugin-attrs'}, + } ); + } + ); + + # invoke plugin and let it prepare the installation + $self->{plugin}->postRemovalPhase( { + 'plugin-repo-path' => $self->{'plugin-repo-path'}, + 'plugin-temp-path' => $self->{'plugin-temp-path'}, + 'openslx-base-path' => $openslxConfig{'base-path'}, + 'openslx-config-path' => $openslxConfig{'config-path'}, + 'plugin-attrs' => $self->{'plugin-attrs'}, + 'vendor-os-path' => $self->{'vendor-os-path'}, + } ); + + rmtree([ $self->{'plugin-repo-path'}, $self->{'plugin-temp-path'} ]); + } + + $self->_removeInstalledPluginFromDB(); + + return 1; +} + +=item getInstalledPlugins() + +Returns the list of names of the plugins that are installed into the current +vendor-OS. + +=cut + +sub getInstalledPlugins +{ + my $self = shift; + + my $openslxDB = instantiateClass("OpenSLX::ConfigDB"); + $openslxDB->connect(); + my $vendorOSID = $self->_fetchVendorOSID($openslxDB); + my @installedPlugins = $openslxDB->fetchInstalledPlugins($vendorOSID); + $openslxDB->disconnect(); + + return @installedPlugins; +} + +=back + +=head2 Support Interface + +This is the plugin support interface for OS-plugins, which represents the +connection between a plugin's implementation and the rest of the OpenSLX system. + +Plugin implementations are meant to use this interface in order to find +out details about the current vendor-OS or download files or install packages. + +=over + +=item vendorOSName() + +Returns the name of the current vendor-OS. + +=cut + +sub vendorOSName +{ + my $self = shift; + + return $self->{'vendor-os-name'}; +} + +=item distroName() + +Returns the name of the distro that the current vendor-OS is based on. + +Each distro name always consists of the distro type, a dash and the +distro version, like 'suse-10.2' or 'ubuntu-7.04'. + +=cut + +sub distroName +{ + my $self = shift; + + return $self->_osSetupEngine()->distroName(); +} + +=item downloadFile($fileURL, $targetPath, $wgetOptions) + +Invokes busybox's wget to download a file from the given URL. + +=over + +=item I<$fileURL> + +The URL of the file to download. + +=item I<$targetPath> [optional] + +The directory where the file should be downloaded into. The default is the +current plugin's temp directory. + +=item I<$wgetOptions> [optional] + +Any other options you'd like to pass to wget. + +=item I + +If the downloaded was successful this method returns C<1>, otherwise it dies. + +=back + +=cut + +sub downloadFile +{ + my $self = shift; + my $fileURL = shift || return; + my $targetPath = shift || $self->{'chrooted-plugin-temp-path'}; + my $wgetOptions = shift || ''; + + my $busybox = $self->_osSetupEngine()->busyboxBinary(); + + if (slxsystem("$busybox wget -P $targetPath $wgetOptions $fileURL")) { + die _tr('unable to download file "%s"! (%s)', $fileURL, $!); + } + + return 1; +} + +=item getInstalledPackages() + +Returns the list of names of the packages (as an array) that are already +installed in the vendor-OS. +Useful if a plugin wants to find out whether or not it has to +install additional packages. + +=cut + +sub getInstalledPackages +{ + my $self = shift; + + my $packager = $self->_osSetupEngine()->packager(); + return if !$packager; + + return $packager->getInstalledPackages(); +} + +=item getInstallablePackagesForSelection() + +Looks at the selection with the given name and returns the list of names of the +packages (as one string separated by spaces) that need to be installed in order +to complete the selection. + +=cut + +sub getInstallablePackagesForSelection +{ + my $self = shift; + my $selection = shift; + + return $self->_osSetupEngine()->getInstallablePackagesForSelection( + $selection + ); +} + +=item installPackages($packages) + +Installs the given packages into the vendor-OS. + +N.B: Since this method uses the meta-packager of the vendor-OS, package +dependencies will be determined and solved automatically. + +=over + +=item I<$packages> + +Contains a list of package names (separated by spaces) that shall be installed. + +=item I + +If the packages have been installed successfully this method return 1, +otherwise it dies. + +=back + +=cut + +sub installPackages +{ + my $self = shift; + my $packages = shift; + + return if !$packages; + + my $metaPackager = $self->_osSetupEngine()->metaPackager(); + return if !$metaPackager; + + return $metaPackager->installPackages($packages, 1); +} + +=item removePackages($packages) + +Removes the given packages from the vendor-OS. + +=over + +=item I<$packages> [ARRAY-ref] + +Contains a list of package names (separated by spaces) that shall be removed. + +=item I + +If the packages have been removed successfully this method return 1, +otherwise it dies. + +=back + +=cut + +sub removePackages +{ + my $self = shift; + my $packages = shift; + + return if !$packages; + + my $metaPackager = $self->_osSetupEngine()->metaPackager(); + return if !$metaPackager; + + return $metaPackager->removePackages($packages); +} + +=back + +=head2 Driver Interface + +The following methods are invoked by the slxos-plugin script in order to +install/remove a plugin into/from a vendor-OS: + +=over + +=item checkStage3AttrValues() + +Checks if the stage3 values given in B<$stage3Attrs> are allowed and make sense. + +This method gets also invoked whenever changes by slxconfig were made (passing +in only the stage3 attributes the user tried to change) and by the config +demuxer (passing in all stage3 attributes for the system currently being +demuxed). + +If all values are ok, this method returns 1 - if not, it extends the given +problems array-ref with the problems that were found (and returns undef). + +This method chroots into the vendor-OS and then asks the plugin itself to check +the attributes. + +=cut + +sub checkStage3AttrValues +{ + my $self = shift; + my $stage3Attrs = shift; + my $problemsOut = shift; + + # we have to pass any problems back to this process (remember: + # installation takes place in a forked process in order to do a chroot). + # We simply serialize the problems into a temp file and deserialize + # it in the calling process. + my $serializedProblemsFile + = "$self->{'plugin-temp-path'}/serialized-problems"; + my $chrootedSerializedProblemsFile + = "$self->{'chrooted-plugin-temp-path'}/serialized-problems"; + + mkpath([ $self->{'plugin-repo-path'}, $self->{'plugin-temp-path'} ]); + + # HACK: do a dummy serialization here in order to get Storable + # completely loaded (otherwise it will complain in the chroot about + # missing modules). + store [], $serializedProblemsFile; + + $self->_callChrootedFunctionForPlugin( + sub { + # let plugin check by itself + my $problems = $self->{plugin}->checkStage3AttrValues( + $stage3Attrs, $self->{'vendorOS-attrs'} + ); + + # serialize list of problems (executed inside chroot) + store($problems, $chrootedSerializedProblemsFile) if $problems; + } + ); + + # now retrieve (deserialize) the found problems and pass them on + my $problems = retrieve $serializedProblemsFile; + rmtree([ $self->{'plugin-temp-path'} ]); + if ($problems && ref($problems) eq 'ARRAY' && @$problems) { + push @$problemsOut, @$problems; + return; + } + + return 1; +} + +=back + +=cut + +sub _loadPlugin +{ + my $self = shift; + + my $pluginModule = "OpenSLX::OSPlugin::$self->{'plugin-name'}"; + my $plugin = instantiateClass( + $pluginModule, { + acceptMissing => 1, + pathToClass => $self->{'plugin-path'}, + } + ); + return if !$plugin; + + # if there's a distro folder, instantiate the most appropriate distro class + my $distro; + if ($self->{'vendor-os-name'} ne '<<>>' + && -d "$self->{'plugin-path'}/OpenSLX/Distro") { + my $pluginBasePath = "$openslxConfig{'base-path'}/lib/plugins"; + my $distroScope = $plugin->{name} . '::OpenSLX::Distro'; + $distro = loadDistroModule({ + distroName => $self->distroName(), + distroScope => $distroScope, + pathToClass => $pluginBasePath, + }); + if (!$distro) { + die _tr( + 'unable to load any distro module for vendor-OS %s in plugin %s', + $self->{'vendor-os-name'}, $plugin->{name} + ); + } + $distro->initialize($self); + } + + $plugin->initialize($self, $distro); + + return $plugin; +} + +sub _callChrootedFunctionForPlugin +{ + my $self = shift; + my $function = shift; + + # create os-setup engine here in order to block access to the vendor-OS + # via other processes (which could cause problems) + my $osSetupEngine = $self->_osSetupEngine(); + + my @bindmounts; + my @chrootPerlIncludes; + + # setup list of perl modules we want to bind into chroot + push @chrootPerlIncludes, "/mnt/opt/openslx/lib"; + + push @bindmounts, { + 'source' => $Config{privlibexp}, + 'target' => "$self->{'vendor-os-path'}/mnt/perl/privlibexp" + }; + push @chrootPerlIncludes, "/mnt/perl/privlibexp"; + push @bindmounts, { + 'source' => $Config{archlibexp}, + 'target' => "$self->{'vendor-os-path'}/mnt/perl/archlibexp" + }; + push @chrootPerlIncludes, "/mnt/perl/archlibexp"; + push @bindmounts, { + 'source' => $Config{vendorlibexp}, + 'target' => "$self->{'vendor-os-path'}/mnt/perl/vendorlibexp" + }; + push @chrootPerlIncludes, "/mnt/perl/vendorlibexp"; + push @bindmounts, { + 'source' => $Config{vendorarchexp}, + 'target' => "$self->{'vendor-os-path'}/mnt/perl/vendorarchexp" + }; + push @chrootPerlIncludes, "/mnt/perl/vendorarchexp"; + + # prepare openslx bind mounts + push @bindmounts, { + 'source' => $openslxConfig{'base-path'}, + 'target' => "$self->{'vendor-os-path'}/mnt/opt/openslx" + }; + push @bindmounts, { + 'source' => $openslxConfig{'config-path'}, + 'target' => "$self->{'vendor-os-path'}/mnt/etc/opt/openslx" + }; + + # create mountpoints + foreach (@bindmounts) { + mkpath($_->{'target'}); + } + + my $pluginSession = OpenSLX::ScopedResource->new({ + name => 'osplugin::session', + acquire => sub { + # bind mount perl includes, openslx base and config paths into vendor-OS + foreach (@bindmounts) { + slxsystem("mount -o bind $_->{'source'} $_->{'target'}") == 0 + or die _tr( + "unable to bind mount '%s' to '%s'! (%s)", + $_->{'source'}, $_->{'target'}, $! + ); + } + + # add mounted perl includes to @INC + foreach (@chrootPerlIncludes) { + unshift @INC, $_; + } + 1 + }, + release => sub { + # cleanup @INC again + while (my $perlinc = pop(@chrootPerlIncludes)) { + if ($INC[0] eq $perlinc) { + shift @INC; + } + } + + # unmount bindmounts + foreach (@bindmounts) { + slxsystem("umount $_->{'target'}") == 0 + or die _tr( + "unable to umount '%s'! (%s)", + $_->{'target'}, $! + ); + } + 1 + }, + }); + + # now let plugin install itself into vendor-OS + $osSetupEngine->callChrootedFunctionForVendorOS($function); + + return; +} + +sub _addInstalledPluginToDB +{ + my $self = shift; + + my $openslxDB = instantiateClass("OpenSLX::ConfigDB"); + $openslxDB->connect(); + my $vendorOSID = $self->_fetchVendorOSID($openslxDB); + $openslxDB->addInstalledPlugin( + $vendorOSID, $self->{'plugin-name'}, $self->{'plugin-attrs'} + ); + $openslxDB->disconnect(); + + return 1; +} + +sub _checkIfRequiredPluginsAreInstalled +{ + my $self = shift; + + my $requiredPlugins = $self->{plugin}->getInfo()->{required} || []; + return 1 if !@$requiredPlugins; + + my $openslxDB = instantiateClass("OpenSLX::ConfigDB"); + $openslxDB->connect(); + my $vendorOSID = $self->_fetchVendorOSID($openslxDB); + my @installedPlugins = $openslxDB->fetchInstalledPlugins($vendorOSID); + $openslxDB->disconnect(); + + my @missingPlugins + = grep { + my $required = $_; + ! grep { $_->{plugin_name} eq $required } @installedPlugins; + } + @$requiredPlugins; + + if (@missingPlugins) { + die _tr( + 'the plugin "%s" requires the following plugins to be installed first: "%s"!', + $self->{'plugin-name'}, join(',', @missingPlugins) + ); + } + + return 1; +} + +sub _checkIfPluginIsRequiredByOthers +{ + my $self = shift; + + my $openslxDB = instantiateClass("OpenSLX::ConfigDB"); + $openslxDB->connect(); + my $vendorOSID = $self->_fetchVendorOSID($openslxDB); + my @installedPlugins = $openslxDB->fetchInstalledPlugins($vendorOSID); + $openslxDB->disconnect(); + + my @lockingPlugins + = grep { + my $installed + = OpenSLX::OSPlugin::Roster->getPlugin($_->{plugin_name}); + my $requiredByInstalled + = $installed + ? ($installed->getInfo()->{required} || []) + : []; + grep { $_ eq $self->{'plugin-name'} } @$requiredByInstalled; + } + @installedPlugins; + + if (@lockingPlugins) { + die _tr( + 'the plugin "%s" is required by the following plugins: "%s"!', + $self->{'plugin-name'}, + join(',', map { $_->{plugin_name} } @lockingPlugins) + ); + } + + return 1; +} + +sub _fetchInstalledPluginAttrs +{ + my $self = shift; + + my $openslxDB = instantiateClass("OpenSLX::ConfigDB"); + $openslxDB->connect(); + my $vendorOSID = $self->_fetchVendorOSID($openslxDB); + my $installedPlugin = $openslxDB->fetchInstalledPlugins( + $vendorOSID, $self->{'plugin-name'} + ); + $openslxDB->disconnect(); + + return {} if !$installedPlugin; + return $installedPlugin->{attrs}; +} + +sub _removeInstalledPluginFromDB +{ + my $self = shift; + + my $openslxDB = instantiateClass("OpenSLX::ConfigDB"); + $openslxDB->connect(); + my $vendorOSID = $self->_fetchVendorOSID($openslxDB); + $openslxDB->removeInstalledPlugin($vendorOSID, $self->{'plugin-name'}); + $openslxDB->disconnect(); + + return 1; +} + +sub _fetchVendorOSID +{ + my $self = shift; + my $openslxDB = shift; + + if ($self->{'vendor-os-name'} eq '<<>>') { + return 0; + } + + my $vendorOS = $openslxDB->fetchVendorOSByFilter( { + name => $self->{'vendor-os-name'}, + } ); + if (!$vendorOS) { + die _tr( + 'unable to find vendor-OS "%s" in DB!', $self->{'vendor-os-name'} + ); + } + + return $vendorOS->{id}; +} + +sub _osSetupEngine +{ + my $self = shift; + + if (!$self->{'ossetup-engine'}) { + # create ossetup-engine for given vendor-OS: + my $osSetupEngine = OpenSLX::OSSetup::Engine->new; + $osSetupEngine->initialize($self->{'vendor-os-name'}, 'plugin'); + $self->{'ossetup-engine'} = $osSetupEngine; + } + + return $self->{'ossetup-engine'}; +} + +1; diff --git a/src/os-plugins/OpenSLX/OSPlugin/Roster.pm b/src/os-plugins/OpenSLX/OSPlugin/Roster.pm new file mode 100644 index 00000000..7bfed044 --- /dev/null +++ b/src/os-plugins/OpenSLX/OSPlugin/Roster.pm @@ -0,0 +1,213 @@ +# Copyright (c) 2006, 2007 - OpenSLX GmbH +# +# This program is free software distributed under the GPL version 2. +# See http://openslx.org/COPYING +# +# If you have any feedback please consult http://openslx.org/feedback and +# send your suggestions, praise, or complaints to feedback@openslx.org +# +# General information about OpenSLX can be found at http://openslx.org/ +# ----------------------------------------------------------------------------- +# OSPlugin::Roster.pm +# - provides information about all available plugins +# ----------------------------------------------------------------------------- +package OpenSLX::OSPlugin::Roster; + +use strict; +use warnings; + +use OpenSLX::Basics; +use Clone qw(clone); + +my %plugins; + +=item C + +Returns a hash that keys the names of available plugins to their info hash. + +=cut + +sub getAvailablePlugins +{ + my $class = shift; + + $class->_init() if !%plugins; + + my %pluginInfo; + foreach my $pluginName (keys %plugins) { + $pluginInfo{$pluginName} = $plugins{$pluginName}->getInfo(); + } + return \%pluginInfo; +} + +=item C + +Returns an instance of the plugin with the given name + +=cut + +sub getPlugin +{ + my $class = shift; + my $pluginName = shift; + + $class->_init() if !%plugins; + + my $plugin = $plugins{$pluginName}; + return if !$plugin; + + return clone($plugin); +} + +=item C + +Returns a hash that contains info about the attributes support by the +given plugin + +=cut + +sub getPluginAttrInfo +{ + my $class = shift; + my $pluginName = shift; + + $class->_init() if !%plugins; + + return if !$plugins{$pluginName}; + + return $plugins{$pluginName}->getAttrInfo(); +} + +=item C + +Fetches attribute info from all available plugins and adds it to the given +hash-ref. + +If a plugin name has been given, only the attributes of that plugin will be +added. + +=over + +=item Return Value + +1 + +=back + +=cut + +sub addAllAttributesToHash +{ + my $class = shift; + my $attrInfo = shift; + my $pluginName = shift; + + return $class->_addAttributesToHash($attrInfo, $pluginName, sub { 1 } ); +} + +=item C + +Fetches attribute info relevant for stage1 (i.e. vendor-OS-attributes) +from all available plugins and adds it to the given hash-ref. + +If a plugin name has been given, only the attributes of that plugin will be +added. + +=over + +=item Return Value + +1 + +=back + +=cut + +sub addAllStage1AttributesToHash +{ + my $class = shift; + my $attrInfo = shift; + my $pluginName = shift; + + return $class->_addAttributesToHash($attrInfo, $pluginName, sub { + my $attr = shift; + return $attr->{applies_to_vendor_os}; + } ); +} + +=item C + +Fetches attribute info relevant for stage3 (i.e. system- or client-attributes) +from all available plugins and adds it to the given hash-ref. + +If a plugin name has been given, only the attributes of that plugin will be +added. + +=over + +=item Return Value + +1 + +=back + +=cut + +sub addAllStage3AttributesToHash +{ + my $class = shift; + my $attrInfo = shift; + my $pluginName = shift; + + return $class->_addAttributesToHash($attrInfo, $pluginName, sub { + my $attr = shift; + return $attr->{applies_to_systems} || $attr->{applies_to_clients}; + } ); +} + +sub _addAttributesToHash +{ + my $class = shift; + my $attrInfo = shift; + my $pluginName = shift; + my $testFunc = shift; + + $class->_init() if !%plugins; + + foreach my $plugin (values %plugins) { + next if $pluginName && $plugin->{name} ne $pluginName; + my $pluginAttrInfo = $plugin->getAttrInfo(); + foreach my $attr (keys %$pluginAttrInfo) { + next if !$testFunc->($pluginAttrInfo->{$attr}); + $attrInfo->{$attr} = clone($pluginAttrInfo->{$attr}); + } + } + return 1; +} + +sub _init +{ + my $class = shift; + + %plugins = (); + my $pluginPath = "$openslxConfig{'base-path'}/lib/plugins"; + foreach my $modulePath (glob("$pluginPath/*")) { + next if $modulePath !~ m{/([^/]+)$}; + my $pluginName = $1; + if (!-e "$modulePath/OpenSLX/OSPlugin/$pluginName.pm") { + vlog( + 1, + "skipped plugin-folder $modulePath as no corresponding perl " + . "module could be found." + ); + next; + } + my $class = "OpenSLX::OSPlugin::$pluginName"; + vlog(2, "loading plugin $class from path '$modulePath'"); + my $plugin = instantiateClass($class, { pathToClass => $modulePath }); + $plugins{$pluginName} = $plugin; + } + return; +} + +1; -- cgit v1.2.3-55-g7522