Add support for tags

Goldfish is getting ready to release base-images that expose https
endpoints. This means that we need to configure the firewall to allow
https to go through.

Opening up the firewall is done by setting tags on the instance, these
tags can be provided from the command line. For example:

$ acloud create_gf --tags http-server https-server ....

Will create an instance with the firewall opened for http and https.

Includes fixes to properly run unit-test that can be run from setup.py.

Change-Id: I8344c21c22f0d2760fc73529df0810a70797c08a
diff --git a/internal/lib/android_compute_client.py b/internal/lib/android_compute_client.py
index 2c77cfb..293a57c 100755
--- a/internal/lib/android_compute_client.py
+++ b/internal/lib/android_compute_client.py
@@ -239,7 +239,8 @@
                        extra_disk_name=None,
                        labels=None,
                        avd_spec=None,
-                       extra_scopes=None):
+                       extra_scopes=None,
+                       tags=None):
         """Create a gce instance with a gce image.
 
         Args:
@@ -263,6 +264,8 @@
             avd_spec: AVDSpec object that tells us what we're going to create.
             extra_scopes: List, extra scopes (strings) to be passed to the
                           instance.
+            tags: A list of tags to associate with the instance. e.g.
+                 ["http-server", "https-server"]
         """
         self._CheckMachineSize()
         disk_args = self._GetDiskArgs(instance, image_name)
@@ -297,7 +300,7 @@
         super(AndroidComputeClient, self).CreateInstance(
             instance, image_name, self._machine_type, metadata, self._network,
             self._zone, disk_args, image_project, gpu, extra_disk_name,
-            labels=labels, extra_scopes=extra_scopes)
+            labels=labels, extra_scopes=extra_scopes, tags=tags)
 
     def CheckBootFailure(self, serial_out, instance):
         """Determine if serial output has indicated any boot failure.
diff --git a/internal/lib/android_compute_client_test.py b/internal/lib/android_compute_client_test.py
index f73913f..34f4346 100644
--- a/internal/lib/android_compute_client_test.py
+++ b/internal/lib/android_compute_client_test.py
@@ -139,7 +139,7 @@
                   instance_name, self.IMAGE, self.MACHINE_TYPE,
                   expected_metadata, self.NETWORK, self.ZONE,
                   expected_disk_args, image_project, gpu, extra_disk_name,
-                  labels=labels, extra_scopes=self.EXTRA_SCOPES)
+                  labels=labels, extra_scopes=self.EXTRA_SCOPES, tags=None)
 
     # pylint: disable=invalid-name
     def testCheckMachineSizeMeetsRequirement(self):
diff --git a/internal/lib/gcompute_client.py b/internal/lib/gcompute_client.py
index 759fb65..ddf6a1a 100755
--- a/internal/lib/gcompute_client.py
+++ b/internal/lib/gcompute_client.py
@@ -1084,7 +1084,8 @@
                        gpu=None,
                        extra_disk_name=None,
                        labels=None,
-                       extra_scopes=None):
+                       extra_scopes=None,
+                       tags=None):
         """Create a gce instance with a gce image.
 
         Args:
@@ -1106,6 +1107,8 @@
             extra_disk_name: String,the name of the extra disk to attach.
             labels: Dict, will be added to the instance's labels.
             extra_scopes: A list of extra scopes to be provided to the instance.
+            tags: A list of tags to associate with the instance. e.g.
+                  ["http-server", "https-server"]
         """
         disk_args = (disk_args
                      or self._GetDiskArgs(instance, image_name, image_project))
@@ -1131,6 +1134,8 @@
 
         if labels is not None:
             body["labels"] = labels
+        if tags:
+            body["tags"] = { "items": tags }
         if gpu:
             body["guestAccelerators"] = [{
                 "acceleratorType": self.GetAcceleratorUrl(gpu, zone),
diff --git a/internal/lib/gcompute_client_test.py b/internal/lib/gcompute_client_test.py
index 5176306..df84a87 100644
--- a/internal/lib/gcompute_client_test.py
+++ b/internal/lib/gcompute_client_test.py
@@ -577,6 +577,78 @@
             operation_scope=gcompute_client.OperationScope.ZONE,
             scope_name=self.ZONE)
 
+
+    @mock.patch.object(gcompute_client.ComputeClient, "GetImage")
+    @mock.patch.object(gcompute_client.ComputeClient, "GetNetworkUrl")
+    @mock.patch.object(gcompute_client.ComputeClient, "GetSubnetworkUrl")
+    @mock.patch.object(gcompute_client.ComputeClient, "GetMachineType")
+    @mock.patch.object(gcompute_client.ComputeClient, "WaitOnOperation")
+    def testCreateInstanceWithTags(self, mock_wait, mock_get_mach_type,
+                           mock_get_subnetwork_url, mock_get_network_url,
+                           mock_get_image):
+        """Test CreateInstance."""
+        mock_get_mach_type.return_value = {"selfLink": self.MACHINE_TYPE_URL}
+        mock_get_network_url.return_value = self.NETWORK_URL
+        mock_get_subnetwork_url.return_value = self.SUBNETWORK_URL
+        mock_get_image.return_value = {"selfLink": self.IMAGE_URL}
+        resource_mock = mock.MagicMock()
+        self.compute_client._service.instances = mock.MagicMock(
+            return_value=resource_mock)
+        resource_mock.insert = mock.MagicMock()
+        self.Patch(
+            self.compute_client,
+            "_GetExtraDiskArgs",
+            return_value=[{"fake_extra_arg": "fake_extra_value"}])
+        extra_disk_name = "gce-x86-userdebug-2345-abcd-data"
+        expected_disk_args = [self._disk_args]
+        expected_disk_args.extend([{"fake_extra_arg": "fake_extra_value"}])
+        expected_scope = []
+        expected_scope.extend(self.compute_client.DEFAULT_INSTANCE_SCOPE)
+        expected_scope.extend(self.EXTRA_SCOPES)
+
+        expected_body = {
+            "machineType": self.MACHINE_TYPE_URL,
+            "name": self.INSTANCE,
+            "networkInterfaces": [
+                {
+                    "network": self.NETWORK_URL,
+                    "subnetwork": self.SUBNETWORK_URL,
+                    "accessConfigs": [
+                        {"name": "External NAT",
+                         "type": "ONE_TO_ONE_NAT"}
+                    ],
+                }
+            ],
+            'tags': {'items': ['https-server']},
+            "disks": expected_disk_args,
+            "serviceAccounts": [
+                {"email": "default",
+                 "scopes": expected_scope}
+            ],
+            "metadata": {
+                "items": [{"key": self.METADATA[0],
+                           "value": self.METADATA[1]}],
+            },
+        }
+
+        self.compute_client.CreateInstance(
+            instance=self.INSTANCE,
+            image_name=self.IMAGE,
+            machine_type=self.MACHINE_TYPE,
+            metadata={self.METADATA[0]: self.METADATA[1]},
+            network=self.NETWORK,
+            zone=self.ZONE,
+            extra_disk_name=extra_disk_name,
+            tags=["https-server"],
+            extra_scopes=self.EXTRA_SCOPES)
+
+        resource_mock.insert.assert_called_with(
+            project=PROJECT, zone=self.ZONE, body=expected_body)
+        mock_wait.assert_called_with(
+            mock.ANY,
+            operation_scope=gcompute_client.OperationScope.ZONE,
+            scope_name=self.ZONE)
+
     @mock.patch.object(gcompute_client.ComputeClient, "GetAcceleratorUrl")
     @mock.patch.object(gcompute_client.ComputeClient, "GetImage")
     @mock.patch.object(gcompute_client.ComputeClient, "GetNetworkUrl")
diff --git a/internal/lib/goldfish_compute_client.py b/internal/lib/goldfish_compute_client.py
index 877eb3d..03872df 100644
--- a/internal/lib/goldfish_compute_client.py
+++ b/internal/lib/goldfish_compute_client.py
@@ -135,7 +135,8 @@
                        blank_data_disk_size_gb=None,
                        gpu=None,
                        avd_spec=None,
-                       extra_scopes=None):
+                       extra_scopes=None,
+                       tags=None):
         """Create a goldfish instance given a stable host image and a build id.
 
         Args:
@@ -153,6 +154,8 @@
                  acceleration is needed. e.g. "nvidia-tesla-k80"
             avd_spec: An AVDSpec instance.
             extra_scopes: A list of extra scopes to be passed to the instance.
+            tags: A list of tags to associate with the instance. e.g.
+                 ["http-server", "https-server"]
         """
         self._CheckMachineSize()
 
@@ -223,5 +226,6 @@
             network=self._network,
             zone=self._zone,
             gpu=gpu,
+            tags=tags,
             labels=labels,
             extra_scopes=extra_scopes)
diff --git a/internal/lib/goldfish_compute_client_test.py b/internal/lib/goldfish_compute_client_test.py
index fcdf085..435c689 100644
--- a/internal/lib/goldfish_compute_client_test.py
+++ b/internal/lib/goldfish_compute_client_test.py
@@ -45,6 +45,7 @@
     BOOT_DISK_SIZE_GB = 10
     GPU = "nvidia-tesla-k80"
     EXTRA_SCOPES = "scope1"
+    TAGS=['http-server']
 
     def _GetFakeConfig(self):
         """Create a fake configuration object.
@@ -115,7 +116,8 @@
             self.INSTANCE, self.IMAGE, self.IMAGE_PROJECT, self.TARGET,
             self.BRANCH, self.BUILD_ID, self.EMULATOR_BRANCH,
             self.EMULATOR_BUILD_ID, self.EXTRA_DATA_DISK_SIZE_GB, self.GPU,
-            extra_scopes=self.EXTRA_SCOPES)
+            extra_scopes=self.EXTRA_SCOPES,
+            tags=self.TAGS)
 
         # pylint: disable=no-member
         gcompute_client.ComputeClient.CreateInstance.assert_called_with(
@@ -129,6 +131,7 @@
             network=self.NETWORK,
             zone=self.ZONE,
             gpu=self.GPU,
+            tags=self.TAGS,
             labels=expected_labels,
             extra_scopes=self.EXTRA_SCOPES)