1#
2# Copyright 2018 - The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#     http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16
17import imp
18import os
19import sys
20import time
21
22from fabric.api import env
23from fabric.api import local
24from fabric.api import run
25from fabric.api import settings
26from fabric.api import sudo
27from fabric.contrib.files import contains
28from fabric.contrib.files import exists
29from fabric.context_managers import cd
30
31_PIP_REQUIREMENTS_PATHS = [
32    "test/vts/script/pip_requirements.txt",
33    "test/framework/harnesses/host_controller/script/pip_requirements.txt"
34]
35
36# Path to the file that contains the abs path to the deployed vtslab pakcage.
37_VTSLAB_PACKAGE_PATH_FILENAME = ".vtslab_package_path"
38
39# Zone filter list for GCE instances.
40_DEFAULT_GCE_ZONE_LIST = [
41    "us-east1-b",
42    "asia-northeast1-a",
43]
44
45
46def SetPassword(password):
47    """Sets password for hosts to access through ssh and to run sudo commands
48
49    usage: $ fab SetPassword:<password for hosts>
50
51    Args:
52        password: string, password for hosts.
53    """
54    env.password = password
55
56
57def GetHosts(hosts_file_path, gce_instance_name=None, account=None):
58    """Configures env.hosts to a given list of hosts.
59
60    usage: $ fab GetHosts:<path to a source file contains hosts info>
61
62    Args:
63        hosts_file_path: string, path to a python file passed from command file
64                         input.
65        gce_instance_name: string, GCE instance name.
66        account: string, account name used for the ssh connection.
67    """
68    if hosts_file_path.endswith(".py"):
69        hosts_module = imp.load_source('hosts_module', hosts_file_path)
70        env.hosts = hosts_module.EmitHostList()
71    else:
72        if not gce_instance_name or not account:
73            print(
74                "Please specify gce_instance_name and account using -H option. "
75                "Example: -H <Google_Cloud_project>,<GCE_instance>,<account>")
76            sys.exit(0)
77        env.key_filename = "~/.ssh/google_compute_engine"
78        gce_list_out = local(
79            "gcloud compute instances list --project=%s --filter=\"zone:(%s)\""
80            % (hosts_file_path, " ".join(_DEFAULT_GCE_ZONE_LIST)),
81            capture=True)
82        for line in gce_list_out.split("\n")[1:]:
83            if line.startswith(gce_instance_name):
84                env.hosts.append("%s@%s" % (account, line.strip().split()[-2]))
85
86
87def SetupIptables(ip_address_file_path):
88    """Configures iptables setting for all hosts listed.
89
90    usage: $ fab SetupIptables:<path to a source file contains ip addresses of
91             certified machines>
92
93    Args:
94        ip_address_file_path: string, path to a python file passed from command
95                              file input.
96    """
97    ip_addresses_module = imp.load_source('ip_addresses_module',
98                                          ip_address_file_path)
99    ip_address_list = ip_addresses_module.EmitIPAddressList()
100
101    sudo("apt-get install -y iptables-persistent")
102    sudo("iptables -P INPUT ACCEPT")
103    sudo("iptables -P FORWARD ACCEPT")
104    sudo("iptables -F")
105
106    for ip_address in ip_address_list:
107        sudo(
108            "iptables -A INPUT -p tcp -s %s --dport 22 -j ACCEPT" % ip_address)
109
110    sudo("iptables -P INPUT DROP")
111    sudo("iptables -P FORWARD DROP")
112    sudo("iptables -A INPUT -p icmp -j ACCEPT")
113    sudo("netfilter-persistent save")
114    sudo("netfilter-persistent reload")
115
116
117def SetupSudoers():
118    """Append sudo rules for vtslab user.
119
120    usage: $ fab SetupSudoers
121    """
122    if not contains("/etc/sudoers", "vtslab", use_sudo=True):
123        sudo("echo '' | sudo tee -a /etc/sudoers")
124        sudo("echo '# Let vtslab account have all authorization' | "
125             "sudo tee -a /etc/sudoers")
126        sudo("echo 'vtslab  ALL=(ALL:ALL) ALL' | sudo tee -a /etc/sudoers")
127
128
129def SetupUSBPermission():
130    """Sets up the USB permission for adb and fastboot.
131
132    usage: $ fab SetupUSBPermission
133    """
134    sudo("curl --create-dirs -L -o /etc/udev/rules.d/51-android.rules -O -L "
135         "https://raw.githubusercontent.com/snowdream/51-android/master/"
136         "51-android.rules")
137    sudo("chmod a+r /etc/udev/rules.d/51-android.rules")
138    sudo("service udev restart")
139
140
141def SetupADBVendorKeysEnvVar():
142    """Appends scripts for ADB_VENDOR_KEYS path setup.
143
144    In setup step, this function looks into .bashrc file for this script, and
145    if there is not then appends the below scripts to .bashrc.
146    Later when shell env created (through ssh or screen instance creation time),
147    .bashrc file will look for _VTSLAB_PACKAGE_PATH_FILENAME and use the
148    contents of the file to set ADB_VENDOR_KEYS.
149
150    usage: $ fab SetupADBVendorKeysEnvVar
151    """
152    if not contains("~/.bashrc", _VTSLAB_PACKAGE_PATH_FILENAME):
153        run("echo '' >> ~/.bashrc", )
154        run("echo '# Set $ADB_VENDOR_KEYS as paths to adb private key files "
155            "within the vtslab-package' >> ~/.bashrc")
156        run("echo 'if [ -f ~/%s ]; then' >> ~/.bashrc" %
157            _VTSLAB_PACKAGE_PATH_FILENAME)
158        run("echo '  export ADB_VENDOR_KEYS=$(find $(cat ~/%s)/android-vtslab/"
159            "testcases/DATA/ak -name \".*.ak\" | tr \"\\n\" \":\")' "
160            ">> ~/.bashrc" % _VTSLAB_PACKAGE_PATH_FILENAME)
161        run("echo 'fi' >> ~/.bashrc")
162
163
164def _CheckADBVendorKeysEnvVar(vtslab_package_file_name=""):
165    """Checks if there is a change in ADB_VENDOR_KEYS env variable.
166
167    if there is, then the adbd needs to be restarted in the screen context
168    before running the HC.
169
170    Args:
171        vtslab_package_file_name: string, the HC package file name that is about
172                                  to be deployed.
173
174    Returns:
175        True if the list of the adb vendor key files have changed,
176        False otherwise.
177    """
178    former_key_set = set()
179    current_key_set = set()
180    set_keyfile_set = lambda set, path_list: map(set.add, map(os.path.basename,
181                                                              path_list))
182    vtslab_package_path_filepath = "~/%s" % _VTSLAB_PACKAGE_PATH_FILENAME
183
184    if exists(vtslab_package_path_filepath):
185        former_HC_package_path = run("cat %s" % vtslab_package_path_filepath)
186        former_HC_package_adbkey_path = os.path.join(
187            former_HC_package_path, "android-vtslab/testcases/DATA/ak")
188        if exists(former_HC_package_adbkey_path):
189            adb_vendor_keys_list = run("find %s -name \".*.ak\"" %
190                                       former_HC_package_adbkey_path).split()
191            set_keyfile_set(former_key_set, adb_vendor_keys_list)
192
193    if exists("~/run/%s.dir/android-vtslab/testcases/DATA/ak" %
194              vtslab_package_file_name):
195        adb_vendor_keys_list = run(
196            "find ~/run/%s.dir/android-vtslab/testcases/DATA/ak -name \".*.ak\""
197            % vtslab_package_file_name).split()
198        set_keyfile_set(current_key_set, adb_vendor_keys_list)
199
200    return former_key_set != current_key_set
201
202
203def SetupPackages(ip_address_file_path=None):
204    """Sets up the execution environment for vts `run` command.
205
206    Need to temporarily open the ports for apt-get and pip commands.
207
208    usage: $ fab SetupPackages
209
210    Args:
211        ip_address_file_path: string, path to a python file passed from command
212                              file input. Will be passed to SetupIptables().
213    """
214    sudo("iptables -P INPUT ACCEPT")
215
216    # todo : replace "kr.ubuntu." to "ubuntu" in /etc/apt/sources.list
217    sudo("apt-get upgrade -y")
218    sudo("apt-get update -y")
219    sudo("apt-get install -y git-core gnupg flex bison gperf build-essential "
220         "zip curl zlib1g-dev gcc-multilib g++-multilib x11proto-core-dev "
221         "libx11-dev lib32z-dev ccache libgl1-mesa-dev libxml2-utils xsltproc "
222         "unzip liblz4-tool udev screen")
223
224    sudo("apt-get install -y android-tools-adb")
225    sudo("usermod -aG plugdev $LOGNAME")
226
227    SetupUSBPermission()
228
229    sudo("apt-get update")
230    sudo("apt-get install -y python2.7")
231    sudo("apt-get install -y python-pip")
232    run("pip install --upgrade pip")
233    sudo("apt-get install -y python-virtualenv")
234
235    sudo("apt-get install -y python-dev python-protobuf protobuf-compiler "
236         "python-setuptools")
237
238    for req_path in _PIP_REQUIREMENTS_PATHS:
239        full_path = os.path.join(os.environ["ANDROID_BUILD_TOP"], req_path)
240        pip_requirement_list = []
241        try:
242            requirements_fd = open(full_path, "r")
243            lines = requirements_fd.readlines()
244            for line in lines:
245                req = line.rstrip()
246                if req != "" and not req.startswith("#"):
247                    pip_requirement_list.append(req)
248        except IOError as e:
249            print("%s: %s" % (e.strerror, full_path))
250            return
251        sudo("pip install %s" % " ".join(pip_requirement_list))
252
253    sudo("pip install --upgrade protobuf")
254
255    lsb_result = run("lsb_release -c -s")
256    sudo("echo \"deb http://packages.cloud.google.com/apt cloud-sdk-%s "
257         "main\" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list" %
258         lsb_result)
259    sudo("curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | "
260         "sudo apt-key add -")
261    sudo("apt-get update && sudo apt-get install -y google-cloud-sdk")
262    sudo("apt-get install -y google-cloud-sdk-app-engine-java "
263         "google-cloud-sdk-app-engine-python kubectl")
264
265    sudo("apt-get install -y m4 bison")
266
267    if ip_address_file_path is not None:
268        SetupIptables(ip_address_file_path)
269
270    SetupADBVendorKeysEnvVar()
271
272
273def DeployVtslab(vtslab_package_gcs_url=None):
274    """Deploys vtslab package.
275
276    Fetches and deploy vtslab by going through the processes described below
277    1. Send the "exit --wait_for_jobs=True" command to all detached screen.
278       And let the screen to terminate itself.
279    2. Create a new screen instance that downloads and runs the new HC,
280       give password and device command to the HC without actually attaching it
281
282    usage: $ fab DeployVtslab -p <password> -H hosts.py -f <gs://vtslab-release/...>
283
284    Args:
285        vtslab_package_gcs_url: string, URL to a certain vtslab package file.
286    """
287    if not vtslab_package_gcs_url:
288        print("Please specify vtslab package file URL using -f option.")
289        return
290    elif not vtslab_package_gcs_url.startswith("gs://vtslab-release/"):
291        print("Please spcify a valid URL for the vtslab package.")
292        return
293    else:
294        vti = "vtslab-schedule-" + vtslab_package_gcs_url[len(
295            "gs://vtslab-release/"):].split("/")[0] + ".appspot.com"
296    with settings(warn_only=True):
297        screen_list_result = run("screen -list")
298    lines = screen_list_result.split("\n")
299    for line in lines:
300        if "(Detached)" in line:
301            screen_name = line.split("\t")[1]
302            print(screen_name)
303            with settings(warn_only=True):
304                run("screen -S %s -X stuff \"exit --wait_for_jobs=True\"" %
305                    screen_name)
306                run("screen -S %s -X stuff \"^M\"" % screen_name)
307                run("screen -S %s -X stuff \"exit\"" % screen_name)
308                run("screen -S %s -X stuff \"^M\"" % screen_name)
309
310    vtslab_package_file_name = os.path.basename(vtslab_package_gcs_url)
311    run("mkdir -p ~/run/%s.dir/" % vtslab_package_file_name)
312    with cd("~/run/%s.dir" % vtslab_package_file_name):
313        run("gsutil cp %s ./" % vtslab_package_gcs_url)
314        run("unzip -o %s" % vtslab_package_file_name)
315        adb_vendor_keys_changed = _CheckADBVendorKeysEnvVar(
316            vtslab_package_file_name)
317        run("pwd > ~/%s" % _VTSLAB_PACKAGE_PATH_FILENAME)
318
319    with cd("~/run/%s.dir/android-vtslab/tools" % vtslab_package_file_name):
320        new_screen_name = run("cat ../testcases/version.txt")
321
322    with cd("~/run/%s.dir/android-vtslab/tools" % vtslab_package_file_name):
323        run("./make_screen %s ; sleep 1" % new_screen_name)
324
325    if adb_vendor_keys_changed:
326        reset_adbd = ""
327        while reset_adbd.lower() not in ["y", "n"]:
328            if reset_adbd:
329                print("Please type 'y' or 'n'")
330            reset_adbd = raw_input(
331                "Reset adb server daemon on host %s (y/n)? " % env.host)
332        if reset_adbd.lower() == "y":
333            run("screen -S %s -X stuff \"adb kill-server^M\"" %
334                new_screen_name)
335            run("screen -S %s -X stuff \"adb start-server^M\"" %
336                new_screen_name)
337    run("screen -S %s -X stuff \"./run --vti=%s\"" % (new_screen_name, vti))
338    run("screen -S %s -X stuff \"^M\"" % new_screen_name)
339    time.sleep(5)
340    run("screen -S %s -X stuff \"password\"" % new_screen_name)
341    run("screen -S %s -X stuff \"^M\"" % new_screen_name)
342    run("screen -S %s -X stuff \"%s\"" % (new_screen_name, env.password))
343    run("screen -S %s -X stuff \"^M\"" % new_screen_name)
344    run("screen -S %s -X stuff \"device --lease=True\"" % new_screen_name)
345    run("screen -S %s -X stuff \"^M\"" % new_screen_name)
346
347    with cd("~/run/%s.dir" % vtslab_package_file_name):
348        run("rm %s" % vtslab_package_file_name)
349
350
351def DeployGCE(vtslab_package_gcs_url=None):
352    """Deploys a vtslab package to GCE nodes.
353
354    Fetches and deploy vtslab on monitor-hc by doing;
355    1. Download android-vtslab-<>.zip from GCS using the given URL and upzip it.
356    2. Send the Ctrl-c key input to all detached screen, then cursor-up
357       key input and enter key input, making the screen to execute
358       the last run command.
359
360    usage: $ fab DeployVtslab -p <password> -H <Google Cloud Platform project name> -f <gs://vtslab-release/...>
361
362    Args:
363        vtslab_package_gcs_url: string, URL to a certain vtslab package file.
364    """
365    if not vtslab_package_gcs_url:
366        print("Please specify vtslab package file URL using -f option.")
367        return
368    elif not vtslab_package_gcs_url.startswith("gs://"):
369        print("Please spcify a valid URL for the vtslab package.")
370        return
371
372    vtslab_package_file_name = os.path.basename(vtslab_package_gcs_url)
373    with cd("~/run"):
374        run("gsutil cp %s ./" % vtslab_package_gcs_url)
375        run("unzip -o %s" % vtslab_package_file_name)
376        run("rm %s" % vtslab_package_file_name)
377
378    with settings(warn_only=True):
379        screen_list_result = run("screen -list")
380    lines = screen_list_result.split("\n")
381    for line in lines:
382        if "(Detached)" in line:
383            screen_name = line.split("\t")[1]
384            run("screen -S %s -X stuff \"^C\"" % screen_name)
385            run("screen -S %s -X stuff \"\033[A\"" % screen_name)
386            run("screen -S %s -X stuff \"^M\"" % screen_name)
387