Merge "acloud: Define new common_hw_property_map in internal config."
diff --git a/create/avd_spec.py b/create/avd_spec.py
index 5e7ab90..8064f5a 100644
--- a/create/avd_spec.py
+++ b/create/avd_spec.py
@@ -21,14 +21,28 @@
"""
import os
+import re
+import subprocess
from acloud import errors
from acloud.internal import constants
+from acloud.internal.lib import android_build_client
+from acloud.internal.lib import auth
+from acloud.public import config
_BUILD_TARGET = "build_target"
_BUILD_BRANCH = "build_branch"
_BUILD_ID = "build_id"
_ENV_ANDROID_PRODUCT_OUT = "ANDROID_PRODUCT_OUT"
+_BRANCH_RE = re.compile(r"^Manifest branch: (?P<branch>.+)")
+_COMMAND_REPO_IMFO = ["repo", "info"]
+# Default values for build target.
+_BRANCH_PREFIX = "aosp-"
+_TARGET_PREFIX = "aosp_"
+_DEFAULT_BUILD_BITNESS = "x86"
+_DEFAULT_BUILD_TYPE = "userdebug"
+
+ALL_SCOPES = [android_build_client.AndroidBuildClient.SCOPE]
class AVDSpec(object):
@@ -45,11 +59,14 @@
self._autoconnect = None
self._avd_type = None
self._flavor = None
- self._instance_type = None
self._image_source = None
+ self._instance_type = None
self._local_image_path = None
- self._remote_image = None
self._num_of_instances = None
+ self._remote_image = None
+ # Create config instance for android_build_client to query build api.
+ config_mgr = config.AcloudConfigManager(args.config_file)
+ self.cfg = config_mgr.Load()
self._ProcessArgs(args)
@@ -155,20 +172,69 @@
# TODO: We need some logic here to determine if we should infer remote
# build info or not.
self._remote_image = {}
- if args.build_target:
- self._remote_image[_BUILD_TARGET] = args.build_target
- else:
- # TODO: actually figure out what we want here.
- pass
+ self._remote_image[_BUILD_BRANCH] = args.branch
+ if not self._remote_image[_BUILD_BRANCH]:
+ self._remote_image[_BUILD_BRANCH] = self._GetBranchFromRepo()
- if args.branch:
- self._remote_image[_BUILD_BRANCH] = args.branch
- else:
- # TODO: infer from user workspace
- pass
+ self._remote_image[_BUILD_TARGET] = args.build_target
+ if not self._remote_image[_BUILD_TARGET]:
+ self._remote_image[_BUILD_TARGET] = self._GetBuildTarget(args)
- if args.build_id:
- self._remote_image[_BUILD_ID] = args.build_id
- else:
- # TODO: extract this info from Android Build.
- pass
+ self._remote_image[_BUILD_ID] = args.build_id
+ if not self._remote_image[_BUILD_ID]:
+ credentials = auth.CreateCredentials(self.cfg, ALL_SCOPES)
+ build_client = android_build_client.AndroidBuildClient(credentials)
+ self._remote_image[_BUILD_ID] = build_client.GetLKGB(
+ self._remote_image[_BUILD_TARGET],
+ self._remote_image[_BUILD_BRANCH])
+
+ @staticmethod
+ def _GetBranchFromRepo():
+ """Get branch information from command "repo info".
+
+ Returns:
+ branch: String, git branch name. e.g. "aosp-master"
+
+ Raises:
+ errors.GetBranchFromRepoInfoError: Can't get branch from
+ output of "repo info".
+ """
+ repo_output = subprocess.check_output(_COMMAND_REPO_IMFO)
+ for line in repo_output.splitlines():
+ match = _BRANCH_RE.match(line)
+ if match:
+ # Android Build will expect the branch in the following format:
+ # aosp-master
+ return _BRANCH_PREFIX + match.group("branch")
+ raise errors.GetBranchFromRepoInfoError(
+ "No branch mentioned in repo info output: %s" % repo_output
+ )
+
+ @staticmethod
+ def _GetBuildTarget(args):
+ """Infer build target if user doesn't specified target name.
+
+ Target = {REPO_PREFIX}{avd_type}_{bitness}_{flavor}-
+ {DEFAULT_BUILD_TARGET_TYPE}.
+ Example target: aosp_cf_x86_phone-userdebug
+
+ Args:
+ args: Namespace object from argparse.parse_args.
+
+ Returns:
+ build_target: String, name of build target.
+ """
+ return "%s%s_%s_%s-%s" % (
+ _TARGET_PREFIX, constants.AVD_TYPES_MAPPING[args.avd_type],
+ _DEFAULT_BUILD_BITNESS, args.flavor,
+ _DEFAULT_BUILD_TYPE)
+
+ @property
+ def instance_type(self):
+ """Return the instance type."""
+ return self._instance_type
+
+ @property
+ def image_source(self):
+ """Return the image type."""
+ return self._image_source
diff --git a/create/avd_spec_test.py b/create/avd_spec_test.py
index ac979dc..0596a3d 100644
--- a/create/avd_spec_test.py
+++ b/create/avd_spec_test.py
@@ -16,6 +16,7 @@
import unittest
import mock
+from acloud import errors
from acloud.create import avd_spec
from acloud.internal import constants
@@ -28,6 +29,7 @@
"""Initialize new avd_spec.AVDSpec."""
self.args = mock.MagicMock()
self.args.local_image = ""
+ self.args.config_file = ""
self.AvdSpec = avd_spec.AVDSpec(self.args)
def testProcessLocalImageArgs(self):
@@ -56,6 +58,33 @@
self.AvdSpec._ProcessImageArgs(self.args)
self.assertEqual(self.AvdSpec._image_source, constants.IMAGE_SRC_LOCAL)
self.assertEqual(self.AvdSpec._local_image_path, "test_path")
+ self.AvdSpec = avd_spec.AVDSpec(self.args)
+
+ @mock.patch("subprocess.check_output")
+ def testGetBranchFromRepo(self, mock_repo):
+ """Test get branch name from repo info."""
+ mock_repo.return_value = "Manifest branch: master"
+ self.assertEqual(self.AvdSpec._GetBranchFromRepo(), "aosp-master")
+
+ mock_repo.return_value = "Manifest branch:"
+ with self.assertRaises(errors.GetBranchFromRepoInfoError):
+ self.AvdSpec._GetBranchFromRepo()
+
+ def testGetBuildTarget(self):
+ """Test get build target name."""
+ self.AvdSpec._remote_image[avd_spec._BUILD_BRANCH] = "master"
+ self.args.flavor = constants.FLAVOR_IOT
+ self.args.avd_type = constants.TYPE_GCE
+ self.assertEqual(
+ self.AvdSpec._GetBuildTarget(self.args),
+ "aosp_gce_x86_iot-userdebug")
+
+ self.AvdSpec._remote_image[avd_spec._BUILD_BRANCH] = "aosp-master"
+ self.args.flavor = constants.FLAVOR_PHONE
+ self.args.avd_type = constants.TYPE_CF
+ self.assertEqual(
+ self.AvdSpec._GetBuildTarget(self.args),
+ "aosp_cf_x86_phone-userdebug")
if __name__ == "__main__":
diff --git a/create/base_avd_create.py b/create/base_avd_create.py
index 937e622..e3c22c2 100644
--- a/create/base_avd_create.py
+++ b/create/base_avd_create.py
@@ -31,7 +31,4 @@
Args:
avd_spec: AVDSpec object that tells us what we're going to create.
"""
- # TODO: rewrite this function to raise NotImplemented once actual device
- # classes are here.
- print("We will (but not yet) create an AVD with these details: %s." %
- avd_spec)
+ raise NotImplementedError
diff --git a/create/create.py b/create/create.py
index 88c0ac7..3ba27c0 100644
--- a/create/create.py
+++ b/create/create.py
@@ -20,8 +20,36 @@
image artifacts.
"""
+from acloud import errors
from acloud.create import avd_spec
-from acloud.create import base_avd_create
+from acloud.create import local_image_local_instance
+from acloud.create import local_image_remote_instance
+from acloud.internal import constants
+
+
+def GetAvdCreatorClass(instance_type, image_source):
+ """Return the creator class for the specified spec.
+
+ Based on the image source and the instance type, return the proper
+ creator class.
+
+ Args:
+ instance_type: String, the AVD instance type (local or remote).
+ image_source: String, the source of the image (local or remote).
+
+ Returns:
+ An AVD creator class (e.g. LocalImageRemoteInstance).
+ """
+ if (instance_type == constants.INSTANCE_TYPE_REMOTE and
+ image_source == constants.IMAGE_SRC_LOCAL):
+ return local_image_remote_instance.LocalImageRemoteInstance
+ if (instance_type == constants.INSTANCE_TYPE_LOCAL and
+ image_source == constants.IMAGE_SRC_LOCAL):
+ return local_image_local_instance.LocalImageLocalInstance
+
+ raise errors.UnsupportedInstanceImageType(
+ "unsupported creation of instance type: %s, image source: %s" %
+ (instance_type, image_source))
def Run(args):
@@ -31,5 +59,7 @@
args: Namespace object from argparse.parse_args.
"""
spec = avd_spec.AVDSpec(args)
- avd_creator = base_avd_create.BaseAVDCreate()
+ avd_creator_class = GetAvdCreatorClass(spec.instance_type,
+ spec.image_source)
+ avd_creator = avd_creator_class()
avd_creator.Create(spec)
diff --git a/create/local_image_local_instance.py b/create/local_image_local_instance.py
new file mode 100644
index 0000000..5c85185
--- /dev/null
+++ b/create/local_image_local_instance.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+#
+# Copyright 2018 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+r"""LocalImageLocalInstance class.
+
+Create class that is responsible for creating a local instance AVD with a
+local image.
+"""
+
+from acloud.create import base_avd_create
+
+
+class LocalImageLocalInstance(base_avd_create.BaseAVDCreate):
+ """Create class for a local image local instance AVD."""
+
+ # pylint: disable=no-self-use
+ def Create(self, avd_spec):
+ """Create the AVD.
+
+ Args:
+ avd_spec: AVDSpec object that tells us what we're going to create.
+ """
+ print("We will create a local instance AVD with a local image: %s" %
+ avd_spec)
diff --git a/create/local_image_remote_instance.py b/create/local_image_remote_instance.py
new file mode 100644
index 0000000..13973c5
--- /dev/null
+++ b/create/local_image_remote_instance.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+#
+# Copyright 2018 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+r"""LocalImageRemoteInstance class.
+
+Create class that is responsible for creating a remote instance AVD with a
+local image.
+"""
+
+from acloud.create import base_avd_create
+
+class LocalImageRemoteInstance(base_avd_create.BaseAVDCreate):
+ """Create class for a local image remote instance AVD."""
+
+ # pylint: disable=no-self-use
+ def Create(self, avd_spec):
+ """Create the AVD.
+
+ Args:
+ avd_spec: AVDSpec object that tells us what we're going to create.
+ """
+ print("We will create a remote instance AVD with a local image: %s" %
+ avd_spec)
diff --git a/errors.py b/errors.py
index bbe01a9..2a1b2a2 100644
--- a/errors.py
+++ b/errors.py
@@ -50,3 +50,15 @@
class CheckPathError(CreateError):
"""Path does not exist."""
+
+
+class UnsupportedInstanceImageType(CreateError):
+ """Unsupported create action for given instance/image type."""
+
+
+class GetBuildIDError(CreateError):
+ """Can't get build id from Android Build."""
+
+
+class GetBranchFromRepoInfoError(CreateError):
+ """Can't get branch information from output of #'repo info'."""
diff --git a/internal/constants.py b/internal/constants.py
index e8185c5..7d1820b 100755
--- a/internal/constants.py
+++ b/internal/constants.py
@@ -37,6 +37,13 @@
IMAGE_SRC_REMOTE = "remote_image"
IMAGE_SRC_LOCAL = "local_image"
+# AVD types in build target
+AVD_TYPES_MAPPING = {
+ TYPE_GCE: "gce",
+ TYPE_CF: "cf",
+ TYPE_GF: "sdk",
+}
+
# Instance types
INSTANCE_TYPE_REMOTE = "remote"
INSTANCE_TYPE_LOCAL = "local"
diff --git a/internal/lib/android_build_client.py b/internal/lib/android_build_client.py
index 4f7ee57..735c0e4 100644
--- a/internal/lib/android_build_client.py
+++ b/internal/lib/android_build_client.py
@@ -18,10 +18,10 @@
import io
import logging
-import os
import apiclient
+from acloud import errors as root_errors
from acloud.internal.lib import base_cloud_client
from acloud.public import errors
@@ -42,12 +42,17 @@
DEFAULT_ATTEMPT_ID = "0"
DEFAULT_CHUNK_SIZE = 20 * 1024 * 1024
NO_ACCESS_ERROR_PATTERN = "does not have storage.objects.create access"
+ # LKGB variables.
+ BUILD_STATUS_COMPLETE = "complete"
+ BUILD_TYPE_SUBMITTED = "submitted"
+ ONE_RESULT = 1
+ BUILD_SUCCESSFUL = True
# Message constant
COPY_TO_MSG = ("build artifact (target: %s, build_id: %s, "
"artifact: %s, attempt_id: %s) to "
"google storage (bucket: %s, path: %s)")
-
+ # pylint: disable=invalid-name
def DownloadArtifact(self,
build_target,
build_id,
@@ -140,3 +145,35 @@
api = self.service.build().get(buildId=build_id, target=build_target)
build = self.Execute(api)
return build.get("branch", "")
+
+ def GetLKGB(self, build_target, build_branch):
+ """Get latest successful build id.
+
+ From branch and target, we can use api to query latest successful build id.
+ e.g. {u'nextPageToken':..., u'builds': [{u'completionTimestamp':u'1534157869286',
+ ... u'buildId': u'4949805', u'machineName'...}]}
+
+ Args:
+ build_target: String, target name, e.g. "aosp_cf_x86_phone-userdebug"
+ build_branch: String, git branch name, e.g. "aosp-master"
+
+ Returns:
+ A string, string of build id number.
+
+ Raises:
+ errors.CreateError: Can't get build id.
+ """
+ api = self.service.build().list(
+ branch=build_branch,
+ target=build_target,
+ buildAttemptStatus=self.BUILD_STATUS_COMPLETE,
+ buildType=self.BUILD_TYPE_SUBMITTED,
+ maxResults=self.ONE_RESULT,
+ successful=self.BUILD_SUCCESSFUL)
+ build = self.Execute(api)
+ if build:
+ return str(build.get("builds")[0].get("buildId"))
+ raise root_errors.GetBuildIDError(
+ "No available good builds for branch: %s target: %s"
+ % (build_branch, build_target)
+ )
diff --git a/internal/lib/android_build_client_test.py b/internal/lib/android_build_client_test.py
index 6830b3e..5629a68 100644
--- a/internal/lib/android_build_client_test.py
+++ b/internal/lib/android_build_client_test.py
@@ -19,18 +19,20 @@
import io
import time
-import apiclient
+import unittest
import mock
-import unittest
+import apiclient
+
from acloud.internal.lib import android_build_client
from acloud.internal.lib import driver_test_lib
from acloud.public import errors
-
+# pylint: disable=protected-access
class AndroidBuildClientTest(driver_test_lib.BaseDriverTest):
"""Test AndroidBuildClient."""
+ BUILD_BRANCH = "fake_branch"
BUILD_TARGET = "fake_target"
BUILD_ID = 12345
RESOURCE_ID = "avd-system.tar.gz"
@@ -45,6 +47,7 @@
self.client = android_build_client.AndroidBuildClient(mock.MagicMock())
self.client._service = mock.MagicMock()
+ # pylint: disable=no-member
def testDownloadArtifact(self):
"""Test DownloadArtifact."""
# Create mocks.
@@ -141,6 +144,24 @@
buildId=self.BUILD_ID)
self.assertEqual(branch, build_info["branch"])
+ def testGetLKGB(self):
+ """Test GetLKGB."""
+ build_info = {"nextPageToken":"Test", "builds": [{"buildId": "3950000"}]}
+ mock_api = mock.MagicMock()
+ mock_build = mock.MagicMock()
+ mock_build.list.return_value = mock_api
+ self.client._service.build = mock.MagicMock(return_value=mock_build)
+ mock_api.execute = mock.MagicMock(return_value=build_info)
+ build_id = self.client.GetLKGB(self.BUILD_TARGET, self.BUILD_BRANCH)
+ mock_build.list.assert_called_once_with(
+ target=self.BUILD_TARGET,
+ branch=self.BUILD_BRANCH,
+ buildAttemptStatus=self.client.BUILD_STATUS_COMPLETE,
+ buildType=self.client.BUILD_TYPE_SUBMITTED,
+ maxResults=self.client.ONE_RESULT,
+ successful=self.client.BUILD_SUCCESSFUL)
+ self.assertEqual(build_id, build_info.get("builds")[0].get("buildId"))
+
if __name__ == "__main__":
unittest.main()
diff --git a/public/acloud_main.py b/public/acloud_main.py
index 44085c8..e3e8196 100644
--- a/public/acloud_main.py
+++ b/public/acloud_main.py
@@ -267,6 +267,7 @@
return parser.parse_args(args)
+# TODO(b/112803893): Delete this method once the new create method has been completed.
def _TranslateAlias(parsed_args):
"""Translate alias to Launch Control compatible values.
@@ -410,7 +411,8 @@
"""
args = _ParseArgs(argv)
_SetupLogging(args.log_file, args.verbose)
- args = _TranslateAlias(args)
+ # Translation of the branch will happen in AvdSpec(), skip it for now.
+ #args = _TranslateAlias(args)
_VerifyArgs(args)
config_mgr = config.AcloudConfigManager(args.config_file)