• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 // Copyright (C) 2022 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 #include "host/commands/test_gce_driver/scoped_instance.h"
17 
18 #include <netinet/ip.h>
19 
20 #include <random>
21 #include <sstream>
22 #include <string>
23 
24 #include <android-base/file.h>
25 #include <android-base/result.h>
26 
27 #include "common/libs/fs/shared_buf.h"
28 
29 using android::base::Error;
30 using android::base::Result;
31 
32 namespace cuttlefish {
33 
PrivKey(const std::string & privkey_path)34 SshCommand& SshCommand::PrivKey(const std::string& privkey_path) & {
35   privkey_path_ = privkey_path;
36   return *this;
37 }
PrivKey(const std::string & privkey_path)38 SshCommand SshCommand::PrivKey(const std::string& privkey_path) && {
39   privkey_path_ = privkey_path;
40   return *this;
41 }
42 
WithoutKnownHosts()43 SshCommand& SshCommand::WithoutKnownHosts() & {
44   without_known_hosts_ = true;
45   return *this;
46 }
WithoutKnownHosts()47 SshCommand SshCommand::WithoutKnownHosts() && {
48   without_known_hosts_ = true;
49   return *this;
50 }
51 
Username(const std::string & username)52 SshCommand& SshCommand::Username(const std::string& username) & {
53   username_ = username;
54   return *this;
55 }
Username(const std::string & username)56 SshCommand SshCommand::Username(const std::string& username) && {
57   username_ = username;
58   return *this;
59 }
60 
Host(const std::string & host)61 SshCommand& SshCommand::Host(const std::string& host) & {
62   host_ = host;
63   return *this;
64 }
Host(const std::string & host)65 SshCommand SshCommand::Host(const std::string& host) && {
66   host_ = host;
67   return *this;
68 }
69 
RemotePortForward(uint16_t remote,uint16_t local)70 SshCommand& SshCommand::RemotePortForward(uint16_t remote, uint16_t local) & {
71   remote_port_forwards_.push_back({remote, local});
72   return *this;
73 }
RemotePortForward(uint16_t remote,uint16_t local)74 SshCommand SshCommand::RemotePortForward(uint16_t remote, uint16_t local) && {
75   remote_port_forwards_.push_back({remote, local});
76   return *this;
77 }
78 
RemoteParameter(const std::string & param)79 SshCommand& SshCommand::RemoteParameter(const std::string& param) & {
80   parameters_.push_back(param);
81   return *this;
82 }
RemoteParameter(const std::string & param)83 SshCommand SshCommand::RemoteParameter(const std::string& param) && {
84   parameters_.push_back(param);
85   return *this;
86 }
87 
Build() const88 Command SshCommand::Build() const {
89   Command remote_cmd{"/usr/bin/ssh"};
90   if (privkey_path_) {
91     remote_cmd.AddParameter("-i");
92     remote_cmd.AddParameter(*privkey_path_);
93   }
94   if (without_known_hosts_) {
95     remote_cmd.AddParameter("-o");
96     remote_cmd.AddParameter("StrictHostKeyChecking=no");
97     remote_cmd.AddParameter("-o");
98     remote_cmd.AddParameter("UserKnownHostsFile=/dev/null");
99   }
100   for (const auto& fwd : remote_port_forwards_) {
101     remote_cmd.AddParameter("-R");
102     remote_cmd.AddParameter(fwd.remote_port, ":127.0.0.1:", fwd.local_port);
103   }
104   if (host_) {
105     remote_cmd.AddParameter(username_ ? *username_ + "@" : "", *host_);
106   }
107   for (const auto& param : parameters_) {
108     remote_cmd.AddParameter(param);
109   }
110   return remote_cmd;
111 }
112 
CreateDefault(GceApi & gce,const std::string & zone,const std::string & instance_name,bool internal)113 Result<std::unique_ptr<ScopedGceInstance>> ScopedGceInstance::CreateDefault(
114     GceApi& gce, const std::string& zone, const std::string& instance_name,
115     bool internal) {
116   auto ssh_key = KeyPair::CreateRsa(4096);
117   if (!ssh_key.ok()) {
118     return Error() << "Could not create ssh key pair: " << ssh_key.error();
119   }
120 
121   auto ssh_pubkey = (*ssh_key)->OpenSshPublicKey();
122   if (!ssh_pubkey.ok()) {
123     return Error() << "Could get openssh format key: " << ssh_pubkey.error();
124   }
125 
126   auto default_instance_info =
127       GceInstanceInfo()
128           .Name(instance_name)
129           .Zone(zone)
130           .MachineType("zones/us-west1-a/machineTypes/n1-standard-4")
131           .AddMetadata("ssh-keys", "vsoc-01:" + *ssh_pubkey)
132           .AddNetworkInterface(GceNetworkInterface::Default())
133           .AddDisk(
134               GceInstanceDisk::EphemeralBootDisk()
135                   .SourceImage(
136                       "projects/cloud-android-releases/global/images/family/"
137                       "cuttlefish-google")
138                   .SizeGb(30))
139           .AddScope("https://www.googleapis.com/auth/androidbuild.internal")
140           .AddScope("https://www.googleapis.com/auth/devstorage.read_only")
141           .AddScope("https://www.googleapis.com/auth/logging.write");
142 
143   auto creation = gce.Insert(default_instance_info).Future().get();
144   if (!creation.ok()) {
145     return Error() << "Failed to create instance: " << creation.error();
146   }
147 
148   auto privkey = CF_EXPECT((*ssh_key)->PemPrivateKey());
149   std::unique_ptr<TemporaryFile> privkey_file(CF_EXPECT(new TemporaryFile()));
150   auto fd_dup = SharedFD::Dup(privkey_file->fd);
151   CF_EXPECT(fd_dup->IsOpen());
152   CF_EXPECT(WriteAll(fd_dup, privkey) == privkey.size());
153   fd_dup->Close();
154 
155   std::unique_ptr<ScopedGceInstance> instance(new ScopedGceInstance(
156       gce, default_instance_info, std::move(privkey_file), internal));
157 
158   auto created_info = gce.Get(default_instance_info).get();
159   if (!created_info.ok()) {
160     return Error() << "Failed to get instance info: " << created_info.error();
161   }
162   instance->instance_ = *created_info;
163 
164   auto ssh_ready = instance->EnforceSshReady();
165   if (!ssh_ready.ok()) {
166     return Error() << "Failed to access SSH on instance: " << ssh_ready.error();
167   }
168   return instance;
169 }
170 
EnforceSshReady()171 Result<void> ScopedGceInstance::EnforceSshReady() {
172   std::string out;
173   std::string err;
174   for (int i = 0; i < 100; i++) {
175     auto ssh = Ssh();
176     if (!ssh.ok()) {
177       return Error() << "Failed to create ssh command: " << ssh.error();
178     }
179 
180     ssh->RemoteParameter("ls");
181     ssh->RemoteParameter("/");
182     auto command = ssh->Build();
183 
184     out = "";
185     err = "";
186     int ret = RunWithManagedStdio(std::move(command), nullptr, &out, &err);
187     if (ret == 0) {
188       return {};
189     }
190   }
191 
192   return Error() << "Failed to ssh to the instance. stdout=\"" << out
193                  << "\", stderr = \"" << err << "\"";
194 }
195 
ScopedGceInstance(GceApi & gce,const GceInstanceInfo & instance,std::unique_ptr<TemporaryFile> privkey,bool use_internal_address)196 ScopedGceInstance::ScopedGceInstance(GceApi& gce,
197                                      const GceInstanceInfo& instance,
198                                      std::unique_ptr<TemporaryFile> privkey,
199                                      bool use_internal_address)
200     : gce_(gce),
201       instance_(instance),
202       privkey_(std::move(privkey)),
203       use_internal_address_(use_internal_address) {}
204 
~ScopedGceInstance()205 ScopedGceInstance::~ScopedGceInstance() {
206   auto delete_ins = gce_.Delete(instance_).Future().get();
207   if (!delete_ins.ok()) {
208     LOG(ERROR) << "Failed to delete instance: " << delete_ins.error();
209   }
210 }
211 
Ssh()212 Result<SshCommand> ScopedGceInstance::Ssh() {
213   const auto& network_interfaces = instance_.NetworkInterfaces();
214   CF_EXPECT(!network_interfaces.empty());
215   auto iface = network_interfaces[0];
216   auto ip = use_internal_address_ ? iface.InternalIp() : iface.ExternalIp();
217   CF_EXPECT(ip.has_value());
218   return SshCommand()
219       .PrivKey(privkey_->path)
220       .WithoutKnownHosts()
221       .Username("vsoc-01")
222       .Host(*ip);
223 }
224 
225 }  // namespace cuttlefish
226