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