• 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/gce_api.h"
17 
18 #include <uuid.h>
19 
20 #include <sstream>
21 #include <string>
22 
23 #include <android-base/logging.h>
24 #include <android-base/strings.h>
25 
26 #include "host/libs/web/credential_source.h"
27 #include "host/libs/web/curl_wrapper.h"
28 
29 using android::base::Error;
30 using android::base::Result;
31 
32 namespace cuttlefish {
33 
OptStringMember(const Json::Value & jn,const std::string & name)34 std::optional<std::string> OptStringMember(const Json::Value& jn,
35                                            const std::string& name) {
36   if (!jn.isMember(name) || jn[name].type() != Json::ValueType::stringValue) {
37     return {};
38   }
39   return jn[name].asString();
40 }
41 
OptObjMember(const Json::Value & jn,const std::string & name)42 const Json::Value* OptObjMember(const Json::Value& jn,
43                                 const std::string& name) {
44   if (!jn.isMember(name) || jn[name].type() != Json::ValueType::objectValue) {
45     return nullptr;
46   }
47   return &(jn[name]);
48 }
49 
OptArrayMember(const Json::Value & jn,const std::string & name)50 const Json::Value* OptArrayMember(const Json::Value& jn,
51                                   const std::string& name) {
52   if (!jn.isMember(name) || jn[name].type() != Json::ValueType::arrayValue) {
53     return nullptr;
54   }
55   return &(jn[name]);
56 }
57 
EnsureObjMember(Json::Value & jn,const std::string & name)58 Json::Value& EnsureObjMember(Json::Value& jn, const std::string& name) {
59   if (!jn.isMember(name) || jn[name].type() != Json::ValueType::objectValue) {
60     jn[name] = Json::Value(Json::ValueType::objectValue);
61   }
62   return jn[name];
63 }
64 
EnsureArrayMember(Json::Value & jn,const std::string & name)65 Json::Value& EnsureArrayMember(Json::Value& jn, const std::string& name) {
66   if (!jn.isMember(name) || jn[name].type() != Json::ValueType::arrayValue) {
67     jn[name] = Json::Value(Json::ValueType::arrayValue);
68   }
69   return jn[name];
70 }
71 
GceInstanceDisk(const Json::Value & json)72 GceInstanceDisk::GceInstanceDisk(const Json::Value& json) : data_(json){};
73 
EphemeralBootDisk()74 GceInstanceDisk GceInstanceDisk::EphemeralBootDisk() {
75   Json::Value initial_json(Json::ValueType::objectValue);
76   initial_json["type"] = "PERSISTENT";
77   initial_json["boot"] = true;
78   initial_json["mode"] = "READ_WRITE";
79   initial_json["autoDelete"] = true;
80   return GceInstanceDisk(initial_json);
81 }
82 
83 constexpr char kGceDiskInitParams[] = "initializeParams";
84 constexpr char kGceDiskName[] = "diskName";
Name() const85 std::optional<std::string> GceInstanceDisk::Name() const {
86   const auto& init_params = OptObjMember(data_, kGceDiskInitParams);
87   if (!init_params) {
88     return {};
89   }
90   return OptStringMember(*init_params, kGceDiskName);
91 }
Name(const std::string & source)92 GceInstanceDisk& GceInstanceDisk::Name(const std::string& source) & {
93   EnsureObjMember(data_, kGceDiskInitParams)[kGceDiskName] = source;
94   return *this;
95 }
Name(const std::string & source)96 GceInstanceDisk GceInstanceDisk::Name(const std::string& source) && {
97   EnsureObjMember(data_, kGceDiskInitParams)[kGceDiskName] = source;
98   return *this;
99 }
100 
101 constexpr char kGceDiskSourceImage[] = "sourceImage";
SourceImage() const102 std::optional<std::string> GceInstanceDisk::SourceImage() const {
103   const auto& init_params = OptObjMember(data_, kGceDiskInitParams);
104   if (!init_params) {
105     return {};
106   }
107   return OptStringMember(*init_params, kGceDiskSourceImage);
108 }
SourceImage(const std::string & source)109 GceInstanceDisk& GceInstanceDisk::SourceImage(const std::string& source) & {
110   EnsureObjMember(data_, kGceDiskInitParams)[kGceDiskSourceImage] = source;
111   return *this;
112 }
SourceImage(const std::string & source)113 GceInstanceDisk GceInstanceDisk::SourceImage(const std::string& source) && {
114   EnsureObjMember(data_, kGceDiskInitParams)[kGceDiskSourceImage] = source;
115   return *this;
116 }
117 
118 constexpr char kGceDiskSizeGb[] = "diskSizeGb";
SizeGb(uint64_t size)119 GceInstanceDisk& GceInstanceDisk::SizeGb(uint64_t size) & {
120   EnsureObjMember(data_, kGceDiskInitParams)[kGceDiskSizeGb] = size;
121   return *this;
122 }
SizeGb(uint64_t size)123 GceInstanceDisk GceInstanceDisk::SizeGb(uint64_t size) && {
124   EnsureObjMember(data_, kGceDiskInitParams)[kGceDiskSizeGb] = size;
125   return *this;
126 }
127 
AsJson() const128 const Json::Value& GceInstanceDisk::AsJson() const { return data_; }
129 
GceNetworkInterface(const Json::Value & data)130 GceNetworkInterface::GceNetworkInterface(const Json::Value& data)
131     : data_(data) {}
132 
133 constexpr char kGceNetworkAccessConfigs[] = "accessConfigs";
Default()134 GceNetworkInterface GceNetworkInterface::Default() {
135   Json::Value json{Json::ValueType::objectValue};
136   json["network"] = "global/networks/default";
137   Json::Value accessConfig{Json::ValueType::objectValue};
138   accessConfig["type"] = "ONE_TO_ONE_NAT";
139   accessConfig["name"] = "External NAT";
140   EnsureArrayMember(json, kGceNetworkAccessConfigs).append(accessConfig);
141   return GceNetworkInterface(json);
142 }
143 
144 constexpr char kGceNetworkExternalIp[] = "natIP";
ExternalIp() const145 std::optional<std::string> GceNetworkInterface::ExternalIp() const {
146   auto accessConfigs = OptArrayMember(data_, kGceNetworkAccessConfigs);
147   if (!accessConfigs || accessConfigs->size() < 1) {
148     return {};
149   }
150   if ((*accessConfigs)[0].type() != Json::ValueType::objectValue) {
151     return {};
152   }
153   return OptStringMember((*accessConfigs)[0], kGceNetworkExternalIp);
154 }
155 
156 constexpr char kGceNetworkInternalIp[] = "networkIP";
InternalIp() const157 std::optional<std::string> GceNetworkInterface::InternalIp() const {
158   return OptStringMember(data_, kGceNetworkInternalIp);
159 }
160 
AsJson() const161 const Json::Value& GceNetworkInterface::AsJson() const { return data_; }
162 
GceInstanceInfo(const Json::Value & json)163 GceInstanceInfo::GceInstanceInfo(const Json::Value& json) : data_(json) {}
164 
165 constexpr char kGceZone[] = "zone";
Zone() const166 std::optional<std::string> GceInstanceInfo::Zone() const {
167   return OptStringMember(data_, kGceZone);
168 }
Zone(const std::string & zone)169 GceInstanceInfo& GceInstanceInfo::Zone(const std::string& zone) & {
170   data_[kGceZone] = zone;
171   return *this;
172 }
Zone(const std::string & zone)173 GceInstanceInfo GceInstanceInfo::Zone(const std::string& zone) && {
174   data_[kGceZone] = zone;
175   return *this;
176 }
177 
178 constexpr char kGceName[] = "name";
Name() const179 std::optional<std::string> GceInstanceInfo::Name() const {
180   return OptStringMember(data_, kGceName);
181 }
Name(const std::string & name)182 GceInstanceInfo& GceInstanceInfo::Name(const std::string& name) & {
183   data_[kGceName] = name;
184   return *this;
185 }
Name(const std::string & name)186 GceInstanceInfo GceInstanceInfo::Name(const std::string& name) && {
187   data_[kGceName] = name;
188   return *this;
189 }
190 
191 constexpr char kGceMachineType[] = "machineType";
MachineType() const192 std::optional<std::string> GceInstanceInfo::MachineType() const {
193   return OptStringMember(data_, kGceMachineType);
194 }
MachineType(const std::string & type)195 GceInstanceInfo& GceInstanceInfo::MachineType(const std::string& type) & {
196   data_[kGceMachineType] = type;
197   return *this;
198 }
MachineType(const std::string & type)199 GceInstanceInfo GceInstanceInfo::MachineType(const std::string& type) && {
200   data_[kGceMachineType] = type;
201   return *this;
202 }
203 
204 constexpr char kGceDisks[] = "disks";
AddDisk(const GceInstanceDisk & disk)205 GceInstanceInfo& GceInstanceInfo::AddDisk(const GceInstanceDisk& disk) & {
206   EnsureArrayMember(data_, kGceDisks).append(disk.AsJson());
207   return *this;
208 }
AddDisk(const GceInstanceDisk & disk)209 GceInstanceInfo GceInstanceInfo::AddDisk(const GceInstanceDisk& disk) && {
210   EnsureArrayMember(data_, kGceDisks).append(disk.AsJson());
211   return *this;
212 }
213 
214 constexpr char kGceNetworkInterfaces[] = "networkInterfaces";
AddNetworkInterface(const GceNetworkInterface & net)215 GceInstanceInfo& GceInstanceInfo::AddNetworkInterface(
216     const GceNetworkInterface& net) & {
217   EnsureArrayMember(data_, kGceNetworkInterfaces).append(net.AsJson());
218   return *this;
219 }
AddNetworkInterface(const GceNetworkInterface & net)220 GceInstanceInfo GceInstanceInfo::AddNetworkInterface(
221     const GceNetworkInterface& net) && {
222   EnsureArrayMember(data_, kGceNetworkInterfaces).append(net.AsJson());
223   return *this;
224 }
NetworkInterfaces() const225 std::vector<GceNetworkInterface> GceInstanceInfo::NetworkInterfaces() const {
226   auto jsonNetworkInterfaces = OptArrayMember(data_, kGceNetworkInterfaces);
227   if (!jsonNetworkInterfaces) {
228     return {};
229   }
230   std::vector<GceNetworkInterface> interfaces;
231   for (const Json::Value& jsonNetworkInterface : *jsonNetworkInterfaces) {
232     interfaces.push_back(GceNetworkInterface(jsonNetworkInterface));
233   }
234   return interfaces;
235 }
236 
237 constexpr char kGceMetadata[] = "metadata";
238 constexpr char kGceMetadataItems[] = "items";
239 constexpr char kGceMetadataKey[] = "key";
240 constexpr char kGceMetadataValue[] = "value";
AddMetadata(const std::string & key,const std::string & value)241 GceInstanceInfo& GceInstanceInfo::AddMetadata(const std::string& key,
242                                               const std::string& value) & {
243   Json::Value item{Json::ValueType::objectValue};
244   item[kGceMetadataKey] = key;
245   item[kGceMetadataValue] = value;
246   auto& metadata = EnsureObjMember(data_, kGceMetadata);
247   EnsureArrayMember(metadata, kGceMetadataItems).append(item);
248   return *this;
249 }
AddMetadata(const std::string & key,const std::string & value)250 GceInstanceInfo GceInstanceInfo::AddMetadata(const std::string& key,
251                                              const std::string& value) && {
252   Json::Value item{Json::ValueType::objectValue};
253   item[kGceMetadataKey] = key;
254   item[kGceMetadataValue] = value;
255   auto& metadata = EnsureObjMember(data_, kGceMetadata);
256   EnsureArrayMember(metadata, kGceMetadataItems).append(item);
257   return *this;
258 }
259 
260 constexpr char kGceServiceAccounts[] = "serviceAccounts";
261 constexpr char kGceScopes[] = "scopes";
AddScope(const std::string & scope)262 GceInstanceInfo& GceInstanceInfo::AddScope(const std::string& scope) & {
263   auto& serviceAccounts = EnsureArrayMember(data_, kGceServiceAccounts);
264   if (serviceAccounts.size() == 0) {
265     serviceAccounts.append(Json::Value(Json::ValueType::objectValue));
266   }
267   serviceAccounts[0]["email"] = "default";
268   auto& scopes = EnsureArrayMember(serviceAccounts[0], kGceScopes);
269   scopes.append(scope);
270   return *this;
271 }
AddScope(const std::string & scope)272 GceInstanceInfo GceInstanceInfo::AddScope(const std::string& scope) && {
273   auto& serviceAccounts = EnsureArrayMember(data_, kGceServiceAccounts);
274   if (serviceAccounts.size() == 0) {
275     serviceAccounts.append(Json::Value(Json::ValueType::objectValue));
276   }
277   serviceAccounts[0]["email"] = "default";
278   auto& scopes = EnsureArrayMember(serviceAccounts[0], kGceScopes);
279   scopes.append(scope);
280   return *this;
281 }
282 
AsJson() const283 const Json::Value& GceInstanceInfo::AsJson() const { return data_; }
284 
GceApi(CurlWrapper & curl,CredentialSource & credentials,const std::string & project)285 GceApi::GceApi(CurlWrapper& curl, CredentialSource& credentials,
286                const std::string& project)
287     : curl_(curl), credentials_(credentials), project_(project) {}
288 
Headers()289 std::vector<std::string> GceApi::Headers() {
290   return {
291       "Authorization:Bearer " + credentials_.Credential(),
292       "Content-Type: application/json",
293   };
294 }
295 
296 class GceApi::Operation::Impl {
297  public:
Impl(GceApi & gce_api,std::function<Result<Json::Value> ()> initial_request)298   Impl(GceApi& gce_api, std::function<Result<Json::Value>()> initial_request)
299       : gce_api_(gce_api), initial_request_(std::move(initial_request)) {
300     operation_future_ = std::async([this]() { return Run(); });
301   }
302 
Run()303   Result<bool> Run() {
304     auto initial_response = initial_request_();
305     if (!initial_response.ok()) {
306       return Error() << "Initial request failed: " << initial_response.error();
307     }
308 
309     auto url = OptStringMember(*initial_response, "selfLink");
310     if (!url) {
311       return Error() << "Operation " << *initial_response
312                      << " was missing `selfLink` field.";
313     }
314     url = *url + "/wait";
315     running_ = true;
316 
317     while (running_) {
318       auto response =
319           gce_api_.curl_.PostToJson(*url, std::string{""}, gce_api_.Headers());
320       const auto& json = response.data;
321       Json::Value errors;
322       if (auto j_error = OptObjMember(json, "error"); j_error) {
323         if (auto j_errors = OptArrayMember(*j_error, "errors"); j_errors) {
324           errors = j_errors->size() > 0 ? *j_errors : Json::Value();
325         }
326       }
327       Json::Value warnings;
328       if (auto j_warnings = OptArrayMember(json, "warnings"); j_warnings) {
329         warnings = j_warnings->size() > 0 ? *j_warnings : Json::Value();
330       }
331       LOG(DEBUG) << "Requested operation status at \"" << *url
332                  << "\", received " << json;
333       if (!response.HttpSuccess() || errors != Json::Value()) {
334         return Error() << "Error accessing \"" << *url
335                        << "\". Errors: " << errors
336                        << ", Warnings: " << warnings;
337       }
338       if (!json.isMember("status") ||
339           json["status"].type() != Json::ValueType::stringValue) {
340         return Error() << json << " \"status\" field invalid";
341       }
342       if (json["status"] == "DONE") {
343         return true;
344       }
345     }
346     return false;
347   }
348 
349  private:
350   GceApi& gce_api_;
351   std::function<Result<Json::Value>()> initial_request_;
352   bool running_;
353   std::future<Result<bool>> operation_future_;
354   friend class GceApi::Operation;
355 };
356 
Operation(std::unique_ptr<GceApi::Operation::Impl> impl)357 GceApi::Operation::Operation(std::unique_ptr<GceApi::Operation::Impl> impl)
358     : impl_(std::move(impl)) {}
359 
360 GceApi::Operation::~Operation() = default;
361 
StopWaiting()362 void GceApi::Operation::StopWaiting() { impl_->running_ = false; }
363 
Future()364 std::future<Result<bool>>& GceApi::Operation::Future() {
365   return impl_->operation_future_;
366 }
367 
RandomUuid()368 static std::string RandomUuid() {
369   uuid_t uuid;
370   uuid_generate_random(uuid);
371   std::string uuid_str = "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx";
372   uuid_unparse(uuid, uuid_str.data());
373   return uuid_str;
374 }
375 
376 // GCE gives back full URLs for zones, but it only wants the last part in
377 // requests
SanitizeZone(const std::string & zone)378 static std::string SanitizeZone(const std::string& zone) {
379   auto last_slash = zone.rfind("/");
380   if (last_slash == std::string::npos) {
381     return zone;
382   }
383   return zone.substr(last_slash + 1);
384 }
385 
Get(const GceInstanceInfo & instance)386 std::future<Result<GceInstanceInfo>> GceApi::Get(
387     const GceInstanceInfo& instance) {
388   auto name = instance.Name();
389   if (!name) {
390     auto task = [json = instance.AsJson()]() -> Result<GceInstanceInfo> {
391       return Error() << "Missing a name for \"" << json << "\"";
392     };
393     return std::async(std::launch::deferred, task);
394   }
395   auto zone = instance.Zone();
396   if (!zone) {
397     auto task = [json = instance.AsJson()]() -> Result<GceInstanceInfo> {
398       return Error() << "Missing a zone for \"" << json << "\"";
399     };
400     return std::async(std::launch::deferred, task);
401   }
402   return Get(*zone, *name);
403 }
404 
Get(const std::string & zone,const std::string & name)405 std::future<Result<GceInstanceInfo>> GceApi::Get(const std::string& zone,
406                                                  const std::string& name) {
407   std::stringstream url;
408   url << "https://compute.googleapis.com/compute/v1";
409   url << "/projects/" << curl_.UrlEscape(project_);
410   url << "/zones/" << curl_.UrlEscape(SanitizeZone(zone));
411   url << "/instances/" << curl_.UrlEscape(name);
412   auto task = [this, url = url.str()]() -> Result<GceInstanceInfo> {
413     auto response = curl_.DownloadToJson(url, Headers());
414     if (!response.HttpSuccess()) {
415       return Error() << "Failed to get instance info, received "
416                      << response.data << " with code " << response.http_code;
417     }
418     return GceInstanceInfo(response.data);
419   };
420   return std::async(task);
421 }
422 
Insert(const Json::Value & request)423 GceApi::Operation GceApi::Insert(const Json::Value& request) {
424   if (!request.isMember("zone") ||
425       request["zone"].type() != Json::ValueType::stringValue) {
426     auto task = [request]() -> Result<Json::Value> {
427       return Error() << "Missing a zone for \"" << request << "\"";
428     };
429     return Operation(
430         std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
431   }
432   auto zone = request["zone"].asString();
433   Json::Value requestNoZone = request;
434   requestNoZone.removeMember("zone");
435   std::stringstream url;
436   url << "https://compute.googleapis.com/compute/v1";
437   url << "/projects/" << curl_.UrlEscape(project_);
438   url << "/zones/" << curl_.UrlEscape(SanitizeZone(zone));
439   url << "/instances";
440   url << "?requestId=" << RandomUuid();  // Avoid duplication on request retry
441   auto task = [this, requestNoZone, url = url.str()]() -> Result<Json::Value> {
442     auto response = curl_.PostToJson(url, requestNoZone, Headers());
443     if (!response.HttpSuccess()) {
444       return Error() << "Failed to create instance: " << response.data
445                      << ". Sent request " << requestNoZone;
446     }
447     return response.data;
448   };
449   return Operation(
450       std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
451 }
452 
Insert(const GceInstanceInfo & request)453 GceApi::Operation GceApi::Insert(const GceInstanceInfo& request) {
454   return Insert(request.AsJson());
455 }
456 
Reset(const std::string & zone,const std::string & name)457 GceApi::Operation GceApi::Reset(const std::string& zone,
458                                 const std::string& name) {
459   std::stringstream url;
460   url << "https://compute.googleapis.com/compute/v1";
461   url << "/projects/" << curl_.UrlEscape(project_);
462   url << "/zones/" << curl_.UrlEscape(SanitizeZone(zone));
463   url << "/instances/" << curl_.UrlEscape(name);
464   url << "/reset";
465   url << "?requestId=" << RandomUuid();  // Avoid duplication on request retry
466   auto task = [this, url = url.str()]() -> Result<Json::Value> {
467     auto response = curl_.PostToJson(url, Json::Value(), Headers());
468     if (!response.HttpSuccess()) {
469       return Error() << "Failed to create instance: " << response.data;
470     }
471     return response.data;
472   };
473   return Operation(
474       std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
475 }
476 
Reset(const GceInstanceInfo & instance)477 GceApi::Operation GceApi::Reset(const GceInstanceInfo& instance) {
478   auto name = instance.Name();
479   if (!name) {
480     auto task = [json = instance.AsJson()]() -> Result<Json::Value> {
481       return Error() << "Missing a name for \"" << json << "\"";
482     };
483     return Operation(
484         std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
485   }
486   auto zone = instance.Zone();
487   if (!zone) {
488     auto task = [json = instance.AsJson()]() -> Result<Json::Value> {
489       return Error() << "Missing a zone for \"" << json << "\"";
490     };
491     return Operation(
492         std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
493   }
494   return Reset(*zone, *name);
495 }
496 
Delete(const std::string & zone,const std::string & name)497 GceApi::Operation GceApi::Delete(const std::string& zone,
498                                  const std::string& name) {
499   std::stringstream url;
500   url << "https://compute.googleapis.com/compute/v1";
501   url << "/projects/" << curl_.UrlEscape(project_);
502   url << "/zones/" << curl_.UrlEscape(SanitizeZone(zone));
503   url << "/instances/" << curl_.UrlEscape(name);
504   url << "?requestId=" << RandomUuid();  // Avoid duplication on request retry
505   auto task = [this, url = url.str()]() -> Result<Json::Value> {
506     auto response = curl_.DeleteToJson(url, Headers());
507     if (!response.HttpSuccess()) {
508       return Error() << "Failed to delete instance: " << response.data;
509     }
510     return response.data;
511   };
512   return Operation(
513       std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
514 }
515 
Delete(const GceInstanceInfo & instance)516 GceApi::Operation GceApi::Delete(const GceInstanceInfo& instance) {
517   auto name = instance.Name();
518   if (!name) {
519     auto task = [json = instance.AsJson()]() -> Result<Json::Value> {
520       return Error() << "Missing a name for \"" << json << "\"";
521     };
522     return Operation(
523         std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
524   }
525   auto zone = instance.Zone();
526   if (!zone) {
527     auto task = [json = instance.AsJson()]() -> Result<Json::Value> {
528       return Error() << "Missing a zone for \"" << json << "\"";
529     };
530     return Operation(
531         std::unique_ptr<Operation::Impl>(new Operation::Impl(*this, task)));
532   }
533   return Delete(*zone, *name);
534 }
535 
536 }  // namespace cuttlefish
537