[4/6] testbot/testagentd: Add support for upgrading the TestAgent server.

Francois Gouget fgouget at codeweavers.com
Mon Apr 14 08:45:45 CDT 2014


---

Each WineTestBot VM runs TestAgentd. So far it's been necessary to 
update each VM whenever TestAgentd needed to be upgraded, typically to 
add support for a new RPC, such as the settime RPC for instance. The 
more VMs, and the more test configurations, the longer that takes.

So with that patch it becomes possible to use the old TestAgentd server 
to bootstrap the new one and get an upgraded server. It's also possible 
to do this only when needed by checking the supported protocol version, 
and to automatically determine which binary to send by using the 
getproperties RPC to retrieve the 'server.arch' property.

So next time it will be possible to deploy a new TestAgentd server 
without needing to go through all the VMs. The main drawback is that it 
requires sending an extra 12KB (once the server is stripped and 
compressed) and a 1 to 2 seconds delay. But these can be avoided by 
updating the VMs *when practical* or required for other reasons.

This patch depends on all previous patches in the series.

 testbot/lib/WineTestBot/TestAgent.pm      | 100 ++++++++++++++++++++++++++
 testbot/src/testagentd/platform.h         |   7 ++
 testbot/src/testagentd/platform_unix.c    |  31 +++++++++
 testbot/src/testagentd/platform_windows.c |  33 +++++++++
 testbot/src/testagentd/testagentd.c       | 112 +++++++++++++++++++++++++++++-
 5 files changed, 281 insertions(+), 2 deletions(-)

diff --git a/testbot/lib/WineTestBot/TestAgent.pm b/testbot/lib/WineTestBot/TestAgent.pm
index 178cf51..fb14b38 100644
--- a/testbot/lib/WineTestBot/TestAgent.pm
+++ b/testbot/lib/WineTestBot/TestAgent.pm
@@ -37,6 +37,8 @@ my $RPC_WAIT = 4;
 my $RPC_RM = 5;
 my $RPC_WAIT2 = 6;
 my $RPC_SETTIME = 7;
+my $RPC_GETPROPERTIES = 8;
+my $RPC_UPGRADE = 9;
 
 my %RpcNames=(
     $RPC_PING => 'ping',
@@ -47,6 +49,8 @@ my %RpcNames=(
     $RPC_RM => 'rm',
     $RPC_WAIT2 => 'wait2',
     $RPC_SETTIME => 'settime',
+    $RPC_GETPROPERTIES => 'getproperties',
+    $RPC_UPGRADE => 'upgrade',
 );
 
 my $Debug = 0;
@@ -1276,4 +1280,100 @@ sub SetTime($)
   return $self->_RecvList('');
 }
 
+sub GetProperties($;$)
+{
+  my ($self, $PropName) = @_;
+  debug("GetProperties ", $PropName || "", "\n");
+
+  # Send the command
+  if (!$self->_StartRPC($RPC_GETPROPERTIES) or
+      !$self->_SendListSize('ArgC', 0))
+  {
+    return undef;
+  }
+
+  # Get the reply
+  my $Count = $self->_RecvListSize('PropertyCount');
+  return undef if (!$Count);
+
+  my $i = 0;
+  my $Properties;
+  while ($Count--)
+  {
+    my ($Type, $Size) = $self->_RecvEntryHeader("Prop$i");
+    if ($Type eq 's')
+    {
+      my $Property = $self->_RecvRawString("Prop$i.s", $Size);
+      return undef if (!defined $Property);
+      debug("  RecvProperty() -> '$Property'\n");
+      if ($Property =~ s/^([a-zA-Z0-9.]+)=//)
+      {
+        $Properties->{$1} = $Property;
+      }
+      else
+      {
+        $self->_SetError($ERROR, "Invalid property string '$Property'");
+        $self->_SkipEntries($Count);
+        return undef;
+      }
+    }
+    elsif ($Type eq 'e')
+    {
+      # The expected property was replaced with an error message
+      my $Message = $self->_RecvRawString("Str$i.e", $Size);
+      if (defined $Message)
+      {
+        debug("  RecvError() -> '$Message'\n");
+        $self->_SetError($ERROR, $Message);
+      }
+      $self->_SkipEntries($Count);
+      return undef;
+    }
+    else
+    {
+      $self->_SetError($ERROR, "Expected an s entry but got $Type instead");
+      $self->_SkipRawData("Prop$i.$Type", $Size);
+      $self->_SkipEntries($Count);
+      return undef;
+    }
+    $i++;
+  }
+
+  return $Properties->{$PropName} if (defined $PropName);
+  return $Properties;
+}
+
+sub Upgrade($$)
+{
+  my ($self, $Filename) = @_;
+  debug("Upgrade $Filename\n");
+
+  my $fh;
+  if (!open($fh, "<", $Filename))
+  {
+      $self->_SetError($ERROR, "Unable to open '$Filename' for reading: $!");
+      return undef;
+  }
+
+  # Send the command
+  if (!$self->_StartRPC($RPC_UPGRADE) or
+      !$self->_SendListSize('ArgC', 1) or
+      !$self->_SendFile('FileData', $fh, $Filename))
+  {
+      close($fh);
+      return undef;
+  }
+  close($fh);
+
+  # Get the reply
+  my $rc = $self->_RecvList('');
+
+  # The server has quit and thus the connection is no longer usable.
+  # So disconnect now to force the next RPC to reconnect, instead or letting it
+  # try to reuse the broken connection and fail.
+  $self->Disconnect();
+
+  return $rc;
+}
+
 1;
diff --git a/testbot/src/testagentd/platform.h b/testbot/src/testagentd/platform.h
index 785b3b5..c6c0f2e 100644
--- a/testbot/src/testagentd/platform.h
+++ b/testbot/src/testagentd/platform.h
@@ -85,6 +85,13 @@ int platform_wait(SOCKET client, uint64_t pid, uint32_t timeout, uint32_t *child
  */
 int platform_settime(uint64_t epoch, uint32_t leeway);
 
+/* Creates a script to be invoked to upgrade the current server.
+ * The current server is responsible for starting the script and quickly exit.
+ * The script will wait a bit, replace the server file and restart the server
+ * with the same arguments as the original server.
+ */
+int platform_upgrade_script(const char* script, const char* tmpserver, char** argv);
+
 /* Returns a string describing the last socket-related error */
 int sockeintr(void);
 const char* sockerror(void);
diff --git a/testbot/src/testagentd/platform_unix.c b/testbot/src/testagentd/platform_unix.c
index c9fc7fd..09330cb 100644
--- a/testbot/src/testagentd/platform_unix.c
+++ b/testbot/src/testagentd/platform_unix.c
@@ -222,6 +222,37 @@ int platform_settime(uint64_t epoch, uint32_t leeway)
     return 1;
 }
 
+int platform_upgrade_script(const char* script, const char* tmpserver, char** argv)
+{
+    char** arg;
+    FILE* fh;
+
+    fh = fopen(script, "w");
+    if (!fh)
+    {
+        set_status(ST_ERROR, "unable to open '%s' for writing: %s", script, strerror(errno));
+        return 0;
+    }
+    /* Allow time for the server to exit */
+    fprintf(fh, "#!/bin/sh\n");
+    fprintf(fh, "sleep 1\n");
+    fprintf(fh, "mv %s %s\n", tmpserver, argv[0]);
+    arg = argv;
+    while (*arg)
+    {
+        fprintf(fh, "'%s' ", *arg);
+        arg++;
+    }
+    fprintf(fh, "\n");
+    fclose(fh);
+    if (chmod(script, S_IRUSR | S_IWUSR | S_IXUSR) < 0)
+    {
+        set_status(ST_ERROR, "could not make '%s' executable: %s", script, strerror(errno));
+        return 0;
+    }
+    return 1;
+}
+
 int sockeintr(void)
 {
     return errno == EINTR;
diff --git a/testbot/src/testagentd/platform_windows.c b/testbot/src/testagentd/platform_windows.c
index 89e435d..dd8da8e 100644
--- a/testbot/src/testagentd/platform_windows.c
+++ b/testbot/src/testagentd/platform_windows.c
@@ -258,6 +258,39 @@ int platform_settime(uint64_t epoch, uint32_t leeway)
     return 1;
 }
 
+
+int platform_upgrade_script(const char* script, const char* tmpserver, char** argv)
+{
+    char testagentd[MAX_PATH];
+    DWORD rc;
+    FILE* fh;
+
+    rc = GetModuleFileName(NULL, testagentd, sizeof(testagentd));
+    if (!rc || rc == sizeof(testagentd))
+    {
+        set_status(ST_ERROR, "unable to get the current process filename (%lu, le=%lu)", rc, GetLastError());
+        return 0;
+    }
+
+    fh = fopen(script, "w");
+    if (!fh)
+    {
+        set_status(ST_ERROR, "unable to open '%s' for writing: %s", script, strerror(errno));
+        return 0;
+    }
+    /* Allow time for the server to exit */
+    fprintf(fh, "ping -n 1 -w 1000 1.1.1.1 >/nul\r\n");
+    /* Note that preserving the server filename is necessary and sufficient
+     * in order to get through the Windows firewall.
+     */
+    fprintf(fh, "copy /y \"%s\" \"%s\"\r\n", tmpserver, testagentd);
+    fprintf(fh, "del \"%s\"\r\n", tmpserver);
+    fprintf(fh, "%s\r\n", GetCommandLine());
+    fclose(fh);
+
+    return 1;
+}
+
 int sockretry(void)
 {
     return (WSAGetLastError() == WSAEINTR);
diff --git a/testbot/src/testagentd/testagentd.c b/testbot/src/testagentd/testagentd.c
index d5d8dfc..2aec9e9 100644
--- a/testbot/src/testagentd/testagentd.c
+++ b/testbot/src/testagentd/testagentd.c
@@ -36,11 +36,13 @@
  * 1.2:  Add more redirection options to the run RPC.
  * 1.3:  Fix the zero / infinite timeouts in the wait2 RPC.
  * 1.4:  Add the settime RPC.
+ * 1.5:  Add support for upgrading the server.
  */
-#define PROTOCOL_VERSION "testagentd 1.4"
+#define PROTOCOL_VERSION "testagentd 1.5"
 
 #define BLOCK_SIZE       65536
 
+static char** server_argv;
 const char *name0;
 int opt_debug = 0;
 
@@ -84,6 +86,8 @@ enum rpc_ids_t
     RPCID_RM,
     RPCID_WAIT2,
     RPCID_SETTIME,
+    RPCID_GETPROPERTIES,
+    RPCID_UPGRADE,
 };
 
 /* This is the RPC currently being processed */
@@ -129,6 +133,8 @@ const char* status_names[] = {"ok:", "error:", "fatal:"};
 /* If true, then the current connection is in a broken state */
 static int broken = 0;
 
+/* If true, then the server should exit */
+static int quit = 0;
 
 static char* vformat_msg(char** buf, unsigned* size, const char* format, va_list valist)
 {
@@ -155,6 +161,15 @@ static char* vformat_msg(char** buf, unsigned* size, const char* format, va_list
     return *buf;
 }
 
+static char* format_msg(char** buf, unsigned* size, const char* format, ...)
+{
+    va_list valist;
+    va_start(valist, format);
+    vformat_msg(buf, size, format, valist);
+    va_end(valist);
+    return *buf;
+}
+
 /* This is a message which indicates the reason for the status */
 static char* status_msg = NULL;
 static unsigned status_size = 0;
@@ -918,6 +933,90 @@ static void do_settime(SOCKET client)
         send_error(client);
 }
 
+static void do_getproperties(SOCKET client)
+{
+    const char* arch;
+    char* buf = NULL;
+    unsigned size = 0;
+
+    if (!expect_list_size(client, 0))
+    {
+        send_error(client);
+        return;
+    }
+    send_list_size(client, 2);
+
+    format_msg(&buf, &size, "protocol.version=%s", PROTOCOL_VERSION);
+    send_string(client, buf);
+
+#ifdef WIN32
+    arch = "win32";
+#else
+    if (sizeof(void*) == 4)
+        arch = "linux32";
+    else
+        arch = "linux64";
+#endif
+    format_msg(&buf, &size, "server.arch=%s", arch);
+    send_string(client, buf);
+    free(buf);
+}
+
+static void do_upgrade(SOCKET client)
+{
+    static const char *filename = "testagentd.tmp";
+    static const char* upgrade_script = "./replace.bat";
+    int fd, success;
+
+    if (!expect_list_size(client, 1)
+        /* Next entry is the file data */
+        )
+    {
+        send_error(client);
+        return;
+    }
+
+    unlink(filename); /* To force re-setting the mode */
+    fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0700);
+    if (fd < 0)
+    {
+        skip_entries(client, 1);
+        set_status(ST_ERROR, "unable to open '%s' for writing: %s", filename, strerror(errno));
+        success = 0;
+    }
+    else
+    {
+        success = recv_file(client, fd, filename);
+        close(fd);
+    }
+
+    if (!success)
+        unlink(filename);
+    else
+        success = platform_upgrade_script(upgrade_script, filename, server_argv);
+
+    if (success)
+    {
+        char* args[2];
+        char* redirects[3] = {"", "", ""};
+
+        send_list_size(client, 0);
+
+        args[0] = strdup(upgrade_script);
+        args[1] = NULL;
+        success = platform_run(args, RUN_DNT, redirects);
+        free(args[0]);
+        if (success)
+        {
+            broken = 1;
+            quit = 1;
+        }
+    }
+    else
+        send_error(client);
+
+}
+
 static void do_unknown(SOCKET client, uint32_t id)
 {
     uint32_t argc;
@@ -982,6 +1081,12 @@ static void process_rpc(SOCKET client)
     case RPCID_SETTIME:
         do_settime(client);
         break;
+    case RPCID_GETPROPERTIES:
+        do_getproperties(client);
+        break;
+    case RPCID_UPGRADE:
+        do_upgrade(client);
+        break;
     default:
         do_unknown(client, rpcid);
     }
@@ -1079,6 +1184,7 @@ int main(int argc, char** argv)
     SOCKET master;
     int on = 1;
 
+    server_argv = argv;
     name0 = p = argv[0];
     while (*p != '\0')
     {
@@ -1220,7 +1326,7 @@ int main(int argc, char** argv)
         exit(1);
     }
     printf("Starting %s\n", PROTOCOL_VERSION);
-    while (1)
+    while (!quit)
     {
         SOCKET client;
         debug("Waiting in accept()\n");
@@ -1250,6 +1356,8 @@ int main(int argc, char** argv)
             exit(1);
         }
     }
+    debug("stopping\n");
+    closesocket(master);
 
     return 0;
 }
-- 
1.9.1




More information about the wine-patches mailing list