blob: 3c13d91ab623afb7c6089d8e313f0e684cc5eb34 [file] [log] [blame]
//
// Copyright (C) 2022 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.
#include "host/commands/test_gce_driver/scoped_instance.h"
#include <netinet/ip.h>
#include <random>
#include <sstream>
#include <string>
#include <android-base/file.h>
#include <android-base/result.h>
#include "common/libs/fs/shared_buf.h"
using android::base::Error;
using android::base::Result;
namespace cuttlefish {
SshCommand& SshCommand::PrivKey(const std::string& privkey_path) & {
privkey_path_ = privkey_path;
return *this;
}
SshCommand SshCommand::PrivKey(const std::string& privkey_path) && {
privkey_path_ = privkey_path;
return *this;
}
SshCommand& SshCommand::WithoutKnownHosts() & {
without_known_hosts_ = true;
return *this;
}
SshCommand SshCommand::WithoutKnownHosts() && {
without_known_hosts_ = true;
return *this;
}
SshCommand& SshCommand::Username(const std::string& username) & {
username_ = username;
return *this;
}
SshCommand SshCommand::Username(const std::string& username) && {
username_ = username;
return *this;
}
SshCommand& SshCommand::Host(const std::string& host) & {
host_ = host;
return *this;
}
SshCommand SshCommand::Host(const std::string& host) && {
host_ = host;
return *this;
}
SshCommand& SshCommand::RemotePortForward(uint16_t remote, uint16_t local) & {
remote_port_forwards_.push_back({remote, local});
return *this;
}
SshCommand SshCommand::RemotePortForward(uint16_t remote, uint16_t local) && {
remote_port_forwards_.push_back({remote, local});
return *this;
}
SshCommand& SshCommand::RemoteParameter(const std::string& param) & {
parameters_.push_back(param);
return *this;
}
SshCommand SshCommand::RemoteParameter(const std::string& param) && {
parameters_.push_back(param);
return *this;
}
Command SshCommand::Build() const {
Command remote_cmd{"/usr/bin/ssh"};
if (privkey_path_) {
remote_cmd.AddParameter("-i");
remote_cmd.AddParameter(*privkey_path_);
}
if (without_known_hosts_) {
remote_cmd.AddParameter("-o");
remote_cmd.AddParameter("StrictHostKeyChecking=no");
remote_cmd.AddParameter("-o");
remote_cmd.AddParameter("UserKnownHostsFile=/dev/null");
}
for (const auto& fwd : remote_port_forwards_) {
remote_cmd.AddParameter("-R");
remote_cmd.AddParameter(fwd.remote_port, ":127.0.0.1:", fwd.local_port);
}
if (host_) {
remote_cmd.AddParameter(username_ ? *username_ + "@" : "", *host_);
}
for (const auto& param : parameters_) {
remote_cmd.AddParameter(param);
}
return remote_cmd;
}
Result<std::unique_ptr<ScopedGceInstance>> ScopedGceInstance::CreateDefault(
GceApi& gce, const std::string& zone, const std::string& instance_name,
bool internal) {
auto ssh_key = KeyPair::CreateRsa(4096);
if (!ssh_key.ok()) {
return Error() << "Could not create ssh key pair: " << ssh_key.error();
}
auto ssh_pubkey = (*ssh_key)->OpenSshPublicKey();
if (!ssh_pubkey.ok()) {
return Error() << "Could get openssh format key: " << ssh_pubkey.error();
}
auto default_instance_info =
GceInstanceInfo()
.Name(instance_name)
.Zone(zone)
.MachineType("zones/us-west1-a/machineTypes/n1-standard-4")
.AddMetadata("ssh-keys", "vsoc-01:" + *ssh_pubkey)
.AddNetworkInterface(GceNetworkInterface::Default())
.AddDisk(
GceInstanceDisk::EphemeralBootDisk()
.SourceImage(
"projects/cloud-android-releases/global/images/family/"
"cuttlefish-google")
.SizeGb(30))
.AddScope("https://www.googleapis.com/auth/androidbuild.internal")
.AddScope("https://www.googleapis.com/auth/devstorage.read_only")
.AddScope("https://www.googleapis.com/auth/logging.write");
auto creation = gce.Insert(default_instance_info).Future().get();
if (!creation.ok()) {
return Error() << "Failed to create instance: " << creation.error();
}
auto privkey = CF_EXPECT((*ssh_key)->PemPrivateKey());
std::unique_ptr<TemporaryFile> privkey_file(CF_EXPECT(new TemporaryFile()));
auto fd_dup = SharedFD::Dup(privkey_file->fd);
CF_EXPECT(fd_dup->IsOpen());
CF_EXPECT(WriteAll(fd_dup, privkey) == privkey.size());
fd_dup->Close();
std::unique_ptr<ScopedGceInstance> instance(new ScopedGceInstance(
gce, default_instance_info, std::move(privkey_file), internal));
auto created_info = gce.Get(default_instance_info).get();
if (!created_info.ok()) {
return Error() << "Failed to get instance info: " << created_info.error();
}
instance->instance_ = *created_info;
auto ssh_ready = instance->EnforceSshReady();
if (!ssh_ready.ok()) {
return Error() << "Failed to access SSH on instance: " << ssh_ready.error();
}
return instance;
}
Result<void> ScopedGceInstance::EnforceSshReady() {
std::string out;
std::string err;
for (int i = 0; i < 100; i++) {
auto ssh = Ssh();
if (!ssh.ok()) {
return Error() << "Failed to create ssh command: " << ssh.error();
}
ssh->RemoteParameter("ls");
ssh->RemoteParameter("/");
auto command = ssh->Build();
out = "";
err = "";
int ret = RunWithManagedStdio(std::move(command), nullptr, &out, &err);
if (ret == 0) {
return {};
}
}
return Error() << "Failed to ssh to the instance. stdout=\"" << out
<< "\", stderr = \"" << err << "\"";
}
ScopedGceInstance::ScopedGceInstance(GceApi& gce,
const GceInstanceInfo& instance,
std::unique_ptr<TemporaryFile> privkey,
bool use_internal_address)
: gce_(gce),
instance_(instance),
privkey_(std::move(privkey)),
use_internal_address_(use_internal_address) {}
ScopedGceInstance::~ScopedGceInstance() {
auto delete_ins = gce_.Delete(instance_).Future().get();
if (!delete_ins.ok()) {
LOG(ERROR) << "Failed to delete instance: " << delete_ins.error();
}
}
Result<SshCommand> ScopedGceInstance::Ssh() {
const auto& network_interfaces = instance_.NetworkInterfaces();
CF_EXPECT(!network_interfaces.empty());
auto iface = network_interfaces[0];
auto ip = use_internal_address_ ? iface.InternalIp() : iface.ExternalIp();
CF_EXPECT(ip.has_value());
return SshCommand()
.PrivKey(privkey_->path)
.WithoutKnownHosts()
.Username("vsoc-01")
.Host(*ip);
}
} // namespace cuttlefish