1#!/usr/bin/env python
2#
3# Copyright 2018 - The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16r"""host setup runner
17
18A setup sub task runner to support setting up the local host for AVD local
19instance.
20"""
21
22from __future__ import print_function
23
24import getpass
25import logging
26import os
27import shutil
28import sys
29import tempfile
30
31from acloud.internal import constants
32from acloud.internal.lib import utils
33from acloud.setup import base_task_runner
34from acloud.setup import setup_common
35
36
37logger = logging.getLogger(__name__)
38
39# Packages "devscripts" and "equivs" are required for "mk-build-deps".
40_AVD_REQUIRED_PKGS = [
41    "devscripts", "equivs", "libvirt-clients", "libvirt-daemon-system"]
42_BASE_REQUIRED_PKGS = ["ssvnc", "lzop"]
43_CUTTLEFISH_COMMOM_PKG = "cuttlefish-common"
44_CF_COMMOM_FOLDER = "cf-common"
45_LIST_OF_MODULES = ["kvm_intel", "kvm"]
46_UPDATE_APT_GET_CMD = "sudo apt-get update"
47_INSTALL_CUTTLEFISH_COMMOM_CMD = [
48    "git clone https://github.com/google/android-cuttlefish.git {git_folder}",
49    "cd {git_folder}",
50    "yes | sudo mk-build-deps -i -r -B",
51    "dpkg-buildpackage -uc -us",
52    "sudo apt-get install -y -f ../cuttlefish-common_*_amd64.deb"]
53
54
55class BasePkgInstaller(base_task_runner.BaseTaskRunner):
56    """Subtask base runner class for installing packages."""
57
58    # List of packages for child classes to override.
59    PACKAGES = []
60
61    def ShouldRun(self):
62        """Check if required packages are all installed.
63
64        Returns:
65            Boolean, True if required packages are not installed.
66        """
67        if not utils.IsSupportedPlatform():
68            return False
69
70        # Any required package is not installed or not up-to-date will need to
71        # run installation task.
72        for pkg_name in self.PACKAGES:
73            if not setup_common.PackageInstalled(pkg_name):
74                return True
75
76        return False
77
78    def _Run(self):
79        """Install specified packages."""
80        cmd = "\n".join(
81            [setup_common.PKG_INSTALL_CMD % pkg
82             for pkg in self.PACKAGES
83             if not setup_common.PackageInstalled(pkg)])
84
85        if not utils.GetUserAnswerYes("\nStart to install package(s):\n%s"
86                                      "\nPress 'y' to continue or anything "
87                                      "else to do it myself and run acloud "
88                                      "again[y/N]: " % cmd):
89            sys.exit(constants.EXIT_BY_USER)
90
91        setup_common.CheckCmdOutput(_UPDATE_APT_GET_CMD, shell=True)
92        for pkg in self.PACKAGES:
93            setup_common.InstallPackage(pkg)
94
95        logger.info("All package(s) installed now.")
96
97
98class AvdPkgInstaller(BasePkgInstaller):
99    """Subtask runner class for installing packages for local instances."""
100
101    WELCOME_MESSAGE_TITLE = ("Install required packages for host setup for "
102                             "local instances")
103    WELCOME_MESSAGE = ("This step will walk you through the required packages "
104                       "installation for running Android cuttlefish devices "
105                       "on your host.")
106    PACKAGES = _AVD_REQUIRED_PKGS
107
108
109class HostBasePkgInstaller(BasePkgInstaller):
110    """Subtask runner class for installing base host packages."""
111
112    WELCOME_MESSAGE_TITLE = "Install base packages on the host"
113    WELCOME_MESSAGE = ("This step will walk you through the base packages "
114                       "installation for your host.")
115    PACKAGES = _BASE_REQUIRED_PKGS
116
117
118class CuttlefishCommonPkgInstaller(base_task_runner.BaseTaskRunner):
119    """Subtask base runner class for installing cuttlefish-common."""
120
121    WELCOME_MESSAGE_TITLE = "Install cuttlefish-common packages on the host"
122    WELCOME_MESSAGE = ("This step will walk you through the cuttlefish-common "
123                       "packages installation for your host.")
124
125    def ShouldRun(self):
126        """Check if cuttlefish-common package is installed.
127
128        Returns:
129            Boolean, True if cuttlefish-common is not installed.
130        """
131        if not utils.IsSupportedPlatform():
132            return False
133
134        # Any required package is not installed or not up-to-date will need to
135        # run installation task.
136        if not setup_common.PackageInstalled(_CUTTLEFISH_COMMOM_PKG):
137            return True
138        return False
139
140    def _Run(self):
141        """Install cuttlefilsh-common packages."""
142        cf_common_path = os.path.join(tempfile.mkdtemp(), _CF_COMMOM_FOLDER)
143        logger.debug("cuttlefish-common path: %s", cf_common_path)
144        cmd = "\n".join(sub_cmd.format(git_folder=cf_common_path)
145                        for sub_cmd in _INSTALL_CUTTLEFISH_COMMOM_CMD)
146
147        if not utils.GetUserAnswerYes("\nStart to install cuttlefish-common :\n%s"
148                                      "\nPress 'y' to continue or anything "
149                                      "else to do it myself and run acloud "
150                                      "again[y/N]: " % cmd):
151            sys.exit(constants.EXIT_BY_USER)
152        try:
153            setup_common.CheckCmdOutput(cmd, shell=True)
154        finally:
155            shutil.rmtree(os.path.dirname(cf_common_path))
156        logger.info("Cuttlefish-common package installed now.")
157
158
159class CuttlefishHostSetup(base_task_runner.BaseTaskRunner):
160    """Subtask class that setup host for cuttlefish."""
161
162    WELCOME_MESSAGE_TITLE = "Host Enviornment Setup"
163    WELCOME_MESSAGE = (
164        "This step will help you to setup enviornment for running Android "
165        "cuttlefish devices on your host. That includes adding user to kvm "
166        "related groups and checking required linux modules."
167    )
168
169    def ShouldRun(self):
170        """Check host user groups and modules.
171
172         Returns:
173             Boolean: False if user is in all required groups and all modules
174                      are reloaded.
175         """
176        if not utils.IsSupportedPlatform():
177            return False
178
179        return not (utils.CheckUserInGroups(constants.LIST_CF_USER_GROUPS)
180                    and self._CheckLoadedModules(_LIST_OF_MODULES))
181
182    @staticmethod
183    def _CheckLoadedModules(module_list):
184        """Check if the modules are all in use.
185
186        Args:
187            module_list: The list of module name.
188        Returns:
189            True if all modules are in use.
190        """
191        logger.info("Checking if modules are loaded: %s", module_list)
192        lsmod_output = setup_common.CheckCmdOutput("lsmod", print_cmd=False)
193        current_modules = [r.split()[0] for r in lsmod_output.splitlines()]
194        all_modules_present = True
195        for module in module_list:
196            if module not in current_modules:
197                logger.info("missing module: %s", module)
198                all_modules_present = False
199        return all_modules_present
200
201    def _Run(self):
202        """Setup host environment for local cuttlefish instance support."""
203        # TODO: provide --uid args to let user use prefered username
204        username = getpass.getuser()
205        setup_cmds = [
206            "sudo rmmod kvm_intel",
207            "sudo rmmod kvm",
208            "sudo modprobe kvm",
209            "sudo modprobe kvm_intel"]
210        for group in constants.LIST_CF_USER_GROUPS:
211            setup_cmds.append("sudo usermod -aG %s % s" % (group, username))
212
213        print("Below commands will be run:")
214        for setup_cmd in setup_cmds:
215            print(setup_cmd)
216
217        if self._ConfirmContinue():
218            for setup_cmd in setup_cmds:
219                setup_common.CheckCmdOutput(setup_cmd, shell=True)
220            print("Host environment setup has done!")
221
222    @staticmethod
223    def _ConfirmContinue():
224        """Ask user if they want to continue.
225
226        Returns:
227            True if user answer yes.
228        """
229        answer_client = utils.InteractWithQuestion(
230            "\nPress 'y' to continue or anything else to do it myself[y/N]: ",
231            utils.TextColors.WARNING)
232        return answer_client in constants.USER_ANSWER_YES
233