]> xenbits.xensource.com Git - people/iwj/osstest.git/commitdiff
Merge remote branch 'remotes/service/incoming' into wip.interfaces
authorIan Jackson <ian.jackson@eu.citrix.com>
Thu, 31 Jan 2013 17:21:10 +0000 (17:21 +0000)
committerIan Jackson <Ian.Jackson@eu.citrix.com>
Thu, 31 Jan 2013 17:21:10 +0000 (17:21 +0000)
Conflicts:
Osstest.pm
ts-hosts-allocate
ts-xen-build-prep

1  2 
Osstest/Executive.pm
Osstest/HostDB/Executive.pm
Osstest/TestSupport.pm
make-flight
mg-hosts
ts-host-install
ts-hosts-allocate-Executive
ts-xen-build
ts-xen-build-prep
ts-xen-install

index 6d155d776ba9dbd60f3b61cb48ba256f9d401ef6,0000000000000000000000000000000000000000..c02c2ca17e3bd299b7272d891781ad5633514cf1
mode 100644,000000..100644
--- /dev/null
@@@ -1,720 -1,0 +1,729 @@@
 +
 +package Osstest::Executive;
 +
 +use strict;
 +use warnings;
 +
 +use Osstest;
 +
 +use POSIX;
 +use IO::File;
 +use File::Copy;
 +use DBI;
 +use Socket;
 +use IPC::Open2;
 +use IO::Handle;
 +use JSON;
 +use File::Basename;
 +use IO::Socket::INET;
 +#use Data::Dumper;
 +
 +BEGIN {
 +    use Exporter ();
 +    our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
 +    $VERSION     = 1.00;
 +    @ISA         = qw(Exporter);
 +    @EXPORT      = qw();
 +    %EXPORT_TAGS = (
 +      );
 +
 +    @EXPORT_OK   = qw();
 +}
 +
 +# DATABASE TABLE LOCK HIERARCHY
 +#
 +#  Lock first
 +#
 +#   flights
 +#            must be locked for any query modifying
 +#                   flights_flight_seq
 +#                   flights_harness_touched
 +#                   jobs
 +#                   steps
 +#                   runvars
 +#
 +#   resources
 +#            must be locked for any query modifying
 +#                   tasks
 +#                   tasks_taskid_seq
 +#                   resource_sharing 
 +#                   hostflags
 +#                   resource_properties
 +#
 +#   any other tables or databases
 +#
 +our (@all_lock_tables) = qw(flights resources);
 +#
 +#  Lock last
 +#
 +# READS:
 +#
 +#  Nontransactional reads are also permitted
 +#  Transactional reads must take out locks as if they were modifying
 +
 +
 +BEGIN {
 +    use Exporter ();
 +    our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
 +    $VERSION     = 1.00;
 +    @ISA         = qw(Exporter);
 +    @EXPORT      = qw(dbfl_check get_harness_rev grabrepolock_reexec
 +                      findtask @all_lock_tables
 +                      tcpconnect_queuedaemon plan_search
 +                      alloc_resources alloc_resources_rollback_begin_work
 +                      resource_check_allocated resource_shared_mark_ready
 +                      duration_estimator
 +                      opendb_state
 +                      );
 +    %EXPORT_TAGS = ( );
 +
 +    @EXPORT_OK   = qw();
 +}
 +
 +augmentconfigdefaults(
 +    ControlDaemonHost => 'woking.cam.xci-test.com',
 +    OwnerDaemonPort => 4031,
 +    QueueDaemonPort => 4032,
 +    QueueDaemonRetry => 120, # seconds
 +    QueueDaemonHoldoff => 30, # seconds
 +    QueueThoughtsTimeout => 30, # seconds
 +    QueueResourcePollInterval => 60, # seconds
 +    QueuePlanUpdateInterval => 300, # seconds
 +    Repos => "$ENV{'HOME'}/repos",
 +    BisectionRevisonGraphSize => '600x300',
 +);
 +
 +#---------- configuration reader etc. ----------
 +
 +sub opendb_tests () {
 +    $dbh_tests ||= $mjobdb->open();
 +}
 +
 +sub grabrepolock_reexec {
 +    my (@org_argv) = @_;
 +    my $repos_lock= "$c{Repos}/lock";
 +    my $repos_locked= $ENV{OSSTEST_REPOS_LOCK_LOCKED};
 +    unless (defined $repos_locked && $repos_locked eq $repos_lock) {
 +        $ENV{OSSTEST_REPOS_LOCK_LOCKED}= $repos_lock;
 +        exec "with-lock-ex","-w",$repos_lock, $0,@org_argv;
 +        die $!;
 +    }
 +}
 +
 +sub get_harness_rev () {
 +    $!=0; $?=0;  my $rev= `git rev-parse HEAD^0`;
 +    die "$? $!" unless defined $rev;
 +
 +    $rev =~ s/\n$//;
 +    die "$rev ?" unless $rev =~ m/^[0-9a-f]+$/;
 +
 +    my $diffr= system 'git diff --exit-code HEAD >/dev/null';
 +    if ($diffr) {
 +        die "$diffr $! ?" if $diffr != 256;
 +        $rev .= '+';
 +    }
 +
 +    return $rev;
 +}
 +
 +#---------- database access ----------#
 +
 +sub opendb_state () {
 +    return opendb('statedb');
 +}
 +
 +our $whoami;
 +
 +sub opendb ($) {
 +    my ($dbname) = @_;
 +
 +    my $pg= $c{"ExecutiveDbname_$dbname"};
 +
 +    if (!defined $pg) {
 +      if (!defined $whoami) {
 +          $whoami = `whoami`;  die if $?;  chomp $whoami;
 +      }
 +        my $pat= $c{ExecutiveDbnamePat};
 +        my %vars= ('dbname' => $dbname,
 +                   'whoami' => $whoami);
 +        $pat =~ s#\<(\w+)\>#
 +            my $val=$vars{$1};  defined $val or die "$pat $1 ?";
 +            $val;
 +        #ge;
 +        $pat =~ s#\<(([.~]?)(/[^<>]+))\>#
 +            my $path= $2 eq '~' ? "$ENV{HOME}/$3" : $1;
 +            my $data= get_filecontents_core_quiet($path);
 +            chomp $data;
 +            $data;
 +        #ge;
 +        $pat =~ s#\<([][])\># $1 eq '[' ? '<' : '>' #ge;
 +
 +        $pg = $c{"ExecutiveDbname_$dbname"} = $pat;
 +    }
 +
 +    my $dbh= DBI->connect("dbi:Pg:$pg", '','', {
 +        AutoCommit => 1,
 +        RaiseError => 1,
 +        ShowErrorStatement => 1,
 +        })
 +        or die "could not open state db $pg";
 +    return $dbh;
 +}
 +
 +#---------- host (and other resource) allocation ----------
 +
 +our $taskid;
 +
 +sub findtask () {
 +    return $taskid if defined $taskid;
 +    
 +    my $spec= $ENV{'OSSTEST_TASK'};
 +    my $q;
 +    my $what;
 +    if (!defined $spec) {
 +        $!=0; $?=0; my $whoami= `whoami`;   defined $whoami or die "$? $!";
 +        $!=0; $?=0; my $node=   `uname -n`; defined $node   or die "$? $!";
 +        chomp($whoami); chomp($node); $node =~ s/\..*//;
 +        my $refkey= "$whoami\@$node";
 +        $what= "static $refkey";
 +        $q= $dbh_tests->prepare(<<END);
 +            SELECT * FROM tasks
 +                    WHERE type='static' AND refkey=?
 +END
 +        $q->execute($refkey);
 +    } else {
 +        my @l = split /\s+/, $spec;
 +        @l==3 or die "$spec ".scalar(@l)." ?";
 +        $what= $spec;
 +        $q= $dbh_tests->prepare(<<END);
 +            SELECT * FROM tasks
 +                    WHERE taskid=? AND type=? AND refkey=?
 +END
 +        $q->execute(@l);
 +    }
 +    my $row= $q->fetchrow_hashref();
 +    die "no task $what ?" unless defined $row;
 +    die "task $what dead" unless $row->{live};
 +    $q->finish();
 +
 +    foreach my $k (qw(username comment)) {
 +        next if defined $row->{$k};
 +        $row->{$k}= "[no $k]";
 +    }
 +
 +    my $newspec= "$row->{taskid} $row->{type} $row->{refkey}";
 +    logm("task $newspec: $row->{username} $row->{comment}");
 +
 +    $taskid= $row->{taskid};
 +    $ENV{'OSSTEST_TASK'}= $newspec if !defined $spec;
 +
 +    return $taskid;
 +}        
 +
 +sub alloc_resources_rollback_begin_work () {
 +    $dbh_tests->rollback();
 +    db_begin_work($dbh_tests, \@all_lock_tables);
 +}
 +
 +our $alloc_resources_waitstart;
 +
 +sub tcpconnect_queuedaemon () {
 +    my $qserv= tcpconnect($c{ControlDaemonHost}, $c{QueueDaemonPort});
 +    $qserv->autoflush(1);
 +
 +    $_= <$qserv>;  defined && m/^OK ms-queuedaemon\s/ or die "$_?";
 +
 +    return $qserv;
 +}
 +
 +sub plan_search ($$$$) {
 +    my ($plan, $dbgprint, $duration, $requestlist) = @_;
 +    #
 +    # Finds first place where $requestlist can be made to fit in $oldplan
 +    # returns {
 +    #     Start =>        start time from now in seconds,
 +    #     ShareReuse =>   no of allocations which are a share reuse
 +    #   }
 +    #
 +    #  $requestlist->[]{Reso}
 +    #  $requestlist->[]{Ident}
 +    #  $requestlist->[]{Shared}          may be undef
 +    #  $requestlist->[]{SharedMaxWear}   undef iff Shared is undef
 +    #  $requestlist->[]{SharedMaxTasks}  undef iff Shared is undef
 +
 +    my $reqix= 0;
 +    my $try_time= 0;
 +    my $confirmedok= 0;
 +    my $share_wear;
 +    my $share_reuse= 0;
 +
 +    for (;;) {
 +      my $req= $requestlist->[$reqix];
 +        my $reso= $req->{Reso};
 +      my $events= $plan->{Events}{$reso};
 +
 +        $events ||= [ ];
 +
 +      # can we do $req at $try_time ?  If not, when later can we ?
 +      PERIOD:
 +      foreach (my $ix=0; $ix<@$events; $ix++) {
 +          $dbgprint->("PLAN LOOP reqs[$reqix]=$req->{Ident}".
 +              " evtix=$ix try=$try_time confirmed=$confirmedok".
 +              (defined($share_wear) ? " wear=$share_wear" : ""));
 +
 +          # check the period from $events[$ix] to next event
 +          my $startevt= $events->[$ix];
 +          my $endevt= $ix+1<@$events ? $events->[$ix+1] : { Time=>1e100 };
 +
 +          last PERIOD if $startevt->{Time} >= $try_time + $duration;
 +            # this period is entirely after the proposed slot;
 +            # so no need to check this or any later periods
 +
 +          next PERIOD if $endevt->{Time} <= $try_time;
 +            # this period is entirely before the proposed slot;
 +            # it doesn't overlap, but most check subsequent periods
 +
 +        CHECK:
 +          {
 +              $dbgprint->("PLAN LOOP   OVERLAP");
 +              last CHECK unless $startevt->{Avail};
 +              my $eshare= $startevt->{Share};
 +              if ($eshare) {
 +                  $dbgprint->("PLAN LOOP   OVERLAP ESHARE");
 +                  last CHECK unless defined $req->{Shared};
 +                  last CHECK unless $req->{Shared} eq $eshare->{Type};
 +                  if (defined $share_wear) {
 +                      $share_wear++ if $startevt->{Type} eq 'Start';
 +                  } else {
 +                      $share_wear= $eshare->{Wear}+1;
 +                  }
 +                  last CHECK if $share_wear > $req->{SharedMaxWear};
 +                  last CHECK if $eshare->{Shares} != $req->{SharedMaxTasks};
 +              }
 +              # We have suitable availability for this period
 +              $dbgprint->("PLAN LOOP   OVERLAP AVAIL OK");
 +              next PERIOD;
 +          };
 +              
 +          # nope
 +          $try_time= $endevt->{Time};
 +          $confirmedok= 0;
 +          undef $share_wear;
 +          $share_reuse= 0;
 +          $dbgprint->("PLAN LOOP   OVERLAP BAD $try_time");
 +      }
 +      $dbgprint->("PLAN NEXT reqs[$reqix]=$req->{Ident}".
 +          " try=$try_time confirmed=$confirmedok reuse=$share_reuse".
 +          (defined($share_wear) ? " wear=$share_wear" : ""));
 +
 +      $confirmedok++;
 +      $share_reuse++ if defined $share_wear;
 +      $reqix++;
 +      $reqix %= @$requestlist;
 +      last if $confirmedok==@$requestlist;
 +    }
 +
 +    return {
 +        Start => $try_time,
 +        ShareReuse => $share_reuse,
 +    };
 +}
 +
 +sub alloc_resources {
 +    my ($resourcecall) = pop @_;
 +    my (%xparams) = @_;
 +    # $resourcecall should die (abort) or return ($ok, $bookinglist)
 +    #
 +    #  values of $ok
 +    #            0  rollback, wait and try again
 +    #            1  commit, completed ok
 +    #            2  commit, wait and try again
 +    #  $bookinglist should be undef or a hash for making a booking
 +    #
 +    # $resourcecall should not look at tasks.live
 +    #  instead it should look for resources.owntaskid == the allocatable task
 +    # $resourcecall runs with all tables locked (see above)
 +
 +    my $qserv;
 +    my $retries=0;
 +    my $ok=0;
 +
 +    logm("resource allocation: starting...");
 +
 +    my $set_info= sub {
 +        return if grep { !defined } @_;
 +        my @s;
 +        foreach my $s (@_) {
 +            local ($_) = ($s);
 +            if (m#[^-+_.,/0-9a-z]# || !m/./) {
 +                s/[\\\"]/\\$&/g;
 +                s/^/\"/;
 +                s/$/\"/;
 +            }
 +            push @s, $_;
 +        }
 +        print $qserv "set-info @s\n";
 +        $_= <$qserv>;  defined && m/^OK/ or die "$_ ?";
 +    };
 +
 +    my $priority= $ENV{OSSTEST_RESOURCE_PRIORITY};
 +    if (!defined $priority) {
 +        if (open TTY_TEST, "/dev/tty") {
 +            close TTY_TEST;
 +            $priority= -10;
 +            logm("resource allocation: on tty, priority=$priority");
 +        }
 +    }
 +
 +    while ($ok==0 || $ok==2) {
 +        my $bookinglist;
 +        if (!eval {
 +            if (!defined $qserv) {
 +                $qserv= tcpconnect_queuedaemon();
 +
 +                my $waitstart= $xparams{WaitStart};
 +                if (!$waitstart) {
 +                    if (!defined $alloc_resources_waitstart) {
 +                        print $qserv "time\n" or die $!;
 +                        $_= <$qserv>;
 +                        defined or die $!;
 +                        if (m/^OK time (\d+)$/) {
 +                            $waitstart= $alloc_resources_waitstart= $1;
 +                        }
 +                    }
 +                }
 +
 +                $set_info->('priority', $priority);
 +                $set_info->('sub-priority',$ENV{OSSTEST_RESOURCE_SUBPRIORITY});
 +                $set_info->('preinfo',     $ENV{OSSTEST_RESOURCE_PREINFO});
 +
 +                if (defined $waitstart) {
 +                    $set_info->('wait-start',$waitstart);
 +                }
 +
 +                my $adjust= $xparams{WaitStartAdjust};
 +                if (defined $adjust) {
 +                    $set_info->('wait-start-adjust',$adjust);
 +                }
 +
 +                my $jobinfo= $xparams{JobInfo};
 +                if (!defined $jobinfo and defined $flight and defined $job) {
 +                    $jobinfo= "$flight.$job";
 +                }
 +                $set_info->('job', $jobinfo);
 +
 +                print $qserv "wait\n" or die $!;
 +                $_= <$qserv>;  defined && m/^OK wait\s/ or die "$_ ?";
 +            }
 +
 +            $dbh_tests->disconnect() if $dbh_tests;
 +            undef $dbh_tests;
 +
 +            logm("resource allocation: awaiting our slot...");
 +
 +            $_= <$qserv>;  defined && m/^\!OK think\s$/ or die "$_ ?";
 +
 +            opendb_tests();
 +
 +            my ($plan);
 +
 +          db_retry($flight,'running', $dbh_tests, \@all_lock_tables,
 +                   [ sub {
 +              print $qserv "get-plan\n" or die $!;
 +              $_= <$qserv>; defined && m/^OK get-plan (\d+)\s/ or die "$_ ?";
 +
 +              my $jplanlen= $1;
 +              my $jplan;
 +              read($qserv, $jplan, $jplanlen) == $jplanlen or die $!;
 +              my $jplanprint= $jplan;
 +              chomp $jplanprint;
 +              logm("resource allocation: base plan $jplanprint");
 +              $plan= from_json($jplan);
 +          }, sub {
 +              if (!eval {
 +                  ($ok, $bookinglist) = $resourcecall->($plan);
 +                  1;
 +              }) {
 +                  warn "resourcecall $@";
 +                  $ok=-1;
 +              }
 +              return db_retry_abort() unless $ok>0;
 +          }]);
 +
 +          if ($bookinglist && $ok!=-1) {
 +              my $jbookings= to_json($bookinglist);
 +                chomp($jbookings);
 +                logm("resource allocation: booking $jbookings");
 +
 +              printf $qserv "book-resources %d\n", length $jbookings
 +                  or die $!;
 +              $_= <$qserv>; defined && m/^SEND\s/ or die "$_ ?";
 +
 +              print $qserv $jbookings or die $!;
 +              $_= <$qserv>; defined && m/^OK book-resources\s/ or die "$_ ?";
 +
 +                $bookinglist= undef; # no need to undo these then
 +
 +              logm("resource allocation: we are in the plan.");
 +          }
 +
 +            if ($ok==1) {
 +                print $qserv "thought-done\n" or die $!;
 +            } elsif ($ok<0) {
 +                return 1;
 +            } else { # 0 or 2
 +                logm("resource allocation: deferring") if $ok==0;
 +                logm("resource allocation: partial commit, deferring");
 +                print $qserv "thought-wait\n" or die $!;
 +            }
 +            $_= <$qserv>;  defined && m/^OK thought\s$/ or die "$_ ?";
 +            
 +            1;
 +        }) {
 +            $retries++;
 +            die "trouble $@" if $retries > 60;
 +            chomp $@;
 +            logm("resource allocation: queue-server trouble ($@)");
 +            if ($bookinglist) {
 +                # If we have allocated things but not managed to book them
 +                # then we need to free them, or we won't reallocate them
 +                # when we retry.
 +                db_retry($flight,'running',$dbh_tests,\@all_lock_tables, sub {
 +                    my $freetask= findtask();
 +                    foreach my $book (@{ $bookinglist->{Bookings} }) {
 +                        my $alloc= $book->{Allocated};
 +                        next unless $alloc;
 +                        my @reskey= ((split / /, $book->{Reso}, 2),
 +                                     $alloc->{Shareix});
 +                        $reskey[0]= "share-$reskey[0]" if $reskey[2];
 +                        logm("resource allocation: unwinding @reskey");
 +                        my $undone= $dbh_tests->do(<<END,{},$freetask,@reskey);
 +                            UPDATE resources
 +                               SET owntaskid=(SELECT taskid FROM tasks
 +                                        WHERE type='magic' AND refkey='idle')
 +                             WHERE owntaskid=?
 +                               AND restype=? AND resname=? AND shareix=?
 +END
 +                        die "$freetask @reskey $undone" unless $undone;
 +                    }
 +                });
 +            }
 +            logm("resource allocation: will retry in $c{QueueDaemonRetry}s");
 +            sleep $c{QueueDaemonRetry};
 +            undef $qserv;
 +            $ok= 0;
 +        }
 +    }
 +    die unless $ok==1;
 +    logm("resource allocation: successful.");
 +}
 +
 +sub resource_check_allocated ($$) {
 +    my ($restype,$resname) = @_;
 +    return db_retry($dbh_tests, [qw(resources)], sub {
 +        return resource_check_allocated_core($restype,$resname);
 +    });
 +}
 +
 +sub resource_check_allocated_core ($$) {
 +    # must run in db_retry with resources locked
 +    my ($restype,$resname) = @_;
 +    my $tid= findtask();
 +    my $shared;
 +
 +    my $res= $dbh_tests->selectrow_hashref(<<END,{}, $restype, $resname);
 +        SELECT * FROM resources LEFT JOIN tasks
 +                   ON taskid=owntaskid
 +                WHERE restype=? AND resname=?
 +END
 +    die "resource $restype $resname not found" unless $res;
 +    die "resource $restype $resname no task" unless defined $res->{taskid};
 +
 +    if ($res->{type} eq 'magic' && $res->{refkey} eq 'shared') {
 +        my $shr= $dbh_tests->selectrow_hashref(<<END,{}, $restype,$resname);
 +                SELECT * FROM resource_sharing
 +                        WHERE restype=? AND resname=?
 +END
 +        die "host $resname shared but no share?" unless $shr;
 +
 +        my $shrestype= 'share-'.$restype;
 +        my $shrt= $dbh_tests->selectrow_hashref
 +            (<<END,{}, $shrestype,$resname,$tid);
 +                SELECT * FROM resources LEFT JOIN tasks ON taskid=owntaskid
 +                        WHERE restype=? AND resname=? AND owntaskid=?
 +END
 +
 +        die "resource $restype $resname not shared by $tid" unless $shrt;
 +        die "resource $resname $resname share $shrt->{shareix} task $tid dead"
 +            unless $shrt->{live};
 +
 +        my $others= $dbh_tests->selectrow_hashref
 +            (<<END,{}, $shrt->{restype}, $shrt->{resname}, $shrt->{shareix});
 +                SELECT count(*) AS ntasks
 +                         FROM resources LEFT JOIN tasks ON taskid=owntaskid
 +                        WHERE restype=? AND resname=? AND shareix!=?
 +                          AND live
 +                          AND owntaskid != (SELECT taskid FROM tasks
 +                                             WHERE type='magic'
 +                                               AND refkey='preparing')
 +END
 +
 +        $shared= { Type => $shr->{sharetype},
 +                   State => $shr->{state},
 +                   ResType => $shrestype,
 +                   Others => $others->{ntasks} };
 +    } else {
 +        die "resource $restype $resname task $res->{owntaskid} not $tid"
 +            unless $res->{owntaskid} == $tid;
 +    }
 +    die "resource $restype $resname task $res->{taskid} dead"
 +        unless $res->{live};
 +
 +    return $shared;
 +}
 +
 +sub resource_shared_mark_ready ($$$) {
 +    my ($restype, $resname, $sharetype) = @_;
 +    # must run outside transaction
 +
 +    my $what= "resource $restype $resname";
++    $sharetype .= ' '.get_harness_rev();
 +
 +    db_retry($dbh_tests, [qw(resources)], sub {
 +        my $oldshr= resource_check_allocated_core($restype, $resname);
 +        if (defined $oldshr) {
 +            die "$what shared $oldshr->{Type} not $sharetype"
 +                unless $oldshr->{Type} eq $sharetype;
 +            die "$what shared state $oldshr->{State} not prep"
 +                unless $oldshr->{State} eq 'prep';
 +            my $nrows= $dbh_tests->do(<<END,{}, $restype,$resname,$sharetype);
 +                UPDATE resource_sharing
 +                   SET state='ready'
 +                 WHERE restype=? AND resname=? AND sharetype=?
 +END
 +            die "unexpected not updated state $what $sharetype $nrows"
 +                unless $nrows==1;
 +
 +            $dbh_tests->do(<<END,{}, $oldshr->{ResType}, $resname);
 +                UPDATE resources
 +                   SET owntaskid=(SELECT taskid FROM tasks
 +                                   WHERE type='magic' AND refkey='idle')
 +                 WHERE owntaskid=(SELECT taskid FROM tasks
 +                                   WHERE type='magic' AND refkey='preparing')
 +                   AND restype=? AND resname=?
 +END
 +        }
 +    });
++    if (!eval {
++       my $qserv = tcpconnect_queuedaemon();
++       print $qserv "prod\n" or die $!;
++       $_ = <$qserv>;  defined && m/^OK prod\b/ or die "$_ ?";
++       1;
++    }) {
++       logm("post-mark-ready queue daemon prod failed: $@");
++    }
 +    logm("$restype $resname shared $sharetype marked ready");
 +}
 +
 +#---------- duration estimator ----------
 +
 +sub duration_estimator ($$;$) {
 +    my ($branch, $blessing, $debug) = @_;
 +    # returns a function which you call like this
 +    #    $durest->($job, $hostidname, $onhost)
 +    # and returns one of
 +    #    ($seconds, $samehostlaststarttime, $samehostlaststatus)
 +    #    ($seconds, undef, undef)
 +    #    ()
 +    # $debug should be something like sub { print DEBUG "@_\n"; }.
 +    # Pass '' for $hostidname and $onhost for asking about on any host
 +
 +    my $recentflights_q= $dbh_tests->prepare(<<END);
 +            SELECT f.flight AS flight,
 +                 f.started AS started,
 +                   j.status AS status
 +                   FROM flights f
 +                     JOIN jobs j USING (flight)
 +                     JOIN runvars r
 +                             ON  f.flight=r.flight
 +                            AND  r.name=?
 +                    WHERE  j.job=r.job
 +                      AND  f.blessing=?
 +                      AND  f.branch=?
 +                      AND  j.job=?
 +                      AND  r.val=?
 +                    AND  (j.status='pass' OR j.status='fail')
 +                      AND  f.started IS NOT NULL
 +                      AND  f.started >= ?
 +                 ORDER BY f.started DESC
 +END
 +
 +    my $duration_anyref_q= $dbh_tests->prepare(<<END);
 +            SELECT f.flight AS flight
 +                    FROM steps s JOIN flights f
 +                      ON s.flight=f.flight
 +                   WHERE s.job=? AND f.blessing=? AND f.branch=?
 +                       AND s.finished IS NOT NULL
 +                       AND f.started IS NOT NULL
 +                       AND f.started >= ?
 +                     ORDER BY s.finished DESC
 +END
 +    # s J J J # fix perl-mode
 +
 +    my $duration_duration_q= $dbh_tests->prepare(<<END);
 +            SELECT sum(finished-started) AS duration FROM steps
 +                        WHERE flight=? AND job=?
 +                            AND step != 'ts-hosts-allocate'
 +END
 +
 +    return sub {
 +        my ($job, $hostidname, $onhost) = @_;
 +
 +        my $dbg= $debug ? sub {
 +            $debug->("DUR $branch $blessing $job $hostidname $onhost @_");
 +        } : sub { };
 +
 +        my $refs=[];
 +        my $limit= time - 86400*14;
 +
 +        if ($hostidname ne '') {
 +            $recentflights_q->execute($hostidname,
 +                                      $blessing,
 +                                      $branch,
 +                                      $job,
 +                                      $onhost,
 +                                      $limit);
 +            $refs= $recentflights_q->fetchall_arrayref({});
 +            $recentflights_q->finish();
 +            $dbg->("SAME-HOST GOT ".scalar(@$refs));
 +        }
 +
 +        if (!@$refs) {
 +            $duration_anyref_q->execute($job, $blessing, $branch, $limit);
 +            $refs= $duration_anyref_q->fetchall_arrayref({});
 +            $duration_anyref_q->finish();
 +            $dbg->("ANY-HOST GOT ".scalar(@$refs));
 +        }
 +
 +        if (!@$refs) {
 +            $dbg->("NONE");
 +            return ();
 +        }
 +
 +        my $duration_max= 0;
 +        foreach my $ref (@$refs) {
 +            $duration_duration_q->execute($ref->{flight}, $job);
 +            my ($duration) = $duration_duration_q->fetchrow_array();
 +            $duration_duration_q->finish();
 +            if ($duration) {
 +                $dbg->("REF $ref->{flight} DURATION $duration");
 +                $duration_max= $duration
 +                    if $duration > $duration_max;
 +            }
 +        }
 +
 +        return ($duration_max, $refs->[0]{started}, $refs->[0]{status});
 +    };
 +}
 +
 +1;
index 687e02df6ba0ec9dc1c3b3ed7a1ee09fa18c7b19,0000000000000000000000000000000000000000..33a2606775c446a7e29afb535b80927124def7d1
mode 100644,000000..100644
--- /dev/null
@@@ -1,82 -1,0 +1,83 @@@
 +
 +package Osstest::HostDB::Executive;
 +
 +use strict;
 +use warnings;
 +
 +use Osstest;
 +use Osstest::Executive;
 +
 +BEGIN {
 +    use Exporter ();
 +    our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
 +    $VERSION     = 1.00;
 +    @ISA         = qw(Exporter);
 +    @EXPORT      = qw();
 +    %EXPORT_TAGS = ( );
 +
 +    @EXPORT_OK   = qw();
 +}
 +
 +sub new { return bless {}, $_[0]; }
 +
 +sub get_properties ($$$) {
 +    my ($hd, $name, $hp) = @_;
 +    
 +    my $q = $dbh_tests->prepare(<<END);
 +        SELECT * FROM resource_properties
 +            WHERE restype='host' AND resname=?
 +END
 +    $q->execute($name);
 +    foreach my ($row = $q->fetchrow_hashref()) {
 +      my $name = $row->{name};
 +      $hp{propname_massage($name)} = $row->{val};
 +    }
 +}
 +
 +sub get_flags ($$) {
 +    my ($hd, $ho) = @_;
 +
 +    my $flags = { };
 +    my $flagsq= $dbh_tests->prepare(<<END);
 +        SELECT hostflag FROM hostflags WHERE hostname=?
 +END
 +    $flagsq->execute($ho->{Name});
 +
 +    while (my ($flag) = $flagsq->fetchrow_array()) {
 +        $flags->{$flag}= 1;
 +    }
 +    $flagsq->finish();
 +    return $flags;
 +}
 +
 +sub default_methods ($$) {
 +    my ($hd, $ho) = @_;
 +
++    return if $ho->{Flags}{'no-reinstall'};
 +    return if $ho->{Ether} && $ho->{Power};
 +
 +    my $dbh_config= opendb('configdb');
 +    my $selname= $ho->{Fqdn};
 +    my $sth= $dbh_config->prepare(<<END);
 +            SELECT * FROM ips WHERE reverse_dns = ?
 +END
 +    $sth->execute($selname);
 +    my $row= $sth->fetchrow_hashref();
 +    die "$ident $name $selname ?" unless $row;
 +    die if $sth->fetchrow_hashref();
 +    $sth->finish();
 +    my $get= sub {
 +      my ($k,$nowarn) = @_;
 +      my $v= $row->{$k};
 +      defined $v or $nowarn or
 +          warn "host $name: undefined $k in configdb::ips\n";
 +      return $v;
 +    };
 +    $ho->{Asset}= $get->('asset',1);
 +    $ho->{Ether} ||= $get->('hardware');
 +    $ho->{Power} ||= "statedb $ho->{Asset}";
 +    push @{ $ho->{Info} }, "(asset=$ho->{Asset})" if defined $ho->{Asset};
 +    $dbh_config->disconnect();
 +}
 +
 +1;
index c1c4f25f16ee764244d79ed416b2781b20efcdb0,0000000000000000000000000000000000000000..141824a960eef0ac369d0a05bafa74244882f58e
mode 100644,000000..100644
--- /dev/null
@@@ -1,1721 -1,0 +1,1723 @@@
-     my $fqdn = $name;
-     $fqdn .= ".$c{TestHostDomain}" unless $fqdn =~ m/\./;
 +
 +package Osstest::TestSupport;
 +
 +use strict;
 +use warnings;
 +
 +use POSIX;
 +use DBI;
 +use IO::File;
 +use IO::Socket::INET;
 +
 +use Osstest;
 +use Osstest::Logtailer;
 +use File::Copy;
 +
 +BEGIN {
 +    use Exporter ();
 +    our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
 +    $VERSION     = 1.00;
 +    @ISA         = qw(Exporter);
 +    @EXPORT      = qw(
 +                      tsreadconfig %r $flight $job $stash
 +                      ts_get_host_guest
 +
 +                      fail broken logm $logm_handle get_filecontents
 +                      report_once
 +
 +                      store_runvar get_runvar get_runvar_maybe
 +                      get_runvar_default need_runvars flight_otherjob
 +                      unique_incrementing_runvar 
 +
 +                      target_cmd_root target_cmd target_cmd_build
 +                      target_cmd_output_root target_cmd_output
 +                      target_getfile target_getfile_root
 +                      target_putfile target_putfile_root
 +                      target_putfilecontents_stash
 +                    target_putfilecontents_root_stash
 +                      target_editfile_root target_file_exists
 +                      target_install_packages target_install_packages_norec
 +                      target_extract_jobdistpath
 +
 +                      poll_loop tcpconnect await_tcp
 +                      contents_make_cpio file_simple_write_contents
 +
 +                      selecthost get_hostflags get_host_property
 +                      power_state power_cycle power_cycle_time
 +                      propname_massage
 +         
 +                      get_stashed open_unique_stashfile
 +                      dir_identify_vcs build_clone built_stash 
 +                      hg_dir_revision git_dir_revision vcs_dir_revision
 +                      store_revision store_vcs_revision
 +
 +                      sshopts authorized_keys
 +                      remote_perl_script_open remote_perl_script_done
 +                      host_reboot target_reboot target_reboot_hard            
 +                      target_choose_vg target_umount_lv target_await_down
 +
 +                      target_ping_check_down target_ping_check_up
 +                      target_kernkind_check target_kernkind_console_inittab
 +                      target_var target_var_prefix
 +                      selectguest prepareguest more_prepareguest_hvm
 +                      guest_var guest_var_commalist
 +                      prepareguest_part_lvmdisk prepareguest_part_xencfg
 +                      guest_umount_lv guest_await guest_await_dhcp_tcp
 +                      guest_checkrunning guest_check_ip guest_find_ether
 +                      guest_find_domid guest_check_up guest_check_up_quick
 +                      guest_get_state guest_await_reboot guest_destroy
 +                      guest_vncsnapshot_begin guest_vncsnapshot_stash
 +                    guest_check_remus_ok guest_editconfig
 +                      host_involves_pcipassthrough host_get_pcipassthrough_devs
 +                      toolstack
 +
 +                      await_webspace_fetch_byleaf create_webfile
 +                      file_link_contents get_timeout
 +                      setup_pxeboot setup_pxeboot_local host_pxefile
 +                      );
 +    %EXPORT_TAGS = ( );
 +
 +    @EXPORT_OK   = qw();
 +}
 +
 +our (%r,$flight,$job,$stash);
 +
 +our %timeout= qw(RebootDown   100
 +                 RebootUp     400
 +                 HardRebootUp 600);
 +
 +our $logm_handle= new IO::File ">& STDERR" or die $!;
 +
 +#---------- test script startup ----------
 +
 +sub tsreadconfig () {
 +    # must be run outside transaction
 +    csreadconfig();
 +
 +    $flight= $mjobdb->current_flight();
 +    $job=    $ENV{'OSSTEST_JOB'};
 +    die "OSSTEST_FLIGHT and/or _JOB missing"
 +      unless defined $flight and defined $job;
 +
 +    my $now= time;  defined $now or die $!;
 +
 +    db_retry($flight,[qw(running constructing)],
 +             $dbh_tests,[qw(flights)], sub {
 +      $mjobdb->job_ensure_started();
 +
 +        undef %r;
 +
 +        logm("starting $flight.$job");
 +
 +        my $q= $dbh_tests->prepare(<<END);
 +            SELECT name, val FROM runvars WHERE flight=? AND job=?
 +END
 +        $q->execute($flight, $job);
 +        my $row;
 +        while ($row= $q->fetchrow_hashref()) {
 +            $r{ $row->{name} }= $row->{val};
 +            logm("setting $row->{name}=$row->{val}");
 +        }
 +        $q->finish();
 +    });
 +
 +    $stash= "$c{Stash}/$flight/$job";
 +    ensuredir("$c{Stash}/$flight");
 +    ensuredir($stash);
 +    ensuredir('tmp');
 +    eval {
 +        system_checked("find tmp -mtime +30 -name t.\\* -print0".
 +                       " | xargs -0r rm -rf --");
 +        1;
 +    } or warn $@;
 +}
 +
 +#---------- test script startup ----------
 +
 +sub ts_get_host_guest { # pass this @ARGV
 +    my ($gn,$whhost) = reverse @_;
 +    $whhost ||= 'host';
 +    $gn ||= 'guest';
 +
 +    my $ho= selecthost($whhost);
 +    my $gho= selectguest($gn,$ho);
 +    return ($ho,$gho);
 +}
 +
 +#---------- general ----------
 +
 +sub logm ($) {
 +    my ($m) = @_;
 +    my @t = gmtime;
 +    printf $logm_handle "%04d-%02d-%02d %02d:%02d:%02d Z %s\n",
 +        $t[5]+1900,$t[4]+1,$t[3], $t[2],$t[1],$t[0],
 +        $m
 +    or die $!;
 +    $logm_handle->flush or die $!;
 +}
 +
 +sub fail ($) {
 +    my ($m) = @_;
 +    logm("FAILURE: $m");
 +    die "failure: $m\n";
 +}
 +
 +sub broken ($;$) {
 +    my ($m, $newst) = @_;
 +    # must be run outside transaction
 +    my $affected;
 +    $newst= 'broken' unless defined $newst;
 +    db_retry($flight,'running', $dbh_tests,[qw(flights)], sub {
 +        $affected= $dbh_tests->do(<<END, {}, $newst, $flight, $job);
 +            UPDATE jobs SET status=?
 +             WHERE flight=? AND job=?
 +               AND (status='queued' OR status='running')
 +END
 +    });
 +    die "BROKEN: $m; ". ($affected>0 ? "marked $flight.$job $newst"
 +                         : "($flight.$job not marked $newst)");
 +}
 +
 +sub get_filecontents ($;$) {
 +    my ($path, $ifnoent) = @_;  # $ifnoent=undef => is error
 +    my $data= get_filecontents_core_quiet($path);
 +    if (!defined $data) {
 +        die "$path does not exist" unless defined $ifnoent;
 +        logm("read $path absent.");
 +        return $ifnoent;
 +    }
 +    logm("read $path ok.");
 +    return $data;
 +}
 +
 +#---------- runvars ----------
 +
 +sub store_runvar ($$) {
 +    my ($param,$value) = @_;
 +    # must be run outside transaction
 +    logm("runvar store: $param=$value");
 +    db_retry($flight,'running', $dbh_tests,[qw(flights)], sub {
 +        $dbh_tests->do(<<END, undef, $flight, $job, $param);
 +          DELETE FROM runvars
 +                WHERE flight=? AND job=? AND name=? AND synth='t'
 +END
 +        $dbh_tests->do(<<END,{}, $flight,$job, $param,$value);
 +            INSERT INTO runvars VALUES (?,?,?,?,'t')
 +END
 +    });
 +    $r{$param}= get_runvar($param, "$flight.$job");
 +}
 +
 +sub get_runvar ($$) {
 +    my ($param, $otherflightjob) = @_;
 +    # may be run outside transaction, or with flights locked
 +    my $r= get_runvar_maybe($param,$otherflightjob);
 +    die "need $param in $otherflightjob" unless defined $r;
 +    return $r;
 +}
 +
 +sub get_runvar_default ($$$) {
 +    my ($param, $otherflightjob, $default) = @_;
 +    # may be run outside transaction, or with flights locked
 +    my $r= get_runvar_maybe($param,$otherflightjob);
 +    return defined($r) ? $r : $default;
 +}
 +
 +sub get_runvar_maybe ($$) {
 +    my ($param, $otherflightjob) = @_;
 +    # may be run outside transaction, or with flights locked
 +    my ($oflight, $ojob) = otherflightjob($otherflightjob);
 +
 +    $mjobdb->jobdb_check_other_job($flight,$job, $oflight,$ojob);
 +
 +    my $row= $dbh_tests->selectrow_arrayref(<<END,{}, $oflight,$ojob,$param);
 +        SELECT val FROM runvars WHERE flight=? AND job=? AND name=?
 +END
 +    if (!$row) { return undef; }
 +    return $row->[0];
 +}
 +
 +sub need_runvars {
 +    my @missing= grep { !defined $r{$_} } @_;
 +    return unless @missing;
 +    die "missing runvars @missing ";
 +}
 +
 +sub flight_otherjob ($$) {
 +    my ($thisflight, $otherflightjob) = @_;    
 +    return $otherflightjob =~ m/^([^.]+)\.([^.]+)$/ ? ($1,$2) :
 +           $otherflightjob =~ m/^\.?([^.]+)$/ ? ($thisflight,$1) :
 +           die "$otherflightjob ?";
 +}
 +
 +sub otherflightjob ($) {
 +    return flight_otherjob($flight,$_[0]);
 +}
 +
 +sub unique_incrementing_runvar ($$) {
 +    my ($param,$start) = @_;
 +    # must be run outside transaction
 +    my $value;
 +    db_retry($flight,'running', $dbh_tests,[qw(flights)], sub {
 +      my $row= $dbh_tests->selectrow_arrayref(<<END,{}, $flight,$job,$param);
 +            SELECT val FROM runvars WHERE flight=? AND job=? AND name=?
 +END
 +      $value= $row ? $row->[0] : $start;
 +      $dbh_tests->do(<<END, undef, $flight, $job, $param);
 +            DELETE FROM runvars
 +                WHERE flight=? AND job=? AND name=? AND synth='t'
 +END
 +      $dbh_tests->do(<<END, undef, $flight, $job, $param, $value+1);
 +            INSERT INTO runvars VALUES (?,?,?,?,'t')
 +END
 +    });
 +    logm("runvar increment: $param=$value");
 +    return $value;
 +}
 +
 +#---------- running commands eg on targets ----------
 +
 +sub cmd {
 +    my ($timeout,$stdout,@cmd) = @_;
 +    my $child= fork;  die $! unless defined $child;
 +    if (!$child) {
 +        if (defined $stdout) {
 +            open STDOUT, '>&', $stdout
 +                or die "STDOUT $stdout $cmd[0] $!";
 +        }
 +        exec @cmd;
 +        die "$cmd[0]: $!";
 +    }
 +    my $start= time;
 +    my $r;
 +    eval {
 +        local $SIG{ALRM} = sub { die "alarm\n"; };
 +        alarm($timeout);
 +        $r= waitpid $child, 0;
 +        alarm(0);
 +    };
 +    if ($@) {
 +        die unless $@ eq "alarm\n";
 +        logm("command timed out [$timeout]: @cmd");
 +        return '(timed out)';
 +    } else {
 +      my $finish= time;
 +      my $took= $finish-$start;
 +      my $warn= $took > 0.5*$timeout;
 +      logm(sprintf "execution took %d seconds%s: %s",
 +           $took, ($warn ? " [**>$timeout/2**]" : "[<=2x$timeout]"), "@cmd")
 +          if $warn or $took > 60;
 +    }
 +    die "$r $child $!" unless $r == $child;
 +    logm("command nonzero waitstatus $?: @cmd") if $?;
 +    return $?;
 +}
 +
 +sub remote_perl_script_open ($$$) {
 +    my ($userhost, $what, $script) = @_;
 +    my ($readh,$writeh);
 +    my ($sshopts) = sshopts();
 +    my $pid= open2($readh,$writeh, "ssh @$sshopts $userhost perl");
 +    print $writeh $script."\n__DATA__\n" or die "$what $!";
 +    my $thing= {
 +        Read => $readh,
 +        Write => $writeh,
 +        Pid => $pid,
 +        Wait => $what,
 +        };
 +    return $thing;
 +}
 +sub remote_perl_script_done ($) {
 +    my ($thing) = @_;
 +    $thing->{Write}->close() or die "$thing->{What} $!";
 +    $thing->{Read}->close() or die "$thing->{What} $!";
 +    $!=0; my $got= waitpid $thing->{Pid}, 0;
 +    $got==$thing->{Pid} or die "$thing->{What} $!";
 +    !$? or die "$thing->{What} $?";
 +}
 +
 +sub sshuho ($$) { my ($user,$ho)= @_; return "$user\@$ho->{Ip}"; }
 +
 +sub sshopts () {
 +    return [ qw(-o StrictHostKeyChecking=no
 +                -o BatchMode=yes
 +                -o ConnectTimeout=100
 +                -o ServerAliveInterval=100
 +                -o PasswordAuthentication=no
 +                -o ChallengeResponseAuthentication=no),
 +             '-o', "UserKnownHostsFile=tmp/t.known_hosts_$flight.$job"
 +             ];
 +}
 +
 +sub tcmdex {
 +    my ($timeout,$stdout,$cmd,$optsref,@args) = @_;
 +    logm("executing $cmd ... @args");
 +    my $r= cmd($timeout,$stdout, $cmd,@$optsref,@args);
 +    $r and die "status $r";
 +}
 +
 +sub tgetfileex {
 +    my ($ruser, $ho,$timeout, $rsrc,$ldst) = @_;
 +    unlink $ldst or $!==&ENOENT or die "$ldst $!";
 +    tcmdex($timeout,undef,
 +           'scp', sshopts(),
 +           sshuho($ruser,$ho).":$rsrc", $ldst);
 +} 
 +sub target_getfile ($$$$) {
 +    my ($ho,$timeout, $rsrc,$ldst) = @_;
 +    tgetfileex('osstest', @_);
 +}
 +sub target_getfile_root ($$$$) {
 +    my ($ho,$timeout, $rsrc,$ldst) = @_;
 +    tgetfileex('root', @_);
 +}
 +
 +sub tputfileex {
 +    my ($ruser, $ho,$timeout, $lsrc,$rdst, $rsync) = @_;
 +    my @args= ($lsrc, sshuho($ruser,$ho).":$rdst");
 +    if (!defined $rsync) {
 +        tcmdex($timeout,undef,
 +               'scp', sshopts(),
 +               @args);
 +    } else {
 +        unshift @args, $rsync if length $rsync;
 +        tcmdex($timeout,undef,
 +               'rsync', [ '-e', 'ssh '.join(' ',@{ sshopts() }) ],
 +               @args);
 +    }
 +}
 +sub target_putfile ($$$$;$) {
 +    # $ho,$timeout,$lsrc,$rdst,[$rsync_opt]
 +    tputfileex('osstest', @_);
 +}
 +sub target_putfile_root ($$$$;$) {
 +    tputfileex('root', @_);
 +}
 +sub target_install_packages {
 +    my ($ho, @packages) = @_;
 +    target_cmd_root($ho, "apt-get -y install @packages",
 +                    300 + 100 * @packages);
 +}
 +sub target_install_packages_norec {
 +    my ($ho, @packages) = @_;
 +    target_cmd_root($ho,
 +                    "apt-get --no-install-recommends -y install @packages",
 +                    300 + 100 * @packages);
 +}
 +
 +sub target_somefile_getleaf ($$$) {
 +    my ($lleaf_ref, $rdest, $ho) = @_;
 +    if (!defined $$lleaf_ref) {
 +        $$lleaf_ref= $rdest;
 +        $$lleaf_ref =~ s,.*/,,;
 +    }
 +    $$lleaf_ref= "$ho->{Name}--$$lleaf_ref";
 +}
 +
 +sub tpfcs_core {
 +    my ($tputfilef,$ho,$timeout,$filedata, $rdest,$lleaf) = @_;
 +    target_somefile_getleaf(\$lleaf,$rdest,$ho);
 +
 +    my $h= new IO::File "$stash/$lleaf", 'w' or die "$lleaf $!";
 +    print $h $filedata or die $!;
 +    close $h or die $!;
 +    $tputfilef->($ho,$timeout, "$stash/$lleaf", $rdest);
 +}
 +sub target_putfilecontents_stash ($$$$;$) {
 +    my ($ho,$timeout,$filedata,$rdest, $lleaf) = @_;
 +    tpfcs_core(\&target_putfile, @_);
 +}
 +sub target_putfilecontents_root_stash ($$$$;$) {
 +    my ($ho,$timeout,$filedata,$rdest, $lleaf) = @_;
 +    tpfcs_core(\&target_putfile_root, @_);
 +}
 +
 +sub target_file_exists ($$) {
 +    my ($ho,$rfile) = @_;
 +    my $out= target_cmd_output_root($ho, "if test -e $rfile; then echo y; fi");
 +    return 1 if $out =~ m/^y$/;
 +    return 0 if $out !~ m/\S/;
 +    die "$rfile $out ?";
 +}
 +
 +sub target_editfile_root ($$$;$$) {
 +    my $code= pop @_;
 +    my ($ho,$rfile,$lleaf,$rdest) = @_;
 +
 +    if (!defined $rdest) {
 +        $rdest= $rfile;
 +    }
 +    target_somefile_getleaf(\$lleaf,$rdest,$ho);
 +    my $lfile;
 +    
 +    for (;;) {
 +        $lfile= "$stash/$lleaf";
 +        if (!lstat $lfile) {
 +            $! == &ENOENT or die "$lfile $!";
 +            last;
 +        }
 +        $lleaf .= '+';
 +    }
 +    if ($rdest eq $rfile) {
 +        logm("editing $rfile as $lfile".'{,.new}');
 +    } else {
 +        logm("editing $rfile to $rdest as $lfile".'{,.new}');
 +    }
 +
 +    target_getfile($ho, 60, $rfile, $lfile);
 +    open '::EI', "$lfile" or die "$lfile: $!";
 +    open '::EO', "> $lfile.new" or die "$lfile.new: $!";
 +
 +    &$code;
 +
 +    '::EI'->error and die $!;
 +    close '::EI' or die $!;
 +    close '::EO' or die $!;
 +    target_putfile_root($ho, 60, "$lfile.new", $rdest);
 +}
 +
 +sub target_cmd_build ($$$$) {
 +    my ($ho,$timeout,$builddir,$script) = @_;
 +    target_cmd($ho, <<END.$script, $timeout);
 +      set -xe
 +        LC_ALL=C; export LC_ALL
 +        PATH=/usr/lib/ccache:\$PATH:/usr/lib/git-core
 +        exec </dev/null
 +        cd $builddir
 +END
 +}
 +
 +sub target_ping_check_core {
 +    my ($ho, $exp) = @_;
 +    my $out= `ping -c 5 $ho->{Ip} 2>&1`;
 +    $out =~ s/\b(?:\d+(?:\.\d+)?\/)*\d+(?:\.\d+)? ?ms\b/XXXms/g;
 +    report_once($ho, 'ping_check',
 +              "ping $ho->{Ip} ".(!$? ? 'up' : $?==256 ? 'down' : "$? ?"));
 +    return undef if $?==$exp;
 +    $out =~ s/\n/ | /g;
 +    return "ping gave ($?): $out";
 +}
 +sub target_ping_check_down ($) { return target_ping_check_core(@_,256); }
 +sub target_ping_check_up ($) { return target_ping_check_core(@_,0); }
 +
 +sub target_await_down ($$) {
 +    my ($ho,$timeout) = @_;
 +    poll_loop($timeout,5,'reboot-down', sub {
 +        return target_ping_check_down($ho);
 +    });
 +}    
 +
 +sub tcmd { # $tcmd will be put between '' but not escaped
 +    my ($stdout,$user,$ho,$tcmd,$timeout) = @_;
 +    $timeout=30 if !defined $timeout;
 +    tcmdex($timeout,$stdout,
 +           'ssh', sshopts(),
 +           sshuho($user,$ho), $tcmd);
 +}
 +sub target_cmd ($$;$) { tcmd(undef,'osstest',@_); }
 +sub target_cmd_root ($$;$) { tcmd(undef,'root',@_); }
 +
 +sub tcmdout {
 +    my $stdout= IO::File::new_tmpfile();
 +    tcmd($stdout,@_);
 +    $stdout->seek(0,0) or die "$stdout $!";
 +    my $r;
 +    { local ($/) = undef;
 +      $r= <$stdout>; }
 +    die "$stdout $!" if !defined $r or $stdout->error or !close $stdout;
 +    chomp($r);
 +    return $r;
 +}
 +
 +sub target_cmd_output ($$;$) { tcmdout('osstest',@_); }
 +sub target_cmd_output_root ($$;$) { tcmdout('root',@_); }
 +
 +sub poll_loop ($$$&) {
 +    my ($maxwait, $interval, $what, $code) = @_;
 +    # $code should return undef when all is well
 +    
 +    logm("$what: waiting ${maxwait}s...");
 +    my $start= time;  die $! unless defined $start;
 +    my $wantwaited= 0;
 +    my $waited= 0;
 +    my $bad;
 +    my $reported= '';
 +    my $logmtmpfile;
 +
 +    my $org_logm_handle= $logm_handle;
 +    my $undivertlogm= sub {
 +        print $org_logm_handle "...\n";
 +        seek $logmtmpfile,0,0;
 +        File::Copy::copy($logmtmpfile, $org_logm_handle);
 +    };
 +
 +    for (;;) {
 +        $logmtmpfile= IO::File::new_tmpfile or die $!;
 +
 +        if (!eval {
 +            local ($Osstest::logm_handle) = ($logmtmpfile);
 +            $bad= $code->();
 +            1;
 +        }) {
 +            $undivertlogm->();
 +            die "$@";
 +        }
 +
 +        my $now= time;  die $! unless defined $now;
 +        $waited= $now - $start;
 +        last if !defined $bad;
 +
 +      if ($reported ne $bad) {
 +          logm("$what: $bad (waiting) ...");
 +          $reported= $bad;
 +      }
 +        last unless $waited <= $maxwait;
 +
 +        $wantwaited += $interval;
 +        my $needwait= $wantwaited - $waited;
 +        sleep($needwait) if $needwait > 0;
 +    }
 +    if (defined $bad) {
 +        $undivertlogm->();
 +        fail("$what: wait timed out: $bad.");
 +    }
 +    logm("$what: ok. (${waited}s)");
 +}
 +
 +#---------- dhcp watching ----------
 +
 +sub dhcp_watch_setup ($$) {
 +    my ($ho,$gho) = @_;
 +
 +    my $meth = get_host_property($ho,'dhcp-watch-method',undef);
 +    $gho->{DhcpWatch} = get_host_method_object($ho, 'DhcpWatch', $meth);
 +}
 +
 +sub guest_check_ip ($) {
 +    my ($gho) = @_;
 +    guest_find_ether($gho);
 +    $gho->{DhcpWatch}->check_ip($gho);
 +}
 +
 +#---------- power cycling ----------
 +
 +sub power_cycle_host_setup ($) {
 +    my ($ho) = @_;
 +    my $methobjs = [ ];
 +    foreach my $meth (split /\;\s*/, $ho->{Power}) {
 +      push @$methobjs, get_host_method_object($ho,'PDU',$meth);
 +    }
 +    $ho->{PowerMethobjs} = $methobjs;
 +}
 +
 +sub power_cycle_time ($) {
 +    my ($ho) = @_;
 +    return get_host_property($ho, 'power-cycle-time', 5);
 +}
 +
 +sub power_cycle ($) {
 +    my ($ho) = @_;
 +    power_state($ho, 0);
 +    sleep(power_cycle_time($ho));
 +    power_state($ho, 1);
 +}
 +
 +sub power_state ($$) {
 +    my ($ho, $on) = @_;
 +    logm("power: setting $on for $ho->{Name}");
 +    foreach my $mo (@{ $ho->{PowerMethobjs} }) {
 +      $mo->power_state($on);
 +    }
 +}
 +
 +#---------- host selection and properties ----------
 +
 +sub selecthost ($) {
 +    my ($ident) = @_;
 +    # must be run outside transaction
 +    my $name;
 +    if ($ident =~ m/=/) {
 +        $ident= $`;
 +        $name= $'; #'
 +        $r{$ident}= $name;
 +    } else {
 +        $name= $r{$ident};
 +        die "no specified $ident" unless defined $name;
 +    }
 +
-         Fqdn => $fqdn,
 +    my $ho= {
 +        Ident => $ident,
 +        Name => $name,
 +        TcpCheckPort => 22,
 +        Info => [],
 +        Suite => get_runvar_default("${ident}_suite",$job,$c{DebianSuite}),
 +    };
 +
 +    #----- calculation of the host's properties -----
 +
 +    $ho->{Properties} = { };
 +    my $setprop = sub {
 +      my ($pn,$val) = @_;
 +      $ho->{Properties}{$pn} = $val;
 +    };
 +
 +    # First, we use the config file's general properites as defaults
 +    foreach my $k (keys %c) {
 +      next unless $k =~ m/^HostProp_([A-Z].*)$/;
 +      $setprop->($1, $c{$k});
 +    }
 +
 +    # Then we read in the HostDB's properties
 +    $mhostdb->get_properties($name, $ho->{Properties});
 +
 +    # Finally, we override any host-specific properties from the config
 +    foreach my $k (keys %c) {
 +      next unless $k =~ m/^HostProp_([a-z0-9]+)_(.*)$/;
 +      next unless $1 eq $name;
 +      $setprop->($2, $c{$k});
 +    }
 +
 +    #----- calculation of the host's flags -----
 +
 +    $ho->{Flags} = $mhostdb->get_flags($ho);
 +
++    #----- fqdn -----
++
++    my $defaultfqdn = $name;
++    $defaultfqdn .= ".$c{TestHostDomain}" unless $defaultfqdn =~ m/\./;
++    $ho->{Fqdn} = get_host_property($ho,'fqdn',$defaultfqdn);
 +
 +
 +    $ho->{Ether}= get_host_property($ho,'ether');
 +    $ho->{DiskDevice}= get_host_property($ho,'disk-device');
 +    $ho->{Power}= get_host_property($ho,'power-method');
 +
 +    $mhostdb->default_methods($ho);
 +
 +    dhcp_watch_setup($ho,$ho);
 +    power_cycle_host_setup($ho);
 +
 +    my $serialmeth = get_host_property($ho,'serial','noop');
 +    $ho->{SerialMethobj} = get_host_method_object($ho,'Serial',$serialmeth);
 +
 +    $ho->{IpStatic} = get_host_property($ho,'ip-addr');
 +    if (!defined $ho->{IpStatic}) {
 +      my $ip_packed= gethostbyname($ho->{Fqdn});
 +      die "$ho->{Fqdn} ?" unless $ip_packed;
 +      $ho->{IpStatic}= inet_ntoa($ip_packed);
 +      die "$ho->{Fqdn} ?" unless defined $ho->{IpStatic};
 +    }
 +    $ho->{Ip}= $ho->{IpStatic};
 +
 +    $mjobdb->host_check_allocated($ho);
 +
 +    logm("host: selected $ho->{Name} ".
 +       (defined $ho->{Ether} ? $ho->{Ether} : '<unknown-ether>').
 +       " $ho->{Ip}".
 +         (!$ho->{Shared} ? '' :
 +          sprintf(" - shared %s %s %d", $ho->{Shared}{Type},
 +                  $ho->{Shared}{State}, $ho->{Shared}{Others}+1)));
 +
 +    return $ho;
 +}
 +
 +sub propname_massage ($) {
 +    # property names used to be in the form word-word-word
 +    # and are moving to WordWordWord
 +    my ($prop) = @_;
 +
 +    $prop = ucfirst $prop;
 +    while ($prop =~ m/-/) {
 +      $prop = $`.ucfirst $'; #';
 +    }
 +    return $prop;
 +}
 +
 +sub get_host_property ($$;$) {
 +    my ($ho, $prop, $defval) = @_;
 +    my $val = $ho->{Properties}{propname_massage($prop)};
 +    return defined($val) ? $val : $defval;
 +}
 +
 +sub get_host_method_object ($$$) {
 +    my ($ho, $kind, $meth) = @_;
 +    my (@meth) = split /\s+/, $meth;
 +    my $mo;
 +    eval ("use Osstest::${kind}::$meth[0];".
 +        "\$mo = Osstest::${kind}::$meth[0]->new(\$ho, \@meth);")
 +      or die "get_host_method_object $kind $meth $@";
 +    return $mo;
 +}
 +
 +#---------- stashed files ----------
 +
 +sub open_unique_stashfile ($) {
 +    my ($leafref) = @_;
 +    my $dh;
 +    for (;;) {
 +        my $df= $$leafref;
 +        $dh= new IO::File "$stash/$df", O_WRONLY|O_EXCL|O_CREAT;
 +        last if $dh;
 +        die "$df $!" unless $!==&EEXIST;
 +        $$leafref .= '+';
 +    }
 +    return $dh;
 +}
 +
 +sub get_stashed ($$) {
 +    my ($param, $otherflightjob) = @_; 
 +    # may be run outside transaction, or with flights locked
 +    my ($oflight, $ojob) = otherflightjob($otherflightjob);
 +    my $path= get_runvar($param, $otherflightjob);
 +    die "$path $& " if
 +        $path =~ m,[^-+._0-9a-zA-Z/], or
 +        $path =~ m/\.\./;
 +    return "$c{Stash}/$oflight/$ojob/$path";
 +}
 +
 +#---------- other stuff ----------
 +
 +sub host_reboot ($) {
 +    my ($ho) = @_;
 +    target_reboot($ho);
 +    poll_loop(40,2, 'reboot-confirm-booted', sub {
 +        my $output;
 +        if (!eval {
 +            $output= target_cmd_output($ho,
 +                "stat /dev/shm/osstest-confirm-booted 2>&1 >/dev/null ||:",
 +                                       40);
 +            1;
 +        }) {
 +            return $@;
 +        }
 +        return length($output) ? $output : undef;
 +    });
 +}
 +
 +sub target_reboot ($) {
 +    my ($ho) = @_;
 +    target_cmd_root($ho, "init 6");
 +    target_await_down($ho, $timeout{RebootDown});
 +    await_tcp(get_timeout($ho,'reboot',$timeout{RebootUp}), 5,$ho);
 +}
 +
 +sub target_reboot_hard ($) {
 +    my ($ho) = @_;
 +    power_cycle($ho);
 +    await_tcp(get_timeout($ho,'reboot',$timeout{HardRebootUp}), 5, $ho);
 +}
 +
 +sub tcpconnect ($$) {
 +    my ($host, $port) = @_;
 +    my $h= new IO::Handle;
 +    my $proto= getprotobyname('tcp');  die $! unless defined $proto;
 +    my $fixedaddr= inet_aton($host);
 +    my @addrs; my $atype;
 +    if (defined $fixedaddr) {
 +        @addrs= $fixedaddr;
 +        $atype= AF_INET;
 +    } else {
 +        $!=0; $?=0; my @hi= gethostbyname($host);
 +        @hi or die "host lookup failed for $host: $? $!";
 +        $atype= $hi[2];
 +        @addrs= @hi[4..$#hi];
 +        die "connect $host:$port: no addresses for $host" unless @addrs;
 +    }
 +    foreach my $addr (@addrs) {
 +        my $h= new IO::Handle;
 +        my $sin; my $pfam; my $str;
 +        if ($atype==AF_INET) {
 +            $sin= sockaddr_in $port, $addr;
 +            $pfam= PF_INET;
 +            $str= inet_ntoa($addr);
 +#        } elsif ($atype==AF_INET6) {
 +#            $sin= sockaddr_in6 $port, $addr;
 +#            $pfam= PF_INET6;
 +#            $str= inet_ntoa6($addr);
 +        } else {
 +            warn "connect $host:$port: unknown AF $atype";
 +            next;
 +        }
 +        if (!socket($h, $pfam, SOCK_STREAM, $proto)) {
 +            warn "connect $host:$port: unsupported PF $pfam";
 +            next;
 +        }
 +        if (!connect($h, $sin)) {
 +            warn "connect $host:$port: [$str]: $!";
 +            next;
 +        }
 +        return $h;
 +
 +    }
 +    die "$host:$port all failed";
 +}
 +
 +#---------- file handling ----------
 +
 +sub contents_make_cpio ($$$) {
 +    my ($fh, $format, $srcdir) = @_;
 +    my $child= fork;  defined $child or die $!;
 +    if (!$child) {
 +        postfork();
 +        chdir($srcdir) or die $!;
 +        open STDIN, 'find ! -name "*~" ! -name "#*" -type f -print0 |'
 +            or die $!;
 +        open STDOUT, '>&', $fh or die $!;
 +        system "cpio -H$format -o --quiet -0 -R 1000:1000";
 +        $? and die $?;
 +        $!=0; close STDIN; die "$! $?" if $! or $?;
 +        exit 0;
 +    }
 +    waitpid($child, 0) == $child or die $!;
 +    $? and die $?;
 +}
 +
 +sub file_simple_write_contents ($$) {
 +    my ($real, $contents) = @_;
 +    # $contents may be a coderef in which case we call it with the
 +    #  filehandle to allow caller to fill in the file
 +
 +    unlink $real or $!==&ENOENT or die "$real $!";
 +    my $flc= new IO::File "$real",'w' or die "$real $!";
 +    if (ref $contents eq 'CODE') {
 +        $contents->($flc);
 +    } else {
 +        print $flc $contents or die "$real $!";
 +    }
 +    close $flc or die "$real $!";
 +}
 +
 +#---------- building, vcs's, etc. ----------
 +
 +sub build_clone ($$$$) {
 +    my ($ho, $which, $builddir, $subdir) = @_;
 +
 +    need_runvars("tree_$which", "revision_$which");
 +
 +    my $tree= $r{"tree_$which"};
 +    my $timeout= 4000;
 +
 +    my $vcs = $r{"treevcs_$which"};
 +    if (defined $vcs) {
 +    } elsif ($tree =~ m/\.hg$/) {
 +        $vcs= 'hg';
 +    } elsif ($tree =~ m/\.git$/) {
 +        $vcs= 'git';
 +    } else {
 +        die "unknown vcs for $which $tree ";
 +    }
 +
 +    if ($vcs eq 'hg') {
 +        
 +        target_cmd_build($ho, $timeout, $builddir, <<END.
 +          hg clone '$tree' $subdir
 +          cd $subdir
 +END
 +                         (length($r{"revision_$which"}) ? <<END : ''));
 +          hg update '$r{"revision_$which"}'
 +END
 +    } elsif ($vcs eq 'git') {
 +
 +        target_cmd_build($ho, $timeout, $builddir, <<END.
 +            git clone '$tree' $subdir
 +            cd $subdir
 +END
 +                         (length($r{"revision_$which"}) ? <<END : ''));
 +          git checkout '$r{"revision_$which"}'
 +END
 +    } else {
 +        die "$vcs $which $tree ?";
 +    }
 +
 +    my $rev= vcs_dir_revision($ho, "$builddir/$subdir", $vcs);
 +    store_vcs_revision($which, $rev, $vcs);
 +}
 +
 +sub dir_identify_vcs ($$) {
 +    my ($ho,$dir) = @_;
 +    return target_cmd_output($ho, <<END);
 +        set -e
 +        if ! test -e $dir; then echo none; exit 0; fi
 +        cd $dir
 +        (test -d .git && echo git) ||
 +        (test -d .hg && echo hg) ||
 +        (echo >&2 'unable to determine vcs'; fail)
 +END
 +}
 +
 +sub store_revision ($$$;$) {
 +    my ($ho,$which,$dir,$optional) = @_;
 +    my $vcs= dir_identify_vcs($ho,$dir);
 +    return if $optional && $vcs eq 'none';
 +    my $rev= vcs_dir_revision($ho,$dir,$vcs);
 +    store_vcs_revision($which,$rev,$vcs);
 +}
 +
 +sub store_vcs_revision ($$$) {
 +    my ($which,$rev,$vcs) = @_;
 +    store_runvar("built_vcs_$which", $vcs);
 +    store_runvar("built_revision_$which", $rev);
 +}
 +
 +sub built_stash ($$$$) {
 +    my ($ho, $builddir, $distroot, $item) = @_;
 +    target_cmd($ho, <<END, 300);
 +      set -xe
 +      cd $builddir
 +        cd $distroot
 +        tar zcf $builddir/$item.tar.gz *
 +END
 +    my $build= "build";
 +    my $stashleaf= "$build/$item.tar.gz";
 +    ensuredir("$stash/$build");
 +    target_getfile($ho, 300,
 +                   "$builddir/$item.tar.gz",
 +                   "$stash/$stashleaf");
 +    store_runvar("path_$item", $stashleaf);
 +}
 +
 +sub vcs_dir_revision ($$$) {
 +    my ($ho,$builddir,$vcs) = @_;
 +    no strict qw(refs);
 +    return &{"${vcs}_dir_revision"}($ho,$builddir);
 +}
 +
 +sub hg_dir_revision ($$) {
 +    my ($ho,$builddir) = @_;
 +    my $rev= target_cmd_output($ho, "cd $builddir && hg identify -ni", 100);
 +    $rev =~ m/^([0-9a-f]{10,}\+?) (\d+\+?)$/ or die "$builddir $rev ?";
 +    return "$2:$1";
 +}
 +
 +sub git_dir_revision ($$) {
 +    my ($ho,$builddir) = @_;
 +    my $rev= target_cmd_output($ho, "cd $builddir && git rev-parse HEAD^0");
 +    $rev =~ m/^([0-9a-f]{10,})$/ or die "$builddir $rev ?";
 +    return "$1";
 +}
 +
 +#---------- hosts and guests ----------
 +
 +sub get_hostflags ($) {
 +    my ($ident) = @_;
 +    # may be run outside transaction, or with flights locked
 +    my $flags= get_runvar_default('all_hostflags',     $job, '').','.
 +               get_runvar_default("${ident}_hostflags", $job, '');
 +    return grep /./, split /\,/, $flags;
 +}
 +
 +sub host_involves_pcipassthrough ($) {
 +    my ($ho) = @_;
 +    return !!grep m/^pcipassthrough\-/, get_hostflags($ho->{Ident});
 +}
 +
 +sub host_get_pcipassthrough_devs ($) {
 +    my ($ho) = @_;
 +    my @devs;
 +    foreach my $name (keys %{ $ho->{Properties} }) {
 +        next unless $name =~ m/^pcipassthrough (\w+)$/;
 +        my $devtype= $1;
 +        next unless grep { m/^pcipassthrough-$devtype$/ } get_hostflags($ho);
 +      my $val = $ho->{Properties}{$name};
 +        $val =~ m,^([0-9a-f]+\:[0-9a-f]+\.\d+)/, or
 +            die "$ho->{Ident} $val ?";
 +        push @devs, {
 +            DevType => $devtype,
 +            Bdf => $1,
 +            Info => $' #'
 +            };
 +    }
 +    return @devs;
 +}
 +
 +sub get_timeout ($$$) {
 +    my ($ho,$which,$default) = @_;
 +    return $default + get_host_property($ho, "$which-time-extra", 0);
 +}
 +
 +sub guest_find_tcpcheckport ($) {
 +    my ($gho) = @_;
 +    $gho->{TcpCheckPort}= $r{"$gho->{Guest}_tcpcheckport"};
 +    $gho->{PingBroken}= $r{"$gho->{Guest}_pingbroken"};
 +}
 +
 +sub selectguest ($$) {
 +    my ($gn,$ho) = @_;
 +    my $gho= {
 +        Guest => $gn,
 +        Name => $r{"${gn}_hostname"},
 +        CfgPath => $r{"${gn}_cfgpath"},
 +      Host => $ho,
 +    };
 +    foreach my $opt (guest_var_commalist($gho,'options')) {
 +        $gho->{Options}{$opt}++;
 +    }
 +    guest_find_lv($gho);
 +    guest_find_ether($gho);
 +    guest_find_tcpcheckport($gho);
 +    dhcp_watch_setup($ho,$gho);
 +    return $gho;
 +}
 +
 +sub guest_find_lv ($) {
 +    my ($gho) = @_;
 +    my $gn= $gho->{Guest};
 +    $gho->{Vg}= $r{"${gn}_vg"};
 +    $gho->{Lv}= $r{"${gn}_disk_lv"};
 +    $gho->{Lvdev}= (defined $gho->{Vg} && defined $gho->{Lv})
 +        ? '/dev/'.$gho->{Vg}.'/'.$gho->{Lv} : undef;
 +}
 +
 +sub guest_find_ether ($) {
 +    my ($gho) = @_;
 +    $gho->{Ether}= $r{"$gho->{Guest}_ether"};
 +}
 +
 +sub report_once ($$$) {
 +    my ($ho, $what, $msg) = @_;
 +    my $k= "Lastmsg_$what";
 +    return if defined($ho->{$k}) and $ho->{$k} eq $msg;
 +    logm($msg);
 +    $ho->{$k}= $msg;
 +}
 +
 +sub guest_await_reboot ($$$) {
 +    my ($ho,$gho, $timeout) = @_;
 +    poll_loop($timeout, 30, "await reboot request from $gho->{Guest}", sub {
 +        my $st= guest_get_state($ho,$gho);
 +        return undef if $st eq 'sr';
 +        fail("guest unexpectedly shutdown; state is '$st'")
 +            if $st =~ m/^s/ || $st eq '';
 +        return "guest state is $st";
 +    });
 +}
 +
 +sub guest_destroy ($$) {
 +    my ($ho,$gho) = @_;
 +    target_cmd_root($ho, toolstack()->{Command}." destroy $gho->{Name}", 40);
 +}    
 +
 +sub target_choose_vg ($$) {
 +    my ($ho, $mbneeded) = @_;
 +    my $vgs= target_cmd_output_root($ho, 'vgdisplay --colon');
 +    my $bestkb= 1.0e90;
 +    my $bestvg;
 +    foreach my $l (split /\n/, $vgs) {
 +        $l =~ s/^\s+//; $l =~ s/\s+$//;
 +        my @l= split /\:/, $l;
 +        my $tvg= $l[0];
 +        my $pesize= $l[12];
 +        my $freepekb= $l[15];
 +        my $tkb= $l[12] * 1.0 * $l[15];
 +        if ($tkb < $mbneeded*1024.0) {
 +            logm("vg $tvg ${tkb}kb free - too small");
 +            next;
 +        }
 +        if ($tkb < $bestkb) {
 +            $bestvg= $tvg;
 +            $bestkb= $tkb;
 +        }
 +    }
 +    die "no vg of sufficient size"
 +        unless defined $bestvg;
 +    logm("vg $bestvg ${bestkb}kb free - will use");
 +    return $bestvg;
 +}
 +
 +sub select_ether ($$) {
 +    my ($ho,$vn) = @_;
 +    # must be run outside transaction
 +    my $ether= $r{$vn};
 +    return $ether if defined $ether;
 +
 +    db_retry($flight,'running', $dbh_tests,[qw(flights)], sub {
 +      my $prefix = get_host_property($ho, 'gen-ether-prefix-base');
 +      $prefix =~ m/^(\w+:\w+):(\w+):(\w+)$/ or die "$prefix ?";
 +      my $lhs = $1;
 +      my $pv = (hex($2)<<8) | (hex($3));
 +      $pv ^= $mjobdb->gen_ether_offset($ho,$flight);
 +      $prefix = sprintf "%s:%02x:%02x", $lhs, ($pv>>8)&0xff, $pv&0xff;
 +
 +      my $glob_ether = $mjobdb->jobdb_db_glob('*_ether');
 +
 +        my $previous= $dbh_tests->selectrow_array(<<END, {}, $flight);
 +            SELECT max(val) FROM runvars WHERE flight=?
 +                AND name $glob_ether
 +                AND val LIKE '$prefix:%'
 +END
 +        if (defined $previous) {
 +            $previous =~ m/^\w+:\w+:\w+:\w+:([0-9a-f]+):([0-9a-f]+)$/i
 +                or die "$previous ?";
 +            my $val= (hex($1)<<8) | hex($2);
 +            $val++;  $val &= 0xffff;
 +            $ether= sprintf "%s:%02x:%02x", $prefix, $val >> 8, $val & 0xff;
 +            logm("select_ether $prefix:... $ether (previous $previous)");
 +        } else {
 +            $ether= "$prefix:00:01";
 +            logm("select_ether $prefix:... $ether (first in flight)");
 +        }
 +        $dbh_tests->do(<<END, {}, $flight,$job,$vn,$ether);
 +            INSERT INTO runvars VALUES (?,?,?,?,'t')
 +END
 +        my $chkrow= $dbh_tests->selectrow_hashref(<<END,{}, $flight);
 +          SELECT val, count(*) FROM runvars WHERE flight=?
 +                AND name $glob_ether
 +                AND val LIKE '$prefix:%'
 +              GROUP BY val
 +              HAVING count(*) <> 1
 +              LIMIT 1
 +END
 +      die "$chkrow->{val} $chkrow->{count}" if $chkrow;
 +    });
 +    $r{$vn}= $ether;
 +    return $ether;
 +}
 +
 +sub guest_var ($$$) {
 +    my ($gho, $runvartail, $default) = @_;
 +    my $val= $r{ $gho->{Guest}."_".$runvartail };  return $val if defined $val;
 +    $val= $r{ "guests_$runvartail" };              return $val if defined $val;
 +    return $default;
 +}
 +
 +sub guest_var_commalist ($$) {
 +    my ($gho,$runvartail) = @_;
 +    return split /\,/, guest_var($gho,$runvartail,'');
 +}
 +
 +sub prepareguest ($$$$$$) {
 +    my ($ho, $gn, $hostname, $tcpcheckport, $mb,
 +        $boot_timeout) = @_;
 +    # must be run outside transaction
 +
 +    # If we are passing through a nic, use its mac address not a generated one
 +    my $ptnichostident= $r{"${gn}_pcipassthrough_nic"};
 +    if (!$ptnichostident) {
 +        select_ether($ho,"${gn}_ether");
 +    } else {
 +        my $ptnicho= selecthost($ptnichostident);
 +        my $ptnicinfo= get_host_property($ptnicho,'pcipassthrough nic');
 +        $ptnicinfo =~ m,/, or die "$ptnichostident $ptnicinfo ?";
 +        my $ptether= $'; #'
 +        $r{"${gn}_ether"}= $ptether;
 +        logm("passthrough nic from $ptnichostident ether $ptether");
 +    }
 +
 +    store_runvar("${gn}_hostname", $hostname);
 +    store_runvar("${gn}_disk_lv", $r{"${gn}_hostname"}.'-disk');
 +    store_runvar("${gn}_tcpcheckport", $tcpcheckport);
 +    store_runvar("${gn}_boot_timeout", $boot_timeout);
 +
 +    my $gho= selectguest($gn, $ho);
 +    store_runvar("${gn}_domname", $gho->{Name});
 +
 +    store_runvar("${gn}_vg", '');
 +    if (!length $r{"${gn}_vg"}) {
 +        store_runvar("${gn}_vg", target_choose_vg($ho, $mb));
 +    }
 +
 +    guest_find_lv($gho);
 +    guest_find_ether($gho);
 +    guest_find_tcpcheckport($gho);
 +    return $gho;
 +}
 +
 +sub prepareguest_part_lvmdisk ($$$) {
 +    my ($ho, $gho, $disk_mb) = @_;
 +    target_cmd_root($ho, "lvremove -f $gho->{Lvdev} ||:");
 +    target_cmd_root($ho, "lvcreate -L ${disk_mb}M -n $gho->{Lv} $gho->{Vg}");
 +    target_cmd_root($ho, "dd if=/dev/zero of=$gho->{Lvdev} count=10");
 +}    
 +
 +sub prepareguest_part_xencfg ($$$$$) {
 +    my ($ho, $gho, $ram_mb, $xopts, $cfgrest) = @_;
 +    my $onreboot= $xopts->{OnReboot} || 'restart';
 +    my $vcpus= guest_var($gho, 'vcpus', $xopts->{DefVcpus} || 2);
 +    my $xoptcfg= $xopts->{ExtraConfig};
 +    $xoptcfg='' unless defined $xoptcfg;
 +    my $xencfg= <<END;
 +name        = '$gho->{Name}'
 +memory = ${ram_mb}
 +vif         = [ 'type=ioemu,mac=$gho->{Ether}' ]
 +#
 +on_poweroff = 'destroy'
 +on_reboot   = '$onreboot'
 +on_crash    = 'preserve'
 +#
 +vcpus = $vcpus
 +#
 +$cfgrest
 +#
 +$xoptcfg
 +END
 +
 +    my $cfgpath= "/etc/xen/$gho->{Name}.cfg";
 +    store_runvar("$gho->{Guest}_cfgpath", "$cfgpath");
 +    $gho->{CfgPath}= $cfgpath;
 +
 +    target_putfilecontents_root_stash($ho,30,$xencfg, $cfgpath);
 +
 +    return $cfgpath;
 +}
 +
 +sub more_prepareguest_hvm ($$$$;@) {
 +    my ($ho, $gho, $ram_mb, $disk_mb, %xopts) = @_;
 +    
 +    my $passwd= 'xenvnc';
 +
 +    prepareguest_part_lvmdisk($ho, $gho, $disk_mb);
 +    
 +    my $specimage= $r{"$gho->{Guest}_image"};
 +    die "$gho->{Guest} ?" unless $specimage;
 +    my $limage= $specimage =~ m,^/, ? $specimage : "$c{Images}/$specimage";
 +    $gho->{Rimage}= "/root/$flight.$job.".basename($specimage);
 +    target_putfile_root($ho, 1000, $limage,$gho->{Rimage}, '-p');
 +
 +    my $postimage_hook= $xopts{PostImageHook};
 +    $postimage_hook->() if $postimage_hook;
 +
 +    my $cfg = <<END;
 +kernel      = 'hvmloader'
 +builder     = 'hvm'
 +#
 +disk        = [
 +            'phy:$gho->{Lvdev},hda,w',
 +            'file:$gho->{Rimage},hdc:cdrom,r'
 +            ]
 +#
 +usb=1
 +usbdevice='tablet'
 +#
 +#stdvga=1
 +keymap='en-gb';
 +#
 +sdl=0
 +opengl=0
 +vnc=1
 +vncunused=1
 +vncdisplay=0
 +vnclisten='$ho->{Ip}'
 +vncpasswd='$passwd'
 +
 +serial='file:/dev/stderr'
 +#
 +boot = 'dc'
 +END
 +
 +    my $devmodel = $r{'device_model_version'};
 +    if (defined $devmodel) {
 +        $cfg .= "device_model_version='$devmodel'\n";
 +    }
 +
 +    my $cfgpath= prepareguest_part_xencfg($ho, $gho, $ram_mb, \%xopts, $cfg);
 +    target_cmd_root($ho, <<END);
 +        (echo $passwd; echo $passwd) | vncpasswd $gho->{Guest}.vncpw
 +END
 +
 +    return $cfgpath;
 +}
 +
 +sub guest_editconfig ($$$) {
 +    my ($ho, $gho, $code) = @_;
 +    target_editfile_root($ho, "$gho->{CfgPath}", sub {
 +        while (<::EI>) {
 +            $code->();
 +            print ::EO or die $!;
 +        }
 +        die $! if ::EI->error;
 +    });
 +}
 +
 +sub guest_check_via_ssh ($) {
 +    my ($gho) = @_;
 +    return $r{"$gho->{Guest}_tcpcheckport"} == 22;
 +}
 +
 +sub guest_check_up_quick ($) {
 +    my ($gho) = @_;
 +    if (guest_check_via_ssh($gho)) {
 +      target_cmd_root($gho, "date");
 +    } else {
 +      target_ping_check_up($gho);
 +    }
 +}
 +
 +sub guest_check_up ($) {
 +    my ($gho) = @_;
 +    guest_await_dhcp_tcp($gho,20);
 +    target_ping_check_up($gho);
 +    target_cmd_root($gho, "echo guest $gho->{Name}: ok")
 +        if guest_check_via_ssh($gho);
 +}
 +
 +sub guest_get_state ($$) {
 +    my ($ho,$gho) = @_;
 +    my $domains= target_cmd_output_root($ho, toolstack()->{Command}." list");
 +    $domains =~ s/^Name.*\n//;
 +    foreach my $l (split /\n/, $domains) {
 +        $l =~ m/^(\S+) (?: \s+ \d+ ){3} \s+ ([-a-z]+) \s/x or die "$l ?";
 +        next unless $1 eq $gho->{Name};
 +        my $st= $2;
 +        $st =~ s/\-//g;
 +        $st='-' if !length $st;
 +        logm("guest $gho->{Name} state is $st");
 +        return $st;
 +    }
 +    logm("guest $gho->{Name} not present on this host");
 +    return '';
 +}
 +
 +our $guest_state_running_re= '[-rb]+';
 +
 +sub guest_checkrunning ($$) {
 +    my ($ho,$gho) = @_;
 +    my $s= guest_get_state($ho,$gho);
 +    return $s =~ m/^$guest_state_running_re$/o;
 +}
 +
 +sub guest_await_dhcp_tcp ($$) {
 +    my ($gho,$timeout) = @_;
 +    guest_find_tcpcheckport($gho);
 +    poll_loop($timeout,1,
 +              "guest $gho->{Name} $gho->{Ether} $gho->{TcpCheckPort}".
 +              " link/ip/tcp",
 +              sub {
 +        my $err= guest_check_ip($gho);
 +        return $err if defined $err;
 +
 +        return
 +            ($gho->{PingBroken} ? undef : target_ping_check_up($gho))
 +            ||
 +            target_tcp_check($gho,5)
 +            ||
 +            undef;
 +    });
 +}
 +
 +sub guest_check_remus_ok {
 +    my ($gho, @hos) = @_;
 +    my @sts;
 +    logm("remus check $gho->{Name}...");
 +    foreach my $ho (@hos) {
 +      my $st;
 +      if (!eval {
 +          $st= guest_get_state($ho, $gho)
 +        }) {
 +          $st= '_';
 +          logm("could not get guest $gho->{Name} state on $ho->{Name}: $@");
 +      }
 +      push @sts, [ $ho, $st ];
 +    }
 +    my @ststrings= map { $_->[1] } @sts;
 +    my $compound= join ',', @ststrings;
 +    my $msg= "remus check $gho->{Name}: result \"$compound\":";
 +    $msg .= " $_->[0]{Name}=$_->[1]" foreach @sts;
 +    logm($msg);
 +    my $runnings= scalar grep { m/$guest_state_running_re/o } @ststrings;
 +    die "running on multiple hosts $compound" if $runnings > 1;
 +    die "not running anywhere $compound" unless $runnings;
 +    die "crashed somewhere $compound" if grep { m/c/ } @ststrings;
 +}
 +
 +sub target_umount_lv ($$$) {
 +    my ($ho,$vg,$lv) = @_;
 +    my $dev= "/dev/$vg/$lv";
 +    for (;;) {
 +      my $link= target_cmd_output_root($ho, "readlink $dev");
 +      return if $link =~ m,^/dev/nbd,; # can't tell if it's open
 +        $lv= target_cmd_output_root($ho, "lvdisplay --colon $dev");
 +        $lv =~ s/^\s+//;  $lv =~ s/\s+$//;
 +        my @lv = split /:/, $lv;
 +        die "@lv ?" unless $lv[0] eq $dev;
 +        return unless $lv[5]; # "open"
 +        logm("lvdisplay output says device is still open: $lv");
 +        target_cmd_root($ho, "umount $dev");
 +    }
 +}
 +
 +sub guest_umount_lv ($$) {
 +    my ($ho,$gho) = @_;
 +    target_umount_lv($ho, $gho->{Vg}, $gho->{Lv});
 +}
 +
 +sub target_tcp_check ($$) {
 +    my ($ho,$interval) = @_;
 +    my $ncout= `nc -n -v -z -w $interval $ho->{Ip} $ho->{TcpCheckPort} 2>&1`;
 +    return undef if !$?;
 +    $ncout =~ s/\n/ | /g;
 +    return "nc: $? $ncout";
 +}
 +
 +sub await_tcp ($$$) {
 +    my ($maxwait,$interval,$ho) = @_;
 +    poll_loop($maxwait,$interval,
 +              "await tcp $ho->{Name} $ho->{TcpCheckPort}",
 +              sub {
 +        return target_tcp_check($ho,$interval);
 +    });
 +}
 +
 +sub guest_await ($$) {
 +    my ($gho,$dhcpwait) = @_;
 +    guest_await_dhcp_tcp($gho,$dhcpwait);
 +    target_cmd_root($gho, "echo guest $gho->{Name}: ok")
 +        if guest_check_via_ssh($gho);
 +    return $gho;
 +}
 +
 +sub target_var_prefix ($) {
 +    my ($ho) = @_;
 +    if (exists $ho->{Guest}) { return $ho->{Guest}.'_'; }
 +    return '';
 +}
 +
 +sub target_var ($$) {
 +    my ($ho,$vn) = @_;
 +    return $r{ target_var_prefix($ho). $vn };
 +}
 +
 +sub target_kernkind_check ($) {
 +    my ($gho) = @_;
 +    my $pfx= target_var_prefix($gho);
 +    my $kernkind= $r{$pfx."kernkind"};
 +    my $isguest= exists $gho->{Guest};
 +    if ($kernkind eq 'pvops') {
 +        store_runvar($pfx."rootdev", 'xvda') if $isguest;
 +        store_runvar($pfx."console", 'hvc0');
 +    } elsif ($kernkind !~ m/2618/) {
 +        store_runvar($pfx."console", 'xvc0') if $isguest;
 +    }
 +}
 +
 +sub target_kernkind_console_inittab ($$$) {
 +    my ($ho, $gho, $root) = @_;
 +
 +    my $inittabpath= "$root/etc/inittab";
 +    my $console= target_var($gho,'console');
 +
 +    if (defined $console && length $console) {
 +        target_cmd_root($ho, <<END);
 +            set -ex
 +            perl -i~ -ne "
 +                next if m/^xc:/;
 +                print \\\$_ or die \\\$!;
 +                next unless s/^1:/xc:/;
 +                s/tty1/$console/;
 +                print \\\$_ or die \\\$!;
 +            " $inittabpath
 +END
 +    }
 +    return $console;
 +}
 +
 +sub target_extract_jobdistpath ($$$$$) {
 +    my ($ho, $part, $path, $job, $distpath) = @_;
 +    $distpath->{$part}= get_stashed($path, $job);
 +    my $local= $path;  $local =~ s/path_//;
 +    my $distcopy= "/root/extract_$local.tar.gz";
 +    target_putfile_root($ho, 300, $distpath->{$part}, $distcopy);
 +    target_cmd_root($ho, "cd / && tar zxf $distcopy", 300);
 +}
 +
 +sub guest_find_domid ($$) {
 +    my ($ho,$gho) = @_;
 +    return if defined $gho->{Domid};
 +    my $list= target_cmd_output_root($ho,
 +                toolstack()->{Command}." list $gho->{Name}");
 +    $list =~ m/^(?!Name\s)(\S+)\s+(\d+)\s+(\d+)+(\d+)\s.*$/m
 +        or die "domain list: $list";
 +    $1 eq $gho->{Name} or die "domain list name $1 expected $gho->{Name}";
 +    $gho->{MemUsed}= $3;
 +    $gho->{Vcpus}= $4;
 +    return $gho->{Domid}= $2;
 +}
 +
 +sub guest_vncsnapshot_begin ($$) {
 +    my ($ho,$gho) = @_;
 +    my $domid= $gho->{Domid};
 +
 +    my $backend= target_cmd_output_root($ho,
 +        "xenstore-read /local/domain/$domid/device/vfb/0/backend ||:");
 +
 +    if ($backend eq '') {
 +        my $port= target_cmd_output_root($ho,
 +            "xenstore-read /local/domain/$domid/console/vnc-port");
 +        $port =~ m/^\d+/ && $port >= 5900 or die "$port ?";
 +        return {
 +            vnclisten => $ho->{Ip},
 +            vncdisplay => $port-5900,
 +        };
 +    }
 +
 +    $backend =~ m,^/local/domain/\d+/backend/vfb/\d+/\d+$,
 +        or die "$backend ?";
 +
 +    my $v = {};
 +    foreach my $k (qw(vnclisten vncdisplay)) {
 +        $v->{$k}= target_cmd_output_root($ho,
 +                "xenstore-read $backend/$k");
 +    }
 +    return $v;
 +}
 +sub guest_vncsnapshot_stash ($$$$) {
 +    my ($ho,$gho,$v,$leaf) = @_;
 +    my $rfile= "/root/$leaf";
 +    target_cmd_root($ho,
 +        "vncsnapshot -passwd $gho->{Guest}.vncpw".
 +                   " -nojpeg -allowblank".
 +                   " $v->{vnclisten}:$v->{vncdisplay}".
 +                   " $rfile", 100);
 +    target_getfile_root($ho,100, "$rfile", "$stash/$leaf");
 +}
 +
 +our %toolstacks=
 +    ('xend' => {
 +        NewDaemons => [qw(xend)],
 +        OldDaemonInitd => 'xend',
 +        Command => 'xm',
 +        CfgPathVar => 'cfgpath',
 +        Dom0MemFixed => 1,
 +        },
 +     'xl' => {
 +        NewDaemons => [],
 +        Dom0MemFixed => 1,
 +        Command => 'xl',
 +        CfgPathVar => 'cfgpath',
 +      RestoreNeedsConfig => 1,
 +        }
 +     );
 +
 +sub toolstack () {
 +    my $tsname= $r{toolstack};
 +    $tsname= 'xend' if !defined $tsname;
 +    my $ts= $toolstacks{$tsname};
 +    die "$tsname ?" unless defined $ts;
 +    if (!exists $ts->{Name}) {
 +        logm("toolstack $tsname");
 +        $ts->{Name}= $tsname;
 +    }
 +    return $ts;
 +}
 +
 +sub authorized_keys () {
 +    my $authkeys= '';
 +    my @akf= map {
 +        "$ENV{'HOME'}/.ssh/$_"
 +        } qw(authorized_keys id_dsa.pub id_rsa.pub);
 +    push @akf, split ':', $c{AuthorizedKeysFiles};
 +    push @akf, $c{TestHostKeypairPath}.'.pub';
 +    foreach my $akf (@akf) {
 +        next unless $akf =~ m/\S/;
 +        $authkeys .= get_filecontents($akf, "# $akf ENOENT\n"). "\n";
 +    }
 +    $authkeys .= $c{AuthorizedKeysAppend};
 +    return $authkeys;
 +}
 +
 +#---------- webspace for installer ----------
 +
 +sub await_webspace_fetch_byleaf ($$$$$) {
 +    my ($maxwait,$interval,$logtailer, $ho, $url) = @_;
 +    my $leaf= $url;
 +    $leaf =~ s,.*/,,;
 +    poll_loop($maxwait,$interval, "fetch $leaf", sub {
 +        my ($line, $last);
 +        $last= '(none)';
 +        while (defined($line= $logtailer->getline())) {
 +            my ($ip, $got) = $line =~
 +                m,^([0-9.]+) \S+ \S+ \[[^][]+\] \"GET \S*/(\S+) ,
 +                or next;
 +            next unless $ip eq $ho->{Ip};
 +            $last= $got;
 +            next unless $got eq $leaf;
 +            return undef;
 +        }
 +        return $last;
 +    });
 +}
 +
 +sub create_webfile ($$$) {
 +    my ($ho, $tail, $contents) = @_; # $contents as for file_link_contents
 +    my $wf_common= $c{WebspaceCommon}.$ho->{Name}."_".$tail;
 +    my $wf_url= $c{WebspaceUrl}.$wf_common;
 +    my $wf_file= $c{WebspaceFile}.$wf_common;
 +    file_link_contents($wf_file, $contents);
 +    return $wf_url;
 +}
 +
 +#---------- pxe handling ----------
 +
 +sub file_link_contents ($$) {
 +    my ($fn, $contents) = @_;
 +    # $contents as for file_write_contents
 +    my ($dir, $base, $ext) =
 +        $fn =~ m,^( (?: .*/ )? )( [^/]+? )( (?: \.[^./]+ )? )$,x
 +        or die "$fn ?";
 +    my $real= "$dir$base--osstest$ext";
 +    my $linktarg= "$base--osstest$ext";
 +
 +    file_simple_write_contents($real, $contents);
 +
 +    my $newlink= "$dir$base--newlink$ext";
 +
 +    if (!lstat "$fn") {
 +        $!==&ENOENT or die "$fn $!";
 +    } elsif (!-l _) {
 +        die "$fn not a symlink";
 +        unlink $fn or die "$fn $!";
 +    }
 +    symlink $linktarg, $newlink or die "$newlink $!";
 +    rename $newlink, $fn or die "$newlink $fn $!";
 +    logm("wrote $fn");
 +}
 +
 +sub host_pxefile ($) {
 +    my ($ho) = @_;
 +    my %v = %r;
 +    if (defined $ho->{Ether}) {
 +      my $eth = $v{'ether'} = $ho->{Ether};
 +      $eth =~ y/A-Z/a-z/;
 +      $eth =~ y/0-9a-f//cd;
 +      length($eth)==12 or die "$eth ?";
 +      $eth =~ s/../$&-/g;
 +      $eth =~ s/\-$//;
 +      $v{'etherhyph'} = $eth;
 +    }
 +    if (defined $ho->{IpStatic}) {
 +      my $ip = $ho->{IpStatic};
 +      $ip =~ s/\b0+//g;
 +      $v{'ipaddr'} = $ip;
 +      $v{'ipaddrhex'} = sprintf "%02X%02X%02X%02X", split /\./, $ip;
 +    }
 +    foreach my $pat (split /\s+/, $c{TftpPxeTemplates}) {
 +      # we skip patterns that contain any references to undefined %var%s
 +      $pat =~ s{\%(\w*)\%}{
 +                  $1 eq '' ? '%' :
 +                  defined($v{$1}) ? $v{$1} :
 +                  next;
 +               }ge;
 +      # and return the first pattern we managed to completely substitute
 +        return $pat;
 +    }
 +    die "no pxe template matched $c{TftpPxeTemplates} ".
 +        (join ",", sort keys %v)." ?";
 +}
 +
 +sub setup_pxeboot ($$) {
 +    my ($ho, $bootfile) = @_;
 +    my $f= host_pxefile($ho);
 +    file_link_contents("$c{TftpPath}$c{TftpPxeDir}$f", $bootfile);
 +}
 +
 +sub setup_pxeboot_local ($) {
 +    my ($ho) = @_;
 +    setup_pxeboot($ho, <<END);
 +serial 0 $c{Baud}
 +timeout 5
 +label local
 +      LOCALBOOT 0
 +default local
 +END
 +}
 +
 +1;
diff --cc make-flight
Simple merge
diff --cc mg-hosts
Simple merge
diff --cc ts-host-install
index 60285265951528a571269341f3c084ab9daa11b5,7931d8f6b3b38c4f873a20d5711bd697928140f3..95cefa1c5cbe14d8127f3a79f9fdd4f62e47a226
@@@ -23,7 -22,9 +23,8 @@@ if (@ARGV && $ARGV[0] =~ m/^--priority(
  our ($whhost) = @ARGV;
  $whhost ||= 'host';
  our $ho= selecthost($whhost);
+ exit 0 if $ho->{Flags}{'no-reinstall'};
  exit 0 if $ho->{SharedReady};
 -die if $ho->{SharedOthers};
  
  our %timeout= qw(ReadPreseed  350
                   Sshd        2400);
index 8d0bebf851a8ca399bd16cd3fcc475d3119478fa,0000000000000000000000000000000000000000..34c53764f69be8269250ba336235c069115871ba
mode 100755,000000..100755
--- /dev/null
@@@ -1,776 -1,0 +1,776 @@@
-                 $hid->{Shared}= $shr;
 +#!/usr/bin/perl -w
 +
 +use strict;
 +use DBI;
 +use Osstest;
 +use Data::Dumper;
 +use POSIX;
 +use JSON;
 +use IO::Handle;
 +use Osstest::TestSupport;
 +
 +tsreadconfig();
 +opendb_state();
 +
 +open DEBUG, ">/dev/null" or die $!;
 +
 +unshift @ARGV, '-D';
 +
 +while (@ARGV and $ARGV[0] =~ m/^-/) {
 +    $_= shift @ARGV;
 +    last if m/^--$/;
 +    while (m/^-./) {
 +        if (s/^-D/-/) {
 +            open DEBUG, ">&STDERR" or die $!;
 +            DEBUG->autoflush(1);
 +        } else {
 +            die "$_ ?";
 +        }
 +    }
 +}
 +
 +# initialised by setup:
 +our $taskid;
 +our %magictaskid;
 +our $fi;
 +our $jobinfo;
 +our $harness_rev;
 +
 +#---------- general utilities, setup, etc. ----------
 +
 +sub show_reskey {
 +    my (@reskey) = @_;
 +    return $reskey[2] eq '0' ? "@reskey[0..1]" : "@reskey";
 +}
 +
 +sub setup () {
 +    $harness_rev = get_harness_rev();
 +
 +    $taskid= findtask();
 +
 +    $fi= $dbh_tests->selectrow_hashref(<<END, {}, $flight);
 +        SELECT * FROM flights
 +         WHERE flight = ?
 +END
 +    logm("flight $flight intended $fi->{intended} branch $fi->{branch}");
 +
 +    $jobinfo= $dbh_tests->selectrow_hashref(<<END, {}, $flight, $job);
 +        SELECT * FROM jobs
 +         WHERE flight = ? AND job = ?
 +END
 +    logm("job $flight.$job recipe $jobinfo->{recipe}");
 +
 +    foreach my $rk (qw(allocatable shared preparing idle)) {
 +        $magictaskid{$rk}= $dbh_tests->selectrow_array(<<END, {}, $rk);
 +            SELECT taskid FROM tasks
 +             WHERE type='magic' AND refkey=?
 +END
 +    }
 +}
 +
 +#---------- prepared sql statements ----------
 +# all users of these must ->finish them afterwards, to avoid db deadlock
 +
 +our ($flagscheckq, $equivflagscheckq, $duration_estimator, $resprop_q,
 +     $alloc_findres_q, $alloc_shared_q, $alloc_sharing_slot_q,
 +     $claim_share_reuse_q, $claim_maxshare_q, $claim_rmshares_q,
 +     $claim_noshares_q, $claim_rmshare_q, $claim_setres_q,
 +     $claim_share_new_q, $claim_share_newresource_q);
 +    
 +sub prepare_statements () {
 +    $flagscheckq= $dbh_tests->prepare(<<END);
 +          SELECT * FROM hostflags
 +           WHERE hostname = ? AND hostflag = ?
 +END
 +
 +    $equivflagscheckq= $dbh_tests->prepare(<<END);
 +          SELECT * FROM hostflags
 +                  WHERE hostname = ?
 +                    AND hostflag LIKE 'equiv-%'
 +END
 +
 +    $duration_estimator= duration_estimator($fi->{branch}, $fi->{intended},
 +                                            sub { print DEBUG "@_\n"; });
 +
 +    $resprop_q= $dbh_tests->prepare(<<END);
 +            SELECT * FROM resource_properties
 +                    WHERE restype = ? AND resname = ?
 +                      AND name = ?
 +END
 +
 +    # for allocation
 +
 +    $alloc_findres_q= $dbh_tests->prepare(<<END);
 +          SELECT *
 +            FROM resources
 +           WHERE restype=? AND resname=? AND shareix=0
 +END
 +
 +    $alloc_shared_q= $dbh_tests->prepare(<<END);
 +            SELECT s.restype, s.resname, s.sharetype, s.state,
 +                   s.wear, s.harness,
 +                   ( SELECT count(*)
 +                     FROM resources r
 +                    WHERE r.restype=?
 +                        AND r.resname=?
 +                        AND r.owntaskid!=?
 +                        AND r.owntaskid!=?
 +                     ) AS ntasks
 +              FROM resource_sharing s
 +             WHERE s.restype=? AND s.resname=?
 +END
 +    # s.. # fix perl-mode
 +
 +    $alloc_sharing_slot_q= $dbh_tests->prepare(<<END);
 +            SELECT * FROM resources
 +                  WHERE restype=? AND resname=? AND owntaskid=?
 +                  LIMIT 1
 +END
 +
 +    $claim_share_reuse_q= $dbh_tests->prepare(<<END);
 +        UPDATE resource_sharing
 +           SET wear = wear + 1
 +         WHERE restype=? AND resname=?
 +END
 +
 +    $claim_maxshare_q= $dbh_tests->prepare(<<END);
 +        SELECT max(shareix) AS shares
 +          FROM resources WHERE restype=? AND resname=?
 +END
 +
 +    $claim_rmshares_q= $dbh_tests->prepare(<<END);
 +        DELETE FROM resources
 +              WHERE restype=? AND resname=?
 +                AND (owntaskid=? OR owntaskid=?)
 +END
 +
 +    $claim_noshares_q= $dbh_tests->prepare(<<END);
 +        SELECT * FROM resources
 +              WHERE restype=? AND resname=?
 +              LIMIT 1
 +END
 +
 +    $claim_rmshare_q= $dbh_tests->prepare(<<END);
 +        DELETE FROM resource_sharing
 +              WHERE restype=? AND resname=?
 +END
 +
 +    $claim_setres_q= $dbh_tests->prepare(<<END);
 +        UPDATE resources
 +           SET owntaskid = ?, subtask = ?
 +         WHERE restype=? AND resname=? AND shareix=?
 +END
 +    $claim_share_new_q= $dbh_tests->prepare(<<END);
 +        INSERT INTO resource_sharing
 +                    (restype, resname, sharetype, state,  wear, harness)
 +             VALUES (?,       ?,       ?,         'prep', 1,    ?      )
 +END
 +    $claim_share_newresource_q= $dbh_tests->prepare(<<END);
 +        INSERT INTO resources
 +                    (restype, resname, shareix, owntaskid, subtask)
 +             VALUES (?,       ?,       ?,       ?,         ''     )
 +END
 +
 +}
 +
 +#---------- finding possibilites for a particular host ----------
 +
 +our @hids;
 +
 +sub compute_hids () {
 +    our %equivs;
 +
 +    foreach my $ident (@ARGV) {
 +        my $hid= { };
 +        my $override_use;
 +        if ($ident =~ m/\=/) {
 +            $hid->{OverrideUse}= $'; #'
 +            $ident= $`;
 +            print DEBUG "HID $ident OVERRIDE $hid->{OverrideUse}\n";
 +        }
 +        my @flags= get_hostflags($ident);
 +        print DEBUG "HID $ident FLAGS @flags\n";
 +        $hid->{Ident}= $ident;
 +        my %flags;
 +        foreach my $flag (@flags) {
 +            print DEBUG "HID $ident FLAG $flag\n";
 +            if ($flag =~ m/^share-/) {
 +                die if exists $hid->{Shared};
 +                my $shr= $'; #'
++                $hid->{Shared}= $shr." ".get_harness_rev();
 +                if ($shr =~ m/^build-/) {
 +                    $hid->{DefaultSharedMaxTasks}= 3;
 +                    $hid->{DefaultSharedMaxWear}= 10;
 +                } else {
 +                    # who can say
 +                    $hid->{DefaultSharedMaxTasks}= 2;
 +                    $hid->{DefaultSharedMaxWear}= 5;
 +                }
 +                print DEBUG "HID $ident FLAG $flag SHARE $shr".
 +                    " $hid->{DefaultSharedMaxTasks}".
 +                    " $hid->{DefaultSharedMaxWear}\n";
 +                next;
 +            } elsif ($flag =~ m/^equiv-/) {
 +                my $formalclass= $'; #'
 +                die if exists $hid->{Equiv};
 +                $equivs{$formalclass}{FormalClass}= $formalclass;
 +                $equivs{$formalclass}{Wanted}++;
 +                my $equiv= $hid->{Equiv}= $equivs{$formalclass};
 +                print DEBUG "HID $ident FLAG $flag EQUIV $equiv->{Wanted}\n";
 +                next;
 +            }
 +            $flags{$flag}= 1;
 +        }
 +        $hid->{Flags}= \%flags;
 +        print DEBUG "HID $ident FLAGS ".(join ',', sort keys %flags)."\n";
 +        push @hids, $hid;
 +    }
 +}
 +
 +sub hid_find_possibilities ($) {
 +    my ($hid) = @_;
 +
 +    delete $hid->{Candidates};
 +
 +    my $use= $hid->{OverrideUse} || $r{ $hid->{Ident} };
 +
 +    my $findhostsq;
 +    if (defined $use) {
 +        print DEBUG "HID $hid->{Ident} USE $use\n";
 +      $findhostsq= $dbh_tests->prepare(<<END);
 +          SELECT *
 +            FROM resources
 +           WHERE restype=? AND resname=? AND shareix=0
 +END
 +        $findhostsq->execute('host',$use);
 +    } else {
 +        print DEBUG "HID $hid->{Ident} INTENDED $fi->{intended}\n";
 +      $findhostsq= $dbh_tests->prepare(<<END);
 +          SELECT *
 +            FROM resources JOIN hostflags
 +              ON (restype='host' AND shareix=0 AND
 +                  resname=hostname AND hostflag=?)
 +END
 +        $findhostsq->execute("blessed-$fi->{intended}");
 +    }
 +
 +    my @candidates;
 +    my $any=0;
 +
 +    while (my $candrow= $findhostsq->fetchrow_hashref()) {
 +        $candrow->{Warnings}= [ ];
 +        $candrow->{Reso}= "$candrow->{restype} $candrow->{resname}";
 +
 +        my $dbg= "HID $hid->{Ident} TRY $candrow->{Reso}:";
 +        print DEBUG "$dbg\n";
 +        my @missingflags;
 +
 +        my $needflag= sub {
 +            my ($flag) = @_;
 +            print DEBUG "$dbg NEEDFLAG $flag\n";
 +            $flagscheckq->execute($candrow->{resname}, $flag);
 +            my $row= $flagscheckq->fetchrow_arrayref();
 +            $flagscheckq->finish();
 +            return if $row;
 +            push @missingflags, $flag;
 +        };
 +
 +        foreach my $flag (keys %{ $hid->{Flags} }) {
 +            $needflag->($flag);
 +        }
 +        my $equiv= $hid->{Equiv};
 +        if ($equiv && !defined $use) {
 +            # if it was specified exactly we don't care about equiv classes
 +            # if you specify one of a class you should specify all, then!
 +            print DEBUG "$dbg EQUIV $equiv->{FormalClass}\n";
 +          $equivflagscheckq->execute($candrow->{resname});
 +          my $erow= $equivflagscheckq->fetchrow_hashref();
 +          if (!$erow) {
 +              print DEBUG "$dbg EQUIV $equiv->{FormalClass} NO-CLASSES\n";
 +              next;
 +          }
 +          my $eq= $erow->{hostflag};
 +          print DEBUG "$dbg EQUIV $equiv->{FormalClass} MAYBE $eq\n";
 +          $candrow->{EquivActual}= $eq;
 +          $erow= $equivflagscheckq->fetchrow_hashref();
 +          if ($erow) {
 +              push @{ $candrow->{Warnings} },
 +                  "host has multiple equivalence class flags";
 +          }
 +          $equivflagscheckq->finish();
 +      }
 +
 +        print DEBUG "$dbg FLAGS MISSINGFLAGS: @missingflags.\n";
 +        if (@missingflags) {
 +            next unless defined $use;
 +            push @{ $candrow->{Warnings} },
 +                "specified host lacks flags @missingflags";
 +        }
 +        $any++;
 +
 +        print DEBUG "$dbg GOOD\n";
 +
 +        find_recent_duration($dbg,$hid,$candrow);
 +
 +        foreach my $kcomb (qw(Shared-Max-Wear Shared-Max-Tasks)) {
 +            my $kdb= $kcomb;  $kdb =~ y/-A-Z/ a-z/;
 +            my $khash= $kcomb;  $khash =~ y/-//d;
 +            $resprop_q->execute($candrow->{restype},$candrow->{resname},$kdb);
 +            my $proprow= $resprop_q->fetchrow_hashref();
 +            my $val= $proprow->{val};
 +            if (defined $val) {
 +                print DEBUG "$dbg $khash ($kdb) FROM-RES-PROP $val\n";
 +            } else {
 +                $val= $hid->{"Default$khash"};
 +                print DEBUG "$dbg $khash ($kdb) FROM-DEFAULT $val\n";
 +            }
 +            $candrow->{$khash}= $val;
 +        }
 +
 +      push @candidates, $candrow;
 +      print DEBUG "$dbg CANDIDATE.\n";
 +    }
 +    $findhostsq->finish();
 +
 +    if (!@candidates) {
 +        if (defined $use) {
 +            logm("specified host $use for $hid->{Ident} nonexistent?");
 +        } else {
 +            logm("no suitable host for $hid->{Ident} (out of $any)");
 +        }
 +    }
 +
 +    $hid->{Candidates} = \@candidates;
 +}
 +
 +sub find_recent_duration ($$) {
 +    my ($dbg, $hid, $candrow) = @_;
 +    
 +    ($candrow->{Duration},
 +     $candrow->{MostRecentStarted},
 +     $candrow->{MostRecentStatus}) =
 +        $duration_estimator->($job, $hid->{Ident}, $candrow->{resname});
 +}
 +
 +
 +#---------- constructing a plan ----------
 +
 +sub hid_class_size {
 +    my ($hid) = @_;
 +    my $equiv= $hid->{Equiv};
 +    return 0 unless $equiv;
 +    return $equiv->{Wanted};
 +}
 +
 +sub optimally_order_hids () {
 +    @hids= sort {
 +      hid_class_size($b) <=> hid_class_size($a) ||
 +      @{ $a->{Candidates} } <=> @{ $b->{Candidates} }
 +    } @hids;
 +    print DEBUG "ORDER ".(join ' ', map { $_->{Ident} } @hids)."\n";
 +}
 +
 +# reset each time around:
 +our $plan;
 +our $best;
 +
 +sub hid_recurse ($$);
 +sub hid_recurse ($$) {
 +    my ($actualmap, $hidix) = @_;
 +    # fills in the plan from $hidix (inclusive) to the end
 +
 +    our %noreuse;
 +    my $dbg= sprintf "RECURSE %*s", $hidix*2, '';
 +    if ($hidix < @hids) {
 +      my $hid= $hids[$hidix];
 +      print DEBUG "$dbg $hid->{Ident}...\n";
 +      foreach my $cand (@{ $hid->{Candidates} }) {
 +            my $reso= $cand->{Reso};
 +          print DEBUG "$dbg CAND $reso\n";
 +            next if defined $noreuse{$reso};
 +
 +          my $poss_actual= $cand->{EquivActual};
 +          my $newactualmap= $actualmap;
 +          if (defined $poss_actual) { # i.e. iff $hid->{Equiv} and not $use
 +              my $equiv= $hid->{Equiv};
 +              my $formal= $equiv->{FormalClass};
 +              my $already= $actualmap->{$formal};
 +              if (defined $already) {
 +                  if ($already ne $poss_actual) {
 +                      print DEBUG "$dbg CAND $reso EQUIV".
 +                          " WRONG $formal: $already != $poss_actual\n";
 +                      next;
 +                  }
 +              } else {
 +                  print DEBUG "$dbg CAND $reso EQUIV".
 +                      " SET $formal: $poss_actual\n";
 +                  $newactualmap= { %$actualmap, $formal => $poss_actual };
 +              }
 +          }
 +
 +          $hid->{Selected}= $cand;
 +            $noreuse{$reso}=1;
 +          hid_recurse($newactualmap, $hidix+1);
 +            delete $noreuse{$reso};
 +      }
 +      return;
 +    }
 +
 +    # hah, got to the end, see when we could do it and calculate the cost
 +
 +    print DEBUG "$dbg EVAL "
 +        .(join '; ', map { $_->{Selected}{Reso} } @hids)."\n";
 +
 +    my $variation_age= 0;
 +    my $duration= undef;
 +    my $previously_failed = 0;
 +    foreach my $hid (@hids) {
 +      my $cand= $hid->{Selected};
 +      my $recentstarted= $cand->{MostRecentStarted};
 +      $variation_age +=
 +            defined $recentstarted ? time - $recentstarted : 90*86400;
 +      $duration= $cand->{Duration} if
 +          !defined($duration) ||
 +          defined($cand->{Duration}) && $cand->{Duration} >= $duration;
 +        $previously_failed++ if $cand->{MostRecentStatus} eq 'fail';
 +    }
 +    my $duration_rightaway_adjust= 0;
 +    
 +    if (!defined $duration) {
 +        # if we can start now then don't penalise unknown hosts so much
 +        $duration= 5000;
 +        $duration_rightaway_adjust= 1000 - $duration;
 +    }
 +    $duration += 10;
 +
 +    print DEBUG "$dbg EVAL DURATION $duration va=$variation_age\n";
 +
 +    if ($jobinfo->{recipe} =~ m/build/) {
 +        $variation_age= 0;
 +    } elsif ($variation_age > 5*86400) {
 +      $variation_age= 5*86400;
 +    } 
 +
 +    my @requestlist;
 +    foreach my $hid (@hids) {
 +        my $req= {
 +            Reso => $hid->{Selected}{Reso},
 +            Ident => $hid->{Ident},
 +            Shared => $hid->{Shared},
 +            SharedMaxWear => $hid->{Selected}{SharedMaxWear},
 +            SharedMaxTasks => $hid->{Selected}{SharedMaxTasks},
 +        };
 +        push @requestlist, $req;
 +    }
 +    my $dbgprint= sub { print DEBUG "$dbg @_\n"; };
 +    my $planned= plan_search($plan, $dbgprint, $duration, \@requestlist);
 +    my $start_time= $planned->{Start};
 +    my $share_reuse= $planned->{ShareReuse};
 +
 +    $duration_rightaway_adjust=0 if $start_time;
 +
 +    my $cost= $start_time
 +      + $duration
 +      + $duration_rightaway_adjust
 +        - $previously_failed * 366*86400
 +        + ($previously_failed ? + $variation_age * 10 : - $variation_age / 30)
 +      - $share_reuse * 10000;
 +    
 +    print DEBUG "$dbg FINAL start=$start_time va=$variation_age".
 +        " previously_failed=$previously_failed cost=$cost\n";
 +
 +    if (!defined $best || $cost < $best->{Cost}) {
 +        print DEBUG "$dbg FINAL BEST: ".
 +          (join '; ', map { $_->{Selected}{Reso} } @hids). "\n";
 +      $best= {
 +          Cost => $cost,
 +          Selections => [ map { $_->{Selected} } @hids ],
 +          Start => $start_time,
 +          Duration => $duration,
 +      };
 +    }
 +}
 +
 +#---------- committing to a plan ----------
 +
 +sub alloc_hosts () {
 +    if (!@hids) {
 +        logm("host allocation: no nosts requested!");
 +        return;
 +    }
 +
 +    my $waitstartadjust=
 +        $jobinfo->{recipe} =~ m/build/
 +        ? -10000
 +        : -10 * @hids;
 +
 +    alloc_resources(WaitStart =>
 +                    ($ENV{OSSTEST_RESOURCE_WAITSTART} || $fi->{started}),
 +                    WaitStartAdjust => $waitstartadjust,
 +                    \&attempt_allocation);
 +
 +    foreach my $hid (@hids) {
 +        my $sel= $hid->{Selected};
 +        die $hid->{Ident} unless defined $sel->{resname};
 +        my $use= $r{ $hid->{Ident} };
 +        next if defined $use;
 +        store_runvar($hid->{Ident}, $sel->{resname});
 +    }
 +
 +    logm("host allocation: all successful and recorded.");
 +}
 +
 +sub attempt_allocation {
 +    ($plan) = @_;
 +    undef $best;
 +
 +    logm("allocating hosts: ".join(' ', map { $_->{Ident} } @hids));
 +
 +    prepare_statements();
 +
 +    foreach my $hid (@hids) {
 +      delete $hid->{Allocated};
 +      hid_find_possibilities($hid);
 +    }
 +    optimally_order_hids();
 +
 +    hid_recurse({}, 0);
 +
 +    if (!$best) {
 +      logm("no plan is possible");
 +      die "no plan is possible";
 +    }
 +
 +    foreach (my $ix=0; $ix<@hids; $ix++) {
 +      $hids[$ix]{Selected}= $best->{Selections}[$ix];
 +    }
 +
 +    my $retval=0;
 +
 +    if (!$best->{Start}) {
 +      $retval= 1;
 +      foreach my $hid (@hids) {
 +          my $got= actual_allocation($hid);
 +          if (!$got) {
 +              $retval= 0;
 +              last;
 +          }
 +          $hid->{Allocated}= $got;
 +      }
 +    }
 +    if (!$retval) {
 +      foreach my $hid (@hids) { delete $hid->{Allocated}; }
 +    }
 +
 +    if ($retval) {
 +      logm("host allocation: successful, reporting to planner.");
 +    } else {
 +      logm("host allocation: planned start in $best->{Start} seconds.");
 +    }
 +
 +    my $booklist= compute_booking_list();
 +
 +    return ($retval, $booklist);
 +}
 +
 +sub compute_booking_list () {
 +    my @bookings;
 +    foreach my $hid (@hids) {
 +      my $sel= $hid->{Selected};
 +      my $alloc= $hid->{Allocated};
 +      my $book= $alloc || {
 +          (defined $hid->{Shared} ? (Share => {
 +              Type => $hid->{Shared},
 +              Shares => $sel->{SharedMaxTasks},
 +          }) : ()),
 +      };
 +      $book->{Reso}= "$sel->{restype} $sel->{resname}";
 +      $book->{Xinfo}= $hid->{Ident};
 +      $book->{Start}= $best->{Start},
 +      $book->{End}= $best->{Start} + $best->{Duration},
 +      push @bookings, $book;
 +    }
 +    return { Bookings => \@bookings };
 +}
 +
 +#---------- actually allocate things ----------
 +
 +sub actual_allocation ($) {
 +    my ($hid) = @_;
 +
 +    # if successful returns { Allocated =>, Share => }
 +    # as for booking list otherwise undef
 +
 +    my $sel= $hid->{Selected};
 +    my $shr= $hid->{Shared};
 +
 +    die unless $sel->{shareix}==0;
 +
 +    $alloc_findres_q->execute($sel->{restype}, $sel->{resname});
 +    my $cand= $alloc_findres_q->fetchrow_hashref();
 +    $alloc_findres_q->finish();
 +    if (!$cand) {
 +        warn "resource $sel->{Reso} allegedly allocatable but not found";
 +        return undef;
 +    }
 +
 +    my $allocatable= $cand->{owntaskid} == $magictaskid{allocatable};
 +    my $dbg= "$hid->{Ident} $sel->{Reso}";
 +    printf DEBUG "%s ALLOC PLAIN %d\n", $dbg, $allocatable;
 +
 +    my $shared;
 +    my $shrestype= 'share-'.$sel->{restype};
 +
 +    if ($cand->{owntaskid} == $magictaskid{shared}) {
 +      $alloc_shared_q->execute
 +                ($shrestype, $sel->{resname},
 +                 $magictaskid{allocatable}, $magictaskid{preparing},
 +                 $sel->{restype}, $sel->{resname});
 +      $shared= $alloc_shared_q->fetchrow_hashref();
 +      $alloc_shared_q->finish();
 +
 +      if (!$shared) {
 +            warn "resource $sel->{Reso} allegedly shared but no sharing info";
 +            return undef;
 +        }
 +
 +      print DEBUG "$dbg ALLOC ISSHARED".
 +          " $shared->{sharetype} $shared->{state}".
 +          " $shared->{ntasks} $shared->{wear} $shared->{harness}\n";
 +
 +      if (!$shared->{ntasks}) {
 +          print DEBUG "$dbg ALLOC ISSHARED CAN-UNSHARE\n";
 +          $allocatable= 1;
 +        }
 +    }
 +
 +    my @allocwarnings;
 +
 +    if (defined $shr &&
 +      defined $shared &&
 +      $shared->{sharetype} eq $shr &&
 +      $shared->{wear} < $sel->{SharedMaxWear} &&
 +        $shared->{harness} eq $harness_rev) {
 +
 +      print DEBUG "$dbg ALLOC SHARED ST $shrestype $shared->{state}\n";
 +
 +      if ($shared->{state} eq 'ready') {
 +          $alloc_sharing_slot_q->execute($shrestype, $shared->{resname},
 +                                         $magictaskid{allocatable});
 +          my $slot= $alloc_sharing_slot_q->fetchrow_hashref();
 +          $alloc_sharing_slot_q->finish();
 +          if (!$slot) {
 +              # this is no use then
 +              print DEBUG "$dbg ALLOC SHARED AVAIL BUSY\n";
 +              warn "resource $shrestype $shared->{resname} allegedly".
 +                    " shareable but no slots";
 +              return undef;
 +          } else {
 +              print DEBUG "$dbg ALLOC SHARED AVAIL SLOTFREE".
 +                  " $slot->{shareix}\n";
 +              $allocatable= 2;
 +              $cand= $slot;
 +          }
 +      } else {
 +          if ($shared->{ntasks}) {
 +              warn "resource $shrestype $shared->{resname} allegedly".
 +                    " available but wrong state $shared->{state} and tasks";
 +              return undef;
 +
 +                # someone was preparing it but they aren't any more
 +                push @allocwarnings,
 +                    "previous preparation apparently abandoned";
 +                $allocatable= 1;
 +            }
 +        }
 +    }
 +
 +    if (!$allocatable) {
 +      warn "resource $sel->{restype} $sel->{resname} allegedly".
 +            " allocatable but not really";
 +      return undef;
 +    }
 +
 +    # Right, allocate this one!
 +    my @reskey= map { $cand->{$_} } qw(restype resname shareix);
 +    print DEBUG "HID $hid->{Ident} ALLOC @reskey\n";
 +
 +    my $bookalloc= { };
 +
 +    if ($allocatable==2) {
 +      # sharing reuse
 +        print DEBUG "HID $hid->{Ident} GO @reskey SHARING REUSE $shrestype\n";
 +
 +        $claim_share_reuse_q->execute($sel->{restype}, $cand->{resname});
 +        $claim_share_reuse_q->finish();
 +
 +      $claim_maxshare_q->execute($shrestype, $cand->{resname});
 +      my ($nshares) = $claim_maxshare_q->fetchrow_array();
 +      $claim_maxshare_q->finish();
 +
 +      $bookalloc->{Share}= {
 +          Type => $shr,
 +          Shares => $nshares,
 +      };
 +    } else {
 +        if ($shared) {
 +            print DEBUG "HID $hid->{Ident} GO @reskey UNSHARE\n";
 +            $claim_rmshares_q->execute($shrestype, $cand->{resname},
 +                   $magictaskid{allocatable}, $magictaskid{preparing});
 +            $claim_rmshares_q->finish();
 +
 +            $claim_noshares_q->execute($shrestype, $cand->{resname});
 +            my $bad= $claim_noshares_q->fetchrow_hashref();
 +            $claim_noshares_q->finish();
 +            die Dumper($bad).'?' if $bad;
 +
 +            $claim_rmshare_q->execute($cand->{restype},$cand->{resname});
 +            $claim_rmshare_q->finish();
 +        }
 +        if ($shr) {
 +            print DEBUG "HID $hid->{Ident} GO @reskey SHARE\n";
 +            $claim_setres_q->execute($magictaskid{shared},"",
 +                                   $cand->{restype},$cand->{resname},0);
 +            $claim_setres_q->finish();
 +
 +            $claim_share_new_q->execute($cand->{restype},$cand->{resname},
 +                                      $shr, $harness_rev);
 +            $claim_share_new_q->finish();
 +
 +            for (my $ix=1; $ix<=$sel->{SharedMaxTasks}; $ix++) {
 +                $claim_share_newresource_q->execute
 +                    ($shrestype, $cand->{resname}, $ix,
 +                     ($ix==1 ? $magictaskid{allocatable}
 +                      : $magictaskid{preparing}));
 +                $claim_share_newresource_q->finish();
 +            }
 +            $reskey[0]= $shrestype;
 +            $reskey[2]= 1;
 +
 +          $bookalloc->{Share}= {
 +              Type => $shr,
 +              Shares => $sel->{SharedMaxTasks},
 +          };
 +        }
 +    }
 +    
 +    logm("allocating for $hid->{Ident}: ".show_reskey(@reskey));
 +    foreach my $warn (@allocwarnings, @{ $cand->{Warnings} }) {
 +        logm("warning about ".show_reskey(@reskey).": $warn");
 +    }
 +
 +    print DEBUG "HID $hid->{Ident} GO @reskey ALLOCATE\n";
 +    my $allocd= $claim_setres_q->execute
 +        ($taskid, "$flight.$job $hid->{Ident}", @reskey);
 +    die unless $allocd;
 +    $claim_setres_q->finish();
 +
 +    $bookalloc->{Allocated}= { Shareix => $reskey[2], Task => $taskid };
 +    return $bookalloc;
 +}
 +
 +#---------- main program ----------
 +
 +setup();
 +compute_hids();
 +alloc_hosts();
diff --cc ts-xen-build
Simple merge
index 9fd3d1ab168f0b34d238f7437c944b6ef290924e,61823516ec37da5d5bae36aef2747be7f3e3b7e5..7b3f961b512f6a7e27282dc1c6991befbc081fc0
@@@ -221,12 -218,13 +221,13 @@@ sub ccache_setup () 
      }
  }
  
- lvextend_stage1();
- prep();
- cacheing_git_mount();
- cacheing_git_install();
- ccache_setup();
- lvextend_stage2();
+ if (!$ho->{Flags}{'no-reinstall'}) {
+     lvextend_stage1();
+     prep();
+     cacheing_git_mount();
+     cacheing_git_install();
+     ccache_setup();
+     lvextend_stage2();
+ }
 -resource_shared_mark_ready($ho->{Ident}, $ho->{Name},
 -                           "build-".$ho->{Suite}."-".$r{arch});
 +$mjobdb->jobdb_resource_shared_mark_ready
 +   ($ho->{Ident}, $ho->{Name}, "build-".$ho->{Suite}."-".$r{arch});
diff --cc ts-xen-install
Simple merge