# 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/ # ----------------------------------------------------------------------------- # DBSchema.pm # - provides database schema of the OpenSLX config-db. # ----------------------------------------------------------------------------- package OpenSLX::DBSchema; use strict; use warnings; use OpenSLX::Basics; ################################################################################ ### DB-schema definition ### This hash-ref describes the current OpenSLX configuration database ### schema. ### Each table is defined by a list of column descriptions (and optionally ### a list of default values). ### A column description is simply the name of the column followed by ':' ### followed by the data type description. The following data types are ### currently supported: ### b => boolean (providing the values 1 and 0 only) ### i => integer (32-bit, signed) ### s.20 => string, followed by length argument (in this case: 20) ### pk => primary key (integer) ### fk => foreign key (integer) ################################################################################ my $VERSION = 0.36; my $DbSchema = { 'version' => $VERSION, 'tables' => { 'client' => { # a client is a PC booting via network 'cols' => [ 'id:pk', # primary key 'name:s.128', # official name of PC (e.g. as given by sticker # on case) 'mac:s.20', # MAC of NIC used for booting 'comment:s.1024', # internal comment (optional, for admins) ], 'vals' => [ { # add default client 'id' => 0, 'name' => '<<>>', 'comment' => 'internal client that holds default values', }, ], }, 'client_attr' => { # attributes of clients 'cols' => [ 'id:pk', # primary key 'client_id:fk', # foreign key to client 'name:s.128', # attribute name 'value:s.255', # attribute value ], }, 'client_system_ref' => { # clients referring to the systems they should offer for booting 'cols' => [ 'client_id:fk', # foreign key 'system_id:fk', # foreign key ], }, 'export' => { # an export describes a vendor-OS "wrapped" in some kind of exporting # format (NFS or NBD-squash). This represents the rootfs that the # clients will see. 'cols' => [ 'id:pk', # primary key 'name:s.64', # unique name of export, is automatically # constructed like this: # - 'vendor_os_id:fk', # foreign key 'comment:s.1024', # internal comment (optional, for admins) 'type:s.10', # 'nbd', 'nfs', ... 'server_ip:s.16', # IP of exporting server, if empty the # boot server will be used 'port:i', # some export types need to use a specific # port for each incarnation, if that's the # case you can specify it here 'uri:s.255', # path to export (squashfs or NFS-path), if # empty it will be auto-generated by # config-demuxer ], }, 'global_info' => { # a home for global counters and other info 'cols' => [ 'id:s.32', # key 'value:s.128', # value ], 'vals' => [ { # add nbd-server-port 'id' => 'next-nbd-server-port', 'value' => '5000', }, ], }, 'groups' => { # a group encapsulates a set of clients as one entity, managing # a group-specific attribute set. All the different attribute # sets a client inherits via group membership are folded into # one resulting attribute set with respect to each group's priority. 'cols' => [ 'id:pk', # primary key 'name:s.128', # name of group 'priority:i', # priority, used for order in group-list # (from 0-highest to 99-lowest) 'comment:s.1024', # internal comment (optional, for admins) ], }, 'group_attr' => { # attributes of groups 'cols' => [ 'id:pk', # primary key 'group_id:fk', # foreign key to group 'name:s.128', # attribute name 'value:s.255', # attribute value ], }, 'group_client_ref' => { # groups referring to their clients 'cols' => [ 'group_id:fk', # foreign key 'client_id:fk', # foreign key ], }, 'group_system_ref' => { # groups referring to the systems each of their clients should # offer for booting 'cols' => [ 'group_id:fk', # foreign key 'system_id:fk', # foreign key ], }, 'installed_plugin' => { # holds the plugins that have been installed into a specific # vendor-OS 'cols' => [ 'id:pk', # primary key 'vendor_os_id:fk', # foreign key 'plugin_name:s.64', # name of installed plugin # (e.g. suse-9.3-kde, debian-3.1-ppc, # suse-10.2-cloned-from-kiwi). # This is used as the folder name for the # corresponding stage1, too. ], }, 'installed_plugin_attr' => { # (stage1-)attributes of installed plugins 'cols' => [ 'id:pk', # primary key 'installed_plugin_id:fk', # foreign key to installed plugin 'name:s.128', # attribute name 'value:s.255', # attribute value ], }, 'meta' => { # information about the database as such 'cols' => [ 'plugin_info_hash:s.32', # hash-value identifying a specific # set of plugins and their # attributes 'schema_version:s.5', # schema-version currently # implemented by DB ], 'vals' => [ { 'plugin_info_hash' => '', 'schema_version' => $VERSION, }, ], }, 'system' => { # a system describes one bootable instance of an export, it # represents a selectable line in the PXE boot menu of all the # clients associated with this system 'cols' => [ 'id:pk', # primary key 'export_id:fk', # foreign key 'name:s.64', # unique name of system, is automatically # constructed like this: # -- 'label:s.64', # name visible to user (pxe-label) # if empty, this will be autocreated from # the name 'kernel:s.128', # path to kernel file, relative to /boot 'description:s.512', # visible description (for PXE TEXT) 'pxe_prefix_ip:s.16', # ip prefix for PXE Menu entry 'comment:s.1024', # internal comment (optional, for admins) ], 'vals' => [ { # add default system 'id' => 0, 'name' => '<<>>', 'comment' => 'internal system that holds default values', }, ], }, 'system_attr' => { # attributes of systems 'cols' => [ 'id:pk', # primary key 'system_id:fk', # foreign key to system 'name:s.128', # attribute name 'value:s.255', # attribute value ], }, 'vendor_os' => { # a vendor-OS describes a folder containing an operating system as # provided by the vendor (a.k.a. unchanged and thus updatable) 'cols' => [ 'id:pk', # primary key 'name:s.48', # structured name of OS installation # (e.g. suse-9.3-kde, debian-3.1-ppc, # suse-10.2-cloned-from-kiwi). # This is used as the folder name for the # corresponding stage1, too. 'comment:s.1024', # internal comment (optional, for admins) 'clone_source:s.255', # if vendor-OS was cloned, this contains # the rsync-URI pointing to the original ], }, }, }; ################################################################################ ### ### standard methods ### ################################################################################ sub new { my $class = shift; my $self = { }; return bless $self, $class; } sub checkAndUpgradeDBSchemaIfNecessary { my $self = shift; my $configDB = shift; my $metaDB = $configDB->{'meta-db'}; vlog(2, "trying to determine schema version..."); my $currVersion = $metaDB->schemaFetchDBVersion(); if (!defined $currVersion) { # that's bad, someone has messed with our DB: there is a # database, but the 'meta'-table is empty. # There might still be data in the other tables, but we have no way to # find out which schema version they're in. So it's safer to give up. croak _tr('Could not determine schema version of database'); } if ($currVersion == 0) { vlog(1, _tr('Creating DB (schema version: %s)', $DbSchema->{version})); foreach my $tableName (keys %{$DbSchema->{tables}}) { # create table (optionally inserting default values, too) $metaDB->schemaAddTable( $tableName, $DbSchema->{tables}->{$tableName}->{cols}, $DbSchema->{tables}->{$tableName}->{vals} ); } $metaDB->schemaSetDBVersion($DbSchema->{version}); $configDB->cleanupAnyInconsistencies() or die _tr('unable to cleanup DB!'); vlog(1, _tr('DB has been created successfully')); } elsif ($currVersion < $DbSchema->{version}) { vlog( 1, _tr( 'Our schema-version is %s, DB is %s, upgrading DB...', $DbSchema->{version}, $currVersion ) ); $self->_schemaUpgradeDBFrom($metaDB, $currVersion); $configDB->cleanupAnyInconsistencies() or die _tr('unable to cleanup DB!'); vlog(1, _tr('upgrade done')); } else { vlog(1, _tr('DB matches current schema version (%s)', $currVersion)); } return 1; } sub getColumnsOfTable { my $self = shift; my $tableName = shift; return map { (/^(\w+)\W/) ? $1 : $_; } @{$DbSchema->{tables}->{$tableName}->{cols}}; } ################################################################################ ### ### methods for upgrading the DB schema ### ################################################################################ my %DbSchemaHistory; sub _schemaUpgradeDBFrom { my $self = shift; my $metaDB = shift; my $currVersion = shift; foreach my $version (sort { $a <=> $b } keys %DbSchemaHistory) { next if $currVersion >= $version; vlog(0, "upgrading schema version to $version"); if ($DbSchemaHistory{$version}->($metaDB)) { $metaDB->schemaSetDBVersion($version); } } return 1; } %DbSchemaHistory = ( 0.2 => sub { my $metaDB = shift; # move attributes into separate tables ... # # ... system attributes ... $metaDB->schemaAddTable( 'system_attr', [ 'id:pk', 'system_id:fk', 'name:s.128', 'value:s.255', ] ); foreach my $system ($metaDB->fetchSystemByFilter()) { my %attrs; foreach my $key (keys %$system) { next if substr($key, 0, 5) ne 'attr_'; my $attrValue = $system->{$key} || ''; next if $system->{id} > 0 && !length($attrValue); my $newAttrName = substr($key, 5); $attrs{$newAttrName} = $attrValue; } $metaDB->setSystemAttrs($system->{id}, \%attrs); } $metaDB->schemaDropColumns( 'system', [ 'attr_automnt_dir', 'attr_automnt_src', 'attr_country', 'attr_dm_allow_shutdown', 'attr_hw_graphic', 'attr_hw_local_disk', 'attr_hw_monitor', 'attr_hw_mouse', 'attr_late_dm', 'attr_netbios_workgroup', 'attr_nis_domain', 'attr_nis_servers', 'attr_ramfs_fsmods', 'attr_ramfs_miscmods', 'attr_ramfs_nicmods', 'attr_ramfs_screen', 'attr_sane_scanner', 'attr_scratch', 'attr_slxgrp', 'attr_start_alsasound', 'attr_start_atd', 'attr_start_cron', 'attr_start_dreshal', 'attr_start_ntp', 'attr_start_nfsv4', 'attr_start_printer', 'attr_start_samba', 'attr_start_snmp', 'attr_start_sshd', 'attr_start_syslog', 'attr_start_x', 'attr_start_xdmcp', 'attr_tex_enable', 'attr_timezone', 'attr_tvout', 'attr_vmware', ], [ 'id:pk', 'export_id:fk', 'name:s.64', 'label:s.64', 'kernel:s.128', 'kernel_params:s.512', 'hidden:b', 'comment:s.1024', ] ); # # ... client attributes ... $metaDB->schemaAddTable( 'client_attr', [ 'id:pk', 'client_id:fk', 'name:s.128', 'value:s.255', ] ); foreach my $client ($metaDB->fetchClientByFilter()) { my %attrs; foreach my $key (keys %$client) { next if substr($key, 0, 5) ne 'attr_'; my $attrValue = $client->{$key} || ''; next if !length($attrValue); my $newAttrName = substr($key, 5); $attrs{$newAttrName} = $attrValue; } $metaDB->setClientAttrs($client->{id}, \%attrs); } $metaDB->schemaDropColumns( 'client', [ 'attr_automnt_dir', 'attr_automnt_src', 'attr_country', 'attr_dm_allow_shutdown', 'attr_hw_graphic', 'attr_hw_local_disk', 'attr_hw_monitor', 'attr_hw_mouse', 'attr_late_dm', 'attr_netbios_workgroup', 'attr_nis_domain', 'attr_nis_servers', 'attr_sane_scanner', 'attr_scratch', 'attr_slxgrp', 'attr_start_alsasound', 'attr_start_atd', 'attr_start_cron', 'attr_start_dreshal', 'attr_start_ntp', 'attr_start_nfsv4', 'attr_start_printer', 'attr_start_samba', 'attr_start_snmp', 'attr_start_sshd', 'attr_start_syslog', 'attr_start_x', 'attr_start_xdmcp', 'attr_tex_enable', 'attr_timezone', 'attr_tvout', 'attr_vmware', ], [ 'id:pk', 'name:s.128', 'mac:s.20', 'boot_type:s.20', 'unbootable:b', 'kernel_params:s.128', 'comment:s.1024', ] ); # # ... group attributes ... $metaDB->schemaAddTable( 'group_attr', [ 'id:pk', 'group_id:fk', 'name:s.128', 'value:s.255', ] ); foreach my $group ($metaDB->fetchGroupByFilter()) { my %attrs; foreach my $key (keys %$group) { next if substr($key, 0, 5) ne 'attr_'; my $attrValue = $group->{$key} || ''; next if !length($attrValue); my $newAttrName = substr($key, 5); $attrs{$newAttrName} = $attrValue; } $metaDB->setGroupAttrs($group->{id}, \%attrs); } $metaDB->schemaDropColumns( 'groups', [ 'attr_automnt_dir', 'attr_automnt_src', 'attr_country', 'attr_dm_allow_shutdown', 'attr_hw_graphic', 'attr_hw_local_disk', 'attr_hw_monitor', 'attr_hw_mouse', 'attr_late_dm', 'attr_netbios_workgroup', 'attr_nis_domain', 'attr_nis_servers', 'attr_sane_scanner', 'attr_scratch', 'attr_slxgrp', 'attr_start_alsasound', 'attr_start_atd', 'attr_start_cron', 'attr_start_dreshal', 'attr_start_ntp', 'attr_start_nfsv4', 'attr_start_printer', 'attr_start_samba', 'attr_start_snmp', 'attr_start_sshd', 'attr_start_syslog', 'attr_start_x', 'attr_start_xdmcp', 'attr_tex_enable', 'attr_timezone', 'attr_tvout', 'attr_vmware', ], [ 'id:pk', 'name:s.128', 'priority:i', 'comment:s.1024', ] ); return 1; }, 0.21 => sub { my $metaDB = shift; # add new table installed_plugin $metaDB->schemaAddTable( 'installed_plugin', [ 'id:pk', 'vendor_os_id:fk', 'plugin_name:s.64', ] ); return 1; }, 0.22 => sub { my $metaDB = shift; # dummy schema change, just to trigger the attribute synchronization # into the default system return 1; }, 0.23 => sub { my $metaDB = shift; # add new column system.description $metaDB->schemaAddColumns( 'system', [ 'description:s.512', ], undef, [ 'id:pk', 'export_id:fk', 'name:s.64', 'label:s.64', 'kernel:s.128', 'kernel_params:s.512', 'hidden:b', 'description:s.512', 'comment:s.1024', ] ); return 1; }, 0.24 => sub { my $metaDB = shift; # split theme::name into theme::splash, theme::displaymanager and # theme::desktop foreach my $system ($metaDB->fetchSystemByFilter()) { my $attrs = $system->{attrs} || {}; next if !exists $attrs->{'theme::name'}; $attrs->{'theme::splash'} = $attrs->{'theme::displaymanager'} = $attrs->{'theme::desktop'} = $attrs->{'theme::name'}; delete $attrs->{'theme::name'}; $metaDB->setSystemAttrs($system->{id}, $attrs); } # force all plugin names to lowercase foreach my $vendorOS ($metaDB->fetchVendorOSByFilter()) { my @installedPlugins = $metaDB->fetchInstalledPlugins($vendorOS->{id}); foreach my $plugin (@installedPlugins) { my $pluginName = $plugin->{plugin_name}; $metaDB->removeInstalledPlugin($vendorOS->{id}, $pluginName); $metaDB->addInstalledPlugin($vendorOS->{id}, lc($pluginName)); } } return 1; }, 0.25 => sub { my $metaDB = shift; # drop attribute ramfs_screen $metaDB->removeAttributeByName('ramfs_screen'); return 1; }, 0.26 => sub { my $metaDB = shift; # rename all exports and systems that contain a single colon to # the current naming scheme with a double colon foreach my $system ($metaDB->fetchSystemByFilter()) { if ($system->{name} =~ m{^([^:]+):([^:]+)$}) { if ($system->{label} eq $system->{name}) { $system->{label} = "${1}::${2}"; } $system->{name} = "${1}::${2}"; $metaDB->changeSystem([ $system->{id} ], [ $system ]); } } foreach my $export ($metaDB->fetchExportByFilter()) { if ($export->{name} =~ m{^([^:]+):([^:]+)$}) { $export->{name} = "${1}::${2}"; $metaDB->changeExport([ $export->{id} ], [ $export ]); } } return 1; }, 0.27 => sub { my $metaDB = shift; # add default vendor-OS, which holds info about the plugins that shall # be automatically installed into all vendor-OS that are being created. $metaDB->addVendorOS([{ id => '0', name => '<<>>', comment => 'holds default plugins for all vendor-OS', }]); return 1; }, 0.28 => sub { my $metaDB = shift; # correct effects of implementation error last time around that caused # the default vendor-OS to not have any plugins at all - so we add # the default plugins here: # OLTA: deactivated for good since this does not work anymore with newer # implementations (as addInstalledPlugin requires the table # 'installed_plugin_attr', which is going to be created in db-schema # version 0.29 (see below) # $metaDB->addInstalledPlugin(0, 'theme'); return 1; }, 0.29 => sub { my $metaDB = shift; # add new table installed_plugin_attrs $metaDB->schemaAddTable( 'installed_plugin_attr', [ 'id:pk', 'installed_plugin_id:fk', 'name:s.128', 'value:s.255', ], ); return 1; }, 0.30 => sub { my $metaDB = shift; # dummy schema change, just to trigger the attribute synchronization # into the default system (required since plugins have been added # and removed) return 1; }, 0.31 => sub { my $metaDB = shift; # dummy schema change, just to trigger the attribute synchronization # again, as the respective code has been extended return 1; }, 0.32 => sub { my $metaDB = shift; # dummy schema change, just to trigger the attribute synchronization, # as the 'theme' plugin has been removed return 1; }, 0.33 => sub { my $metaDB = shift; # add new column meta.plugin_info_hash $metaDB->schemaAddColumns( 'meta', [ 'plugin_info_hash:s.32', ], undef, [ 'plugin_info_hash:s.32', 'schema_version:s.5', ] ); return 1; }, 0.34 => sub { my $metaDB = shift; # turn client fields 'boot_type', 'kernel_params' and 'unbootable' # into attributes: foreach my $client ($metaDB->fetchClientByFilter()) { my $attrs = $metaDB->fetchClientAttrs($client->{id}); $attrs->{boot_type} = $client->{boot_type} || 'pxe'; $attrs->{kernel_params_client} = $client->{kernel_params}; $attrs->{unbootable} = $client->{unbootable}; $metaDB->setClientAttrs($client->{id}, $attrs); } $metaDB->schemaDropColumns( 'client', [ 'boot_type', 'kernel_params', 'unbootable', ], [ 'id:pk', 'name:s.128', 'mac:s.20', 'comment:s.1024', ] ); # turn system fields 'hidden' and 'kernel_params' into attributes: foreach my $system ($metaDB->fetchSystemByFilter()) { my $attrs = $metaDB->fetchSystemAttrs($system->{id}); $attrs->{hidden} = $system->{hidden}; $attrs->{kernel_params} = $system->{kernel_params}; $metaDB->setSystemAttrs($system->{id}, $attrs); } $metaDB->schemaDropColumns( 'system', [ 'hidden', 'kernel_params', ], [ 'id:pk', 'export_id:fk', 'name:s.64', 'label:s.64', 'kernel:s.128', 'description:s.512', 'comment:s.1024', ] ); return 1; }, 0.35 => sub { my $metaDB = shift; # add new column system.pxe_prefix_ip $metaDB->schemaAddColumns( 'system', [ 'pxe_prefix_ip:s.16', ], undef ); return 1; }, 0.36 => sub { my $metaDB = shift; # value 'preboot-cd' in client-attr 'boot_type' has been changed # to 'preboot', and a separate attribute 'preboot_media' has been # introduced: foreach my $client ($metaDB->fetchClientByFilter()) { my $attrs = $metaDB->fetchClientAttrs($client->{id}); if ($attrs->{boot_type} eq 'preboot-cd') { $attrs->{boot_type} = 'preboot'; $attrs->{preboot_media} = 'cd'; $metaDB->setClientAttrs($client->{id}, $attrs); } } return 1; }, ); 1;