diff --git a/db/EmulabConstants.pm.in b/db/EmulabConstants.pm.in index f72ca7862a23fa8507a2357036bdd2fb5a1d5225..d2b99912b967cca4b1a31070ec04540a29730042 100644 --- a/db/EmulabConstants.pm.in +++ b/db/EmulabConstants.pm.in @@ -76,6 +76,9 @@ use vars qw(@ISA @EXPORT); TB_OSID_DESTROY TB_OSID_MIN TB_OSID_MAX TB_OSID_OSIDLEN TB_OSID_OSNAMELEN TB_OSID_VERSLEN + TB_TAINTSTATE_USERONLY TB_TAINTSTATE_BLACKBOX TB_TAINTSTATE_DANGEROUS + TB_TAINTSTATE_ALL + TB_IMAGEID_READINFO TB_IMAGEID_MODIFYINFO TB_IMAGEID_EXPORT TB_IMAGEID_CREATE TB_IMAGEID_DESTROY TB_IMAGEID_ACCESS TB_IMAGEID_MIN TB_IMAGEID_MAX @@ -375,6 +378,14 @@ sub TB_OSID_MBKERNEL() { "_KERNEL_"; } # multiboot kernel OSID sub TB_OSID_FREEBSD_MFS() { "FREEBSD-MFS" }; sub TB_OSID_FRISBEE_MFS() { "FRISBEE-MFS" }; +# OS/Node taint states +sub TB_TAINTSTATE_USERONLY() { "useronly"; }; +sub TB_TAINTSTATE_BLACKBOX() { "blackbox"; }; +sub TB_TAINTSTATE_DANGEROUS() { "dangerous"; }; +sub TB_TAINTSTATE_ALL() { (TB_TAINTSTATE_USERONLY(), + TB_TAINTSTATE_BLACKBOX(), + TB_TAINTSTATE_DANGEROUS()); }; + # ImageIDs # # Clarification: diff --git a/db/Node.pm.in b/db/Node.pm.in index ae728106240cf5cf0a7c4ceaa4b12d487c429c13..b1d9053f70233d3d5db21a48da8b856c8668a6ab 100755 --- a/db/Node.pm.in +++ b/db/Node.pm.in @@ -3742,5 +3742,144 @@ sub ClrTipAclUrl($) "where node_id='$node_id'"); } +# +# Check to see if the node is tainted, or tainted in a +# particular way. +# +sub IsTainted($;$) +{ + my ($self, $taint) = @_; + + my $taint_states = $self->taint_states(); + return 0 + if (!defined($taint_states) || $taint_states eq ""); + + # Just looking to see if any taint is applied? + return 1 + if (!defined($taint)); + + # Looking for a specific taint. + return grep {$_ eq $taint} split(',', $taint_states); +} + +# +# Get the current set of taint states for the Node +# +sub GetTaintStates($) { + my ($self) = @_; + + my $taint_states = $self->taint_states(); + return () + if (!defined($taint_states) || $taint_states eq ""); + + return split(',', $taint_states); +} + +# +# Explicitly set the taint states based on an input array of states. +# Squash any duplicates or empty/undefined entries. +# +sub SetTaintStates($@) { + my ($self, @taint_states) = @_; + + my @newtstates = (); + my @validtstates = TB_TAINTSTATE_ALL(); + + foreach my $tstate (@taint_states) { + next if (!$tstate); + if (!grep {$_ eq $tstate} @validtstates) { + warn "Invalid taint state: $tstate\n"; + return -1; + } + if (!grep {$_ eq $tstate} @newtstates) { + push @newtstates, $tstate; + } + } + + return 0 + if (!@newtstates); + + return $self->Update({"taint_states" => join(',', @newtstates)}); +} + +# +# Add a taint state to the node. +# +sub AddTaint($$) +{ + my ($self, $taint) = @_; + + if (!grep {$_ eq $taint} TB_TAINTSTATE_ALL()) { + warn "Invalid taint state: $taint\n"; + return -1; + } + + return 0 + if ($self->IsTainted($taint)); + + my $taint_states = $self->taint_states(); + if (!defined($taint_states) || $taint_states eq "") { + $taint_states = $taint; + } + else { + $taint_states .= ",$taint"; + } + + return $self->Update({"taint_states" => $taint_states}); +} + +# +# Inherit the taint states from an OS. Take the union with whatever +# taint states are already set for the node. +# +sub InheritTaintStates($$) { + my ($self, $osinfo) = @_; + require OSinfo; + + if (!ref($osinfo)) { + my $tmp = OSinfo->Lookup($osinfo); + if (!defined($tmp)) { + warn "Cannot lookup osinfo for $osinfo\n"; + return -1; + } + $osinfo = $tmp; + } + + my $os_taint_states = $osinfo->taint_states(); + return 0 + if (!defined($os_taint_states) || $os_taint_states eq ""); + my @taint_states = split(',', $os_taint_states); + my $node_taint_states = $self->taint_states(); + if ($node_taint_states) { + push @taint_states, split(',', $node_taint_states); + } + + return $self->SetTaintStates(@taint_states); +} + +# +# Remove a taint state (or all taint states) from the node. +# +sub RemoveTaint($;$) +{ + my ($self, $taint) = @_; + + return 0 + if (!$self->IsTainted($taint)); + + my $taint_states = $self->taint_states(); + return 0 + if (!defined($taint_states) || $taint_states eq ""); + + if (defined($taint)) { + $taint_states = join(',', + grep {$_ ne $taint} split(',', $taint_states)); + } else { + $taint_states = ""; + } + + return $self->Update({"taint_states" => $taint_states}); +} + # _Always_ make sure that this 1 is at the end of the file... 1; diff --git a/db/OSinfo.pm.in b/db/OSinfo.pm.in index 35377f35d96975d945c7ee93d12f56511244f1b7..4128dd610ec2b37a278ffbc173845b2a33c0c2fc 100644 --- a/db/OSinfo.pm.in +++ b/db/OSinfo.pm.in @@ -730,5 +730,115 @@ sub MapToImage($$) return Image->Lookup($imageid); } +# +# Check if the OS is tainted, or tainted in a +# particular way. +# +sub IsTainted($;$) +{ + my ($self, $taint) = @_; + + my $taint_states = $self->taint_states(); + return 0 + if (!defined($taint_states) || $taint_states eq ""); + + # Just looking to see if any taint is applied? + return 1 + if (!defined($taint)); + + # Looking for a specific taint. + return grep {$_ eq $taint} split(',', $taint_states); +} + +# +# Get the current set of taint states for the OS +# +sub GetTaintStates() { + my ($self) = @_; + + my $taint_states = $self->taint_states(); + return () + if (!defined($taint_states) || $taint_states eq ""); + + return split(',', $taint_states); +} + +# +# Explicitly set the taint states based on an input array of states. +# Squash any duplicates or empty/undefined entries. +# +sub SetTaintStates($@) { + my ($self, @taint_states) = @_; + + my @newtstates = (); + my @validtstates = TB_TAINTSTATE_ALL(); + + foreach my $tstate (@taint_states) { + next if (!$tstate); + if (!grep {$_ eq $tstate} @validtstates) { + warn "Invalid taint state: $tstate\n"; + return -1; + } + if (!grep {$_ eq $tstate} @newtstates) { + push @newtstates, $tstate; + } + } + + return 0 + if (!@newtstates); + + return $self->Update({"taint_states" => join(',', @newtstates)}); +} + +# +# Add a taint state to the OS. +# +sub AddTaint($$) +{ + my ($self, $taint) = @_; + + if (!grep {$_ eq $taint} TB_TAINTSTATE_ALL()) { + warn "Invalid taint state: $taint\n"; + return -1; + } + + return 0 + if ($self->IsTainted($taint)); + + my $taint_states = $self->taint_states(); + if (!defined($taint_states) || $taint_states eq "") { + $taint_states = $taint; + } + else { + $taint_states .= ",$taint"; + } + + return $self->Update({"taint_states" => $taint_states}); +} + +# +# Remove a taint state (or all taint states) from the OS. +# +sub RemoveTaint($;$) +{ + my ($self, $taint) = @_; + + return 0 + if (!$self->IsTainted($taint)); + + my $taint_states = $self->taint_states(); + return 0 + if (!defined($taint_states) || $taint_states eq ""); + + if (defined($taint)) { + $taint_states = join(',', + grep {$_ ne $taint} split(',', $taint_states)); + } else { + $taint_states = ""; + } + + return $self->Update({"taint_states" => $taint_states}); +} + # _Always_ make sure that this 1 is at the end of the file... 1; diff --git a/sql/database-create.sql b/sql/database-create.sql index 863acb84fe955c115e595880d988ba0d223cd7d5..9e21a1b7ae0af4192241658b07e7a025255c430d 100644 --- a/sql/database-create.sql +++ b/sql/database-create.sql @@ -2916,6 +2916,7 @@ CREATE TABLE `nodes` ( `uuid` varchar(40) NOT NULL default '', `reserved_memory` int(10) unsigned default '0', `nonfsmounts` tinyint(1) NOT NULL default '0', + `taint_states` set('useronly','blackbox','dangerous') default NULL, PRIMARY KEY (`node_id`), KEY `phys_nodeid` (`phys_nodeid`), KEY `node_id` (`node_id`,`phys_nodeid`), @@ -3151,6 +3152,7 @@ CREATE TABLE `os_info` ( `mfs` tinyint(4) NOT NULL default '0', `reboot_waittime` int(10) unsigned default NULL, `protogeni_export` tinyint(1) NOT NULL default '0', + `taint_states` set('useronly','blackbox','dangerous') default NULL, PRIMARY KEY (`osid`), UNIQUE KEY `pid` (`pid`,`osname`), KEY `OS` (`OS`), diff --git a/sql/database-fill.sql b/sql/database-fill.sql index e67552a3eb8be9d89d18e8640483f98706381d82..0c682e3602ffc4969f1fbc9469a4a4757c195ac1 100644 --- a/sql/database-fill.sql +++ b/sql/database-fill.sql @@ -1160,6 +1160,7 @@ REPLACE INTO table_regex VALUES ('os_info','op_mode','text','regex','^[-\\w]*$', REPLACE INTO table_regex VALUES ('os_info','nextosid','text','redirect','os_info:osid',0,0,NULL); REPLACE INTO table_regex VALUES ('os_info','def_parentosid','text','redirect','os_info:osid',0,0,NULL); REPLACE INTO table_regex VALUES ('os_info','reboot_waittime','int','redirect','default:int',0,2000,NULL); +REPLACE INTO table_regex VALUES ('os_info','taint_states','text','regex','^[-\\w,]*$',1,128,NULL); REPLACE INTO table_regex VALUES ('sitevariables','name','text','regex','^[\\w\\/]+$',1,255,NULL); REPLACE INTO table_regex VALUES ('sitevariables','value','text','redirect','default:text',0,0,NULL); diff --git a/sql/updates/4/385 b/sql/updates/4/385 new file mode 100644 index 0000000000000000000000000000000000000000..19eaae3f78a03b3cb9b49e211676936c171ba1c6 --- /dev/null +++ b/sql/updates/4/385 @@ -0,0 +1,33 @@ +# +# Add noexport flag to images. +# +use strict; +use libdb; + +my $impotent = 0; + +sub DoUpdate($$$) +{ + my ($dbhandle, $dbname, $version) = @_; + + if (!DBSlotExists("os_info", "taint_states")) { + DBQueryFatal("alter table os_info add ". + " `taint_states` set('useronly','blackbox','dangerous') ". + " default NULL"); + } + DBQueryFatal("REPLACE INTO table_regex VALUES ". + "('os_info','taint_states','text','regex',". + "'^[-\\\\w,]*\$',1,128,NULL)"); + + if (!DBSlotExists("nodes", "taint_states")) { + DBQueryFatal("alter table nodes add ". + " `taint_states` set('useronly','blackbox','dangerous') ". + " default NULL"); + } + + return 0; +} + +# Local Variables: +# mode:perl +# End: diff --git a/tbsetup/libosload.pm.in b/tbsetup/libosload.pm.in index e16fbb77a915798e4ab06cd6c8fc59b5f3cb6e88..c83eb57c3f49d1d390ac18c5e424e9ba31a3e07b 100644 --- a/tbsetup/libosload.pm.in +++ b/tbsetup/libosload.pm.in @@ -252,6 +252,17 @@ sub osload ($$) { tberror "$node: Could not map to object!"; goto failednode; } + + # Check to see if the node is tainted. If so, then the disk + # needs to be cleaned up (zeroed). If there was an explicit request + # to zero all node disks, then capture that here too. + my $zeronode = 0; + if ($nodeobject->IsTainted()) { + $zeronode = 2; # use maximum firepower. + } + elsif ($zerofree) { + $zeronode = $zerofree; + } # Get default imageid for this node. # NOTE that virtnodes don't have default imageids -- they are only @@ -300,6 +311,7 @@ sub osload ($$) { my $defosid; my $maxwait = 0; my @access_keys; + my @tstates = (); # # Most of the DB work related to images is determining what @@ -449,6 +461,26 @@ sub osload ($$) { my $osid = $rowref->{$partname}; if (defined($osid)) { + # Have the node inherit taint states from each OS + # to be loaded on it (or that is already loaded on + # it). This action is additive, i.e. the node + # will end up with the union of taint states + # across all partition OSes. We also retain any + # taint states the node had previously; it's + # important not to clear these existing states + # until OS loading and disk zeroing have been + # performed. + my $osinfo = OSinfo->Lookup($osid); + if (defined($osinfo)) { + if ($osinfo->IsTainted()) { + # Save new/incoming taint states for later... + push @tstates, $osinfo->GetTaintStates(); + $nodeobject->InheritTaintStates($osinfo) == 0 or + warn "Node $node could not inherit taint ". + "states from osid $osid\n"; + } + } + my %part = ( 'node_id' => $node, 'partition' => $i, @@ -545,7 +577,7 @@ sub osload ($$) { $reload_mode = "UISP"; $reload_func = \&SetupReloadUISP; $reboot_required = 0; # We don't reboot motes to reload them - $zerofree = 0; # and we don't zero "the disk" + $zeronode = 0; # and we don't zero "the disk" } else { $reload_mode = "Frisbee"; $reload_func = \&SetupReloadFrisbee; @@ -589,7 +621,8 @@ sub osload ($$) { 'osid' => $defosid, 'reboot' => $reboot_required, 'wait' => $wait_required, - 'zerofree' => $zerofree, + 'zerofree' => $zeronode, + 'tstates' => \@tstates, 'prepare' => $prepare, 'maxwait' => $maxwait, 'isremote' => $isremote, @@ -1010,7 +1043,21 @@ sub WaitTillReloadDone($$$$$@) if (!$query_result->numrows) { print STDERR "osload ($node): left reloading mode at ".`date` if ($debug); - + + # + # Now reset tainting for the node. Start off + # with a clean slate. Next, apply any taint states + # previously identified and saved off for the + # still-existing and/or newly-loaded OSes. Doing this + # now allows us to clear any remnant taint states + # nullified by OS loading and/or disk zeroing. + # + $nodeobject->RemoveTaint(); + my $tstates = $reload_info->{$node}{'tstates'}; + if (@{$tstates}) { + $nodeobject->SetTaintStates(@{$tstates}); + } + $count--; $done{$node} = 1; next; diff --git a/tbsetup/libosload_new.pm.in b/tbsetup/libosload_new.pm.in index a937e696ebf38c2d33c3ec5af56c0a9a8bd4d493..4d2036f99c31be75a9b7c5cfdd57431a8f147a72 100644 --- a/tbsetup/libosload_new.pm.in +++ b/tbsetup/libosload_new.pm.in @@ -625,6 +625,14 @@ sub osload($$$) { foreach my $k (keys(%{$nodeflags{$node}})) { $nargs{$k} = $nodeflags{$node}{$k}; } + + # XXX: this probably belongs in the code calling into libosload? + # Check to see if the node is tainted. If so, then the disk + # needs to be cleaned up (zeroed). + if ($nodeobject->IsTainted()) { + $nargs{'zerofree'} = 2; # use maximum firepower. + } + # Wait, don't do this -- waitmode is global; nowait is per-node! # # XXX hack to handle that we would see a 'nowait' flag per-node, @@ -1136,6 +1144,21 @@ sub WaitTillReloadDone($$$$$@) } else { # success! + + # + # Now reset tainting for the node. Start off + # with a clean slate. Next, apply any taint states + # previously identified and saved off for the + # still-existing and/or newly-loaded OSes. Doing this + # now allows us to clear any remnant taint states + # nullified by OS loading and/or disk zeroing. + # + $nodeobject->RemoveTaint(); + my $tstates = $self->nodeinfo($nodeobject,'tstates'); + if (@{$tstates}) { + $nodeobject->SetTaintStates(@{$tstates}); + } + $count--; $done{$node} = 1; $typeobject->ReloadDone($nodeobject); @@ -1982,6 +2005,7 @@ sub UpdatePartitions($$) # my %partitions = (); my $curmbrvers = 0; + my @tstates = (); # # XXX assumes a DOS MBR, but this is ingrained in the DB schema @@ -2077,6 +2101,26 @@ sub UpdatePartitions($$) my $osid = $rowref->{$partname}; if (defined($osid)) { + # Have the node inherit taint states from each OS + # to be loaded on it (or that is already loaded on + # it). This action is additive, i.e. the node + # will end up with the union of all taint states + # across the OSes set for each disk partition. We + # also retain any taint states the node had + # previously; it's important not to clear these + # existing states until OS loading and disk + # zeroing have been performed. + my $osinfo = OSinfo->Lookup($osid); + if (defined($osinfo)) { + if ($osinfo->IsTainted()) { + # Save new/incoming taint states for later... + push @tstates, $osinfo->GetTaintStates(); + $nodeobject->InheritTaintStates($osinfo) == 0 or + warn "Node $node_id could not inherit taint ". + "states from osid $osid\n"; + } + } + my %part = ( 'node_id' => $node_id, 'partition' => $i, @@ -2093,6 +2137,9 @@ sub UpdatePartitions($$) } } + # Store the taint states for this node object + $self->nodeinfo($nodeobject,'tstates',\@tstates); + # # Now that we have processed all images, update the actual DB # partitions table entries for this node.