• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.util.avd;
17 
18 import com.android.tradefed.log.LogUtil.CLog;
19 
20 import com.google.gson.Gson;
21 
22 import java.io.IOException;
23 import java.net.URI;
24 import java.net.http.HttpClient;
25 import java.net.http.HttpRequest;
26 import java.net.http.HttpResponse;
27 import java.net.http.HttpResponse.BodyHandlers;
28 import java.nio.file.Path;
29 import java.util.List;
30 
31 /**
32  * Java implementation of Cuttlefish Host Orchestator API.
33  *
34  * <p>- Endpoints:
35  * https://github.com/google/android-cuttlefish/blob/main/frontend/src/host_orchestrator/orchestrator/controller.go#L56-L102
36  * - Objects:
37  * https://github.com/google/android-cuttlefish/blob/main/frontend/src/host_orchestrator/api/v1/messages.go
38  */
39 public class HostOrchestratorClient {
40 
41     // https://github.com/google/android-cuttlefish/blob/main/frontend/src/host_orchestrator/api/v1/messages.go#L104
42     public static final class Operation {
43         public String name;
44         public boolean done;
45     }
46 
47     public static final class Cvd {
48         public String group;
49         public String name;
50         public String status;
51     }
52 
53     public static final class ListCvdsResponse {
54         public List<Cvd> cvds;
55     }
56 
buildListCvdsRequest(String baseURL)57     public static HttpRequest buildListCvdsRequest(String baseURL) {
58         return HttpRequest.newBuilder().uri(URI.create(String.format("%s/cvds", baseURL))).build();
59     }
60 
61     // https://github.com/google/android-cuttlefish/blob/fff7e3487c924435e6f6120345edf1dddb49d50b/frontend/src/host_orchestrator/orchestrator/controller.go#L78
buildGetOperationRequest(String baseURL, String name)62     public static HttpRequest buildGetOperationRequest(String baseURL, String name) {
63         return HttpRequest.newBuilder()
64                 .uri(URI.create(String.format("%s/operations/%s", baseURL, name)))
65                 .build();
66     }
67 
68     // https://github.com/google/android-cuttlefish/blob/fff7e3487c924435e6f6120345edf1dddb49d50b/frontend/src/host_orchestrator/orchestrator/controller.go#L82
buildGetOperationResultRequest(String baseURL, String name)69     public static HttpRequest buildGetOperationResultRequest(String baseURL, String name) {
70         return HttpRequest.newBuilder()
71                 .uri(URI.create(String.format("%s/operations/%s/result", baseURL, name)))
72                 .build();
73     }
74 
75     // https://github.com/google/android-cuttlefish/blob/fff7e3487c924435e6f6120345edf1dddb49d50b/frontend/src/host_orchestrator/orchestrator/controller.go#L69
buildCreateBugreportRequest(String baseURL, String group)76     public static HttpRequest buildCreateBugreportRequest(String baseURL, String group) {
77         return HttpRequest.newBuilder()
78                 .uri(URI.create(String.format("%s/cvds/%s/:bugreport", baseURL, group)))
79                 .POST(java.net.http.HttpRequest.BodyPublishers.noBody())
80                 .build();
81     }
82 
83     // https://github.com/google/android-cuttlefish/blob/fff7e3487c924435e6f6120345edf1dddb49d50b/frontend/src/host_orchestrator/orchestrator/controller.go#L75
buildPowerwashRequest(String baseURL, String group, String name)84     public static HttpRequest buildPowerwashRequest(String baseURL, String group, String name) {
85         return HttpRequest.newBuilder()
86                 .uri(URI.create(String.format("%s/cvds/%s/%s/:powerwash", baseURL, group, name)))
87                 .POST(java.net.http.HttpRequest.BodyPublishers.noBody())
88                 .build();
89     }
90 
91     // https://github.com/google/android-cuttlefish/blob/fff7e3487c924435e6f6120345edf1dddb49d50b/frontend/src/host_orchestrator/orchestrator/controller.go#L71
buildRemoveInstanceRequest( String baseURL, String group, String name)92     public static HttpRequest buildRemoveInstanceRequest(
93             String baseURL, String group, String name) {
94         return HttpRequest.newBuilder()
95                 .uri(URI.create(String.format("%s/cvds/%s/%s", baseURL, group, name)))
96                 .DELETE()
97                 .build();
98     }
99 
100     public static interface IHoHttpClient {
send(HttpRequest request)101         HttpResponse<String> send(HttpRequest request)
102                 throws IOException, InterruptedException, ErrorResponseException;
103 
send(HttpRequest request, Path dst)104         HttpResponse<Path> send(HttpRequest request, Path dst)
105                 throws IOException, InterruptedException, ErrorResponseException;
106     }
107 
108     public static final class HoHttpClient implements IHoHttpClient {
109         private final HttpClient mClient;
110 
HoHttpClient()111         public HoHttpClient() {
112             mClient = HttpClient.newBuilder().followRedirects(HttpClient.Redirect.NORMAL).build();
113         }
114 
115         @Override
send(HttpRequest request)116         public HttpResponse<String> send(HttpRequest request)
117                 throws IOException, InterruptedException, ErrorResponseException {
118             return mClient.send(request, BodyHandlers.ofString());
119         }
120 
121         @Override
send(HttpRequest request, Path dst)122         public HttpResponse<Path> send(HttpRequest request, Path dst)
123                 throws IOException, InterruptedException, ErrorResponseException {
124             return mClient.send(request, BodyHandlers.ofFile(dst));
125         }
126     }
127 
128     public static final class ErrorResponseException extends Exception {
129         private final int mStatusCode;
130         private final String mBody;
131 
ErrorResponseException(int statusCode, String body)132         public ErrorResponseException(int statusCode, String body) {
133             super(
134                     String.format(
135                             "error response with status code: %d, response body: %s",
136                             statusCode, body));
137             mStatusCode = statusCode;
138             mBody = body;
139         }
140 
getStatusCode()141         public int getStatusCode() {
142             return mStatusCode;
143         }
144 
getBody()145         public String getBody() {
146             return mBody;
147         }
148     }
149 
sendRequest( IHoHttpClient client, HttpRequest request, Class<T> responseClass)150     public static <T> T sendRequest(
151         IHoHttpClient client, HttpRequest request, Class<T> responseClass)
152             throws IOException, InterruptedException, ErrorResponseException {
153         HttpResponse<String> res = client.send(request);
154         if (res.statusCode() != 200) {
155             throw new ErrorResponseException(res.statusCode(), res.body());
156         }
157         return new Gson().fromJson(res.body(), responseClass);
158     }
159 
saveToFile(IHoHttpClient client, HttpRequest request, Path dst)160     public static void saveToFile(IHoHttpClient client, HttpRequest request, Path dst)
161             throws IOException, InterruptedException, ErrorResponseException {
162         HttpResponse<Path> res = client.send(request, dst);
163         if (res.statusCode() != 200) {
164             throw new ErrorResponseException(res.statusCode(), "");
165         }
166         CLog.i("Response body for \"%s\" successfully saved to \"%s\"", request.uri(), dst);
167     }
168 }
169