autotest: IPv6 support

IPv4 requires a DHCP server. IPv4 addresses change more often than
IPv6 ones, which breaks long-running tests. IPv4 addresses are mangled
by NATs. etc.

Unit tests:
- Extend coverage with additional tests.
- Adjust exception types in some negative tests.
- "host:port" negative test removed since port detection is now
  stricter, considering digits only. Preserving the laxer detection
  would make the code a bit more complex for no real value.
- "host:22:33" is now mistaken for an IPv6 address.  Remove this
  negative test too since there isn't anymore a way to invalidate
  without adding / backporting from Python 3.3 some minimal IPv6
  address parser.

Manually tested these two types of input and they still fail; however
failures come later as "Could not resolve hostname" network errors,
beyond the reach of unit tests.

BUG=none
TEST=./utils/unittest_suite.py --debug -r server
TEST=\
  v4=10.1.2.3
  v6=fe80::9249:faff:fe00:7400%p4p1
  hname=yourdut.local
  mytest=hardware_DiskSize
  (set -ex;
   for a in $v4 ${v4}:22 $v6 [$v6] [$v6]:22 $hname ${hname}:22; do
       test_that "$a" $mytest
       stat /tmp/test_that_latest/results-1-"$mytest/$mytest/"
   done ); [ $? -eq 0 ] || echo FAILED
  # cannot trust autotest's "PASS", see http://crbug.com/555073
TEST=\
  # Disable rsync on DUT:
  mount -o remount,rw /
 ( cd /usr/local/bin/; mv rsync rsync.disabled )
  # On host:
  #  run again same test loop above (this time with "scp" instead of rsync)

Change-Id: Icb7d232a9f64d1277810758a5c6c16d5711a0163
Reviewed-on: https://chromium-review.googlesource.com/312719
Commit-Ready: Marc Herbert <marc.herbert@intel.com>
Tested-by: Michael W Mason <michael.w.mason@intel.com>
Reviewed-by: Dan Shi <dshi@chromium.org>
Reviewed-by: Michael W Mason <michael.w.mason@intel.com>
diff --git a/server/hosts/abstract_ssh.py b/server/hosts/abstract_ssh.py
index c7641cb..6a7367f 100644
--- a/server/hosts/abstract_ssh.py
+++ b/server/hosts/abstract_ssh.py
@@ -1,4 +1,4 @@
-import os, time, socket, shutil, glob, logging, traceback, tempfile
+import os, time, socket, shutil, glob, logging, traceback, tempfile, re
 import subprocess
 
 from multiprocessing import Lock
@@ -121,7 +121,16 @@
         """
         if escape:
             paths = [utils.scp_remote_escape(path) for path in paths]
-        return '%s@%s:"%s"' % (self.user, self.hostname, " ".join(paths))
+
+        remote = self.hostname
+
+        # rsync and scp require IPv6 brackets, even when there isn't any
+        # trailing port number (ssh doesn't support IPv6 brackets).
+        # In the Python >= 3.3 future, 'import ipaddress' will parse addresses.
+        if re.search(r':.*:', remote):
+            remote = '[%s]' % remote
+
+        return '%s@%s:"%s"' % (self.user, remote, " ".join(paths))
 
 
     def _make_rsync_cmd(self, sources, dest, delete_dest, preserve_symlinks):