[Acloud] Check if user has access to the project on start

Cherry-pick cl/148795271

TEST: Manually test the google3 version. can't test the android tree
version untill b/35918788 is fixed
BUG:32411892

Change-Id: I8920db33f82d035218ef608962849628bbc8d9eb
diff --git a/internal/lib/gcompute_client.py b/internal/lib/gcompute_client.py
index 8cd32b3..05710c4 100755
--- a/internal/lib/gcompute_client.py
+++ b/internal/lib/gcompute_client.py
@@ -26,6 +26,7 @@
 and it only keeps states about authentication. ComputeClient should be very
 generic, and only knows how to talk to Compute Engine APIs.
 """
+import copy
 import functools
 import logging
 import os
@@ -60,6 +61,7 @@
     OPERATION_TIMEOUT_SECS = 15 * 60  # 15 mins
     OPERATION_POLL_INTERVAL_SECS = 5
     MACHINE_SIZE_METRICS = ["guestCpus", "memoryMb"]
+    ACCESS_DENIED_CODE = 403
 
     def __init__(self, acloud_config, oauth2_credentials):
         """Initialize.
@@ -1015,3 +1017,25 @@
         sshkey_item["value"] = "\n".join([sshkey_item["value"].strip(), entry
                                           ]).strip()
         self.SetCommonInstanceMetadata(metadata)
+
+    def CheckAccess(self):
+        """Check if the user has read access to the cloud project.
+
+        Returns:
+            True if the user has at least read access to the project.
+            False otherwise.
+
+        Raises:
+            errors.HttpError if other unexpected error happens when
+            accessing the project.
+        """
+        api = self.service.zones().list(project=self._project)
+        retry_http_codes = copy.copy(self.RETRY_HTTP_CODES)
+        retry_http_codes.remove(self.ACCESS_DENIED_CODE)
+        try:
+            self.Execute(api, retry_http_codes=retry_http_codes)
+        except errors.HttpError as e:
+            if e.code == self.ACCESS_DENIED_CODE:
+                return False
+            raise
+        return True
diff --git a/internal/proto/internal_config.proto b/internal/proto/internal_config.proto
index d8b4b3a..f02edda 100755
--- a/internal/proto/internal_config.proto
+++ b/internal/proto/internal_config.proto
@@ -74,4 +74,9 @@
   // It is used during the Oauth2 authentication flow. It is okay to
   // make up a value, e.g. "acloud".
   optional string user_agent = 14;
+  // Error messages to be displayed to user when the user
+  // does not have access to the cloud project.
+  // Key is the name of the project.
+  // Value is the error message to show.
+  map <string, string> no_project_access_msg_map = 15;
 }
diff --git a/public/acloud_main.py b/public/acloud_main.py
index 4469366..4a7a7b1 100755
--- a/public/acloud_main.py
+++ b/public/acloud_main.py
@@ -321,6 +321,9 @@
     cfg = config_mgr.Load()
     cfg.OverrideWithArgs(args)
 
+    # Check access.
+    device_driver.CheckAccess(cfg)
+
     if args.which == CMD_CREATE:
         report = device_driver.CreateAndroidVirtualDevices(
             cfg,
diff --git a/public/config.py b/public/config.py
index ebc2927..a423c01 100755
--- a/public/config.py
+++ b/public/config.py
@@ -105,6 +105,9 @@
             for device, orientation in
             internal_cfg.device_default_orientation_map.iteritems()
         }
+        self.no_project_access_msg_map = {
+            project: msg for project, msg
+            in internal_cfg.no_project_access_msg_map.iteritems()}
         self.min_machine_size = internal_cfg.min_machine_size
         self.disk_image_name = internal_cfg.disk_image_name
         self.disk_image_mime_type = internal_cfg.disk_image_mime_type
diff --git a/public/device_driver.py b/public/device_driver.py
index d0bdf35..0f2acca 100755
--- a/public/device_driver.py
+++ b/public/device_driver.py
@@ -563,3 +563,21 @@
         r.AddError(str(e))
         r.SetStatus(report.Status.FAIL)
     return r
+
+
+def CheckAccess(cfg):
+    """Check if user has access.
+
+    Args:
+         cfg: An AcloudConfig instance.
+    """
+    credentials = auth.CreateCredentials(cfg, ALL_SCOPES)
+    compute_client = android_compute_client.AndroidComputeClient(
+            cfg, credentials)
+    logger.info("Checking if user has access to project %s", cfg.project)
+    if not compute_client.CheckAccess():
+        logger.error("User does not have access to project %s", cfg.project)
+        # Print here so that command line user can see it.
+        print "Looks like you do not have access to %s. " % cfg.project
+        if cfg.project in cfg.no_project_access_msg_map:
+            print cfg.no_project_access_msg_map[cfg.project]