1 /* 2 * Copyright (C) 2019 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 package com.android.tradefed.device.cloud; 17 18 import com.android.tradefed.device.TestDeviceOptions; 19 import com.android.tradefed.util.CommandResult; 20 import com.android.tradefed.util.CommandStatus; 21 import com.android.tradefed.util.IRunUtil; 22 23 import java.util.Arrays; 24 import java.util.List; 25 26 /** Utility to create another user in Cuttlefish VM. New user will allow to run a second device. */ 27 public class MultiUserSetupUtil { 28 29 /** Files that must be copied between users to avoid conflicting ownership */ 30 private static final List<String> FILE_TO_BE_COPIED = 31 Arrays.asList("android-info.txt", "*.img"); 32 33 /** Files that can simply be shared between the different users */ 34 private static final List<String> FILE_TO_BE_LINKED = Arrays.asList("bin", "config", "lib64"); 35 36 /** Setup a new remote user on an existing Cuttlefish VM. */ prepareRemoteUser( String username, GceAvdInfo remoteInstance, TestDeviceOptions options, IRunUtil runUtil, long timeoutMs)37 public static CommandResult prepareRemoteUser( 38 String username, 39 GceAvdInfo remoteInstance, 40 TestDeviceOptions options, 41 IRunUtil runUtil, 42 long timeoutMs) { 43 // First create the user 44 String createUserCommand = 45 "sudo useradd " + username + " -G sudo,kvm,cvdnetwork -m -s /bin/bash -p '*'"; 46 CommandResult createUserRes = 47 RemoteSshUtil.remoteSshCommandExec( 48 remoteInstance, options, runUtil, timeoutMs, createUserCommand.split(" ")); 49 if (!CommandStatus.SUCCESS.equals(createUserRes.getStatus())) { 50 return createUserRes; 51 } 52 return null; 53 } 54 55 /** Create the 'cvd-XX' user on the remote device if missing. */ addExtraCvdUser( int userId, GceAvdInfo remoteInstance, TestDeviceOptions options, IRunUtil runUtil, long timeoutMs)56 public static CommandResult addExtraCvdUser( 57 int userId, 58 GceAvdInfo remoteInstance, 59 TestDeviceOptions options, 60 IRunUtil runUtil, 61 long timeoutMs) { 62 String useridString = getUserNumber(userId); 63 String username = String.format("cvd-%s", useridString); 64 String createUserCommand = 65 "sudo useradd " + username + " -G plugdev -m -s /bin/bash -p '*'"; 66 CommandResult createUserRes = 67 RemoteSshUtil.remoteSshCommandExec( 68 remoteInstance, options, runUtil, timeoutMs, createUserCommand.split(" ")); 69 if (!CommandStatus.SUCCESS.equals(createUserRes.getStatus())) { 70 if (createUserRes 71 .getStderr() 72 .contains(String.format("user '%s' already exists", username))) { 73 return null; 74 } 75 return createUserRes; 76 } 77 return null; 78 } 79 80 /** Setup the tuntap interface required to start the Android devices if they are missing. */ setupNetworkInterface( int userId, GceAvdInfo remoteInstance, TestDeviceOptions options, IRunUtil runUtil, long timeoutMs)81 public static CommandResult setupNetworkInterface( 82 int userId, 83 GceAvdInfo remoteInstance, 84 TestDeviceOptions options, 85 IRunUtil runUtil, 86 long timeoutMs) { 87 if (userId < 9) { 88 // TODO: use 'tuntap show' to check if interface exists already or not. 89 return null; 90 } 91 String useridString = getUserNumber(userId); 92 String mtap = String.format("cvd-mtap-%s", useridString); 93 String wtap = String.format("cvd-wtap-%s", useridString); 94 String addNetworkInterface = "sudo ip tuntap add dev %s mode tap group cvdnetwork"; 95 String mtapCommand = String.format(addNetworkInterface, mtap); 96 CommandResult addNetworkInterfaceRes = 97 RemoteSshUtil.remoteSshCommandExec( 98 remoteInstance, options, runUtil, timeoutMs, mtapCommand.split(" ")); 99 if (!CommandStatus.SUCCESS.equals(addNetworkInterfaceRes.getStatus())) { 100 return addNetworkInterfaceRes; 101 } 102 103 String wtapCommand = String.format(addNetworkInterface, wtap); 104 addNetworkInterfaceRes = 105 RemoteSshUtil.remoteSshCommandExec( 106 remoteInstance, options, runUtil, timeoutMs, wtapCommand.split(" ")); 107 if (!CommandStatus.SUCCESS.equals(addNetworkInterfaceRes.getStatus())) { 108 return addNetworkInterfaceRes; 109 } 110 return null; 111 } 112 113 /** Setup a new remote user on an existing Cuttlefish VM. */ prepareRemoteHomeDir( String mainRootUser, String username, GceAvdInfo remoteInstance, TestDeviceOptions options, IRunUtil runUtil, long timeoutMs)114 public static CommandResult prepareRemoteHomeDir( 115 String mainRootUser, 116 String username, 117 GceAvdInfo remoteInstance, 118 TestDeviceOptions options, 119 IRunUtil runUtil, 120 long timeoutMs) { 121 StringBuilder copyCommandBuilder = new StringBuilder("sudo cp "); 122 for (String file : FILE_TO_BE_COPIED) { 123 copyCommandBuilder.append(" /home/" + mainRootUser + "/" + file); 124 } 125 copyCommandBuilder.append(" /home/" + username + "/"); 126 CommandResult cpRes = 127 RemoteSshUtil.remoteSshCommandExec( 128 remoteInstance, 129 options, 130 runUtil, 131 timeoutMs, 132 copyCommandBuilder.toString().split(" ")); 133 if (!CommandStatus.SUCCESS.equals(cpRes.getStatus())) { 134 return cpRes; 135 } 136 // Own the copied files 137 String chownUser = getChownCommand(username); 138 CommandResult chownRes = 139 RemoteSshUtil.remoteSshCommandExec( 140 remoteInstance, options, runUtil, timeoutMs, chownUser); 141 if (!CommandStatus.SUCCESS.equals(chownRes.getStatus())) { 142 return chownRes; 143 } 144 // Link files that can be shared between users 145 for (String file : FILE_TO_BE_LINKED) { 146 String copyDevice = 147 "sudo ln -s /home/" + mainRootUser + "/" + file + " /home/" + username + "/"; 148 CommandResult copyRes = 149 RemoteSshUtil.remoteSshCommandExec( 150 remoteInstance, options, runUtil, timeoutMs, copyDevice.split(" ")); 151 if (!CommandStatus.SUCCESS.equals(copyRes.getStatus())) { 152 return copyRes; 153 } 154 } 155 return null; 156 } 157 158 /** Gets the command for a user to own the main directory. */ getChownCommand(String username)159 public static String getChownCommand(String username) { 160 return "find /home/" + username + " | sudo xargs chown " + username; 161 } 162 163 /** Returns the user id string version that follow the remote device notation. */ getUserNumber(int userId)164 public static String getUserNumber(int userId) { 165 return (userId > 9) ? Integer.toString(userId) : "0" + Integer.toString(userId); 166 } 167 } 168