summaryrefslogtreecommitdiffstats
path: root/controller-server
diff options
context:
space:
mode:
Diffstat (limited to 'controller-server')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java19
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/statuspage/StatusPageClient.java9
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/statuspage/StatusPageProxyHandler.java28
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java39
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java64
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-reference-2.json5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application2.json78
7 files changed, 185 insertions, 57 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
index 4b09e78ede2..2652b49f86f 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
@@ -345,13 +345,15 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
private void toSlime(Cursor object, Application application, HttpRequest request) {
object.setString("application", application.id().application().value());
object.setString("instance", application.id().instance().value());
+
// Currently deploying change
if (application.change().isPresent()) {
- Cursor deployingObject = object.setObject("deploying");
- application.change().platform().ifPresent(v -> deployingObject.setString("version", v.toString()));
- application.change().application()
- .filter(v -> v != ApplicationVersion.unknown)
- .ifPresent(v -> toSlime(v, deployingObject.setObject("revision")));
+ toSlime(object.setObject("deploying"), application.change());
+ }
+
+ // Outstanding change
+ if (application.outstandingChange().isPresent()) {
+ toSlime(object.setObject("outstandingChange"), application.outstandingChange());
}
// Jobs sorted according to deployment spec
@@ -454,6 +456,13 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
return new SlimeJsonResponse(slime);
}
+ private void toSlime(Cursor object, Change change) {
+ change.platform().ifPresent(version -> object.setString("version", version.toString()));
+ change.application()
+ .filter(version -> !version.isUnknown())
+ .ifPresent(version -> toSlime(version, object.setObject("revision")));
+ }
+
private void toSlime(Cursor response, DeploymentId deploymentId, Deployment deployment, HttpRequest request) {
Cursor serviceUrlArray = response.setArray("serviceUrls");
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/statuspage/StatusPageClient.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/statuspage/StatusPageClient.java
index ecb2f611171..93213172048 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/statuspage/StatusPageClient.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/statuspage/StatusPageClient.java
@@ -1,6 +1,8 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.restapi.statuspage;
+import com.yahoo.slime.Slime;
+import com.yahoo.vespa.config.SlimeUtils;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
@@ -36,8 +38,8 @@ public class StatusPageClient {
this.key = Objects.requireNonNull(key, "key cannot be null");
}
- /** GET given page and return response body */
- public byte[] get(String page, Optional<String> since) {
+ /** GET given page and return response body as slime */
+ public Slime get(String page, Optional<String> since) {
HttpGet get = new HttpGet(pageUrl(page, since));
try (CloseableHttpClient client = client()) {
try (CloseableHttpResponse response = client.execute(get)) {
@@ -45,7 +47,8 @@ public class StatusPageClient {
throw new IllegalArgumentException("Received status " + response.getStatusLine().getStatusCode() +
" from StatusPage");
}
- return EntityUtils.toByteArray(response.getEntity());
+ byte[] body = EntityUtils.toByteArray(response.getEntity());
+ return SlimeUtils.jsonToSlime(body);
}
} catch (IOException e) {
throw new UncheckedIOException(e);
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/statuspage/StatusPageProxyHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/statuspage/StatusPageProxyHandler.java
index e652e293c26..ad69681fe7e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/statuspage/StatusPageProxyHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/statuspage/StatusPageProxyHandler.java
@@ -6,13 +6,13 @@ import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.container.jdisc.LoggingRequestHandler;
import com.yahoo.container.jdisc.secretstore.SecretStore;
+import com.yahoo.slime.Slime;
import com.yahoo.vespa.hosted.controller.restapi.ErrorResponse;
import com.yahoo.vespa.hosted.controller.restapi.Path;
+import com.yahoo.vespa.hosted.controller.restapi.SlimeJsonResponse;
import com.yahoo.vespa.hosted.controller.statuspage.config.StatuspageConfig;
import com.yahoo.yolean.Exceptions;
-import java.io.IOException;
-import java.io.OutputStream;
import java.net.URI;
import java.util.Optional;
import java.util.logging.Level;
@@ -60,28 +60,8 @@ public class StatusPageProxyHandler extends LoggingRequestHandler {
}
StatusPageClient client = StatusPageClient.create(apiUrl, secretStore.getSecret(secretKey));
Optional<String> since = Optional.ofNullable(request.getProperty("since"));
- byte[] response = client.get(path.get("page"), since);
- return new ByteArrayHttpResponse(response);
- }
-
- private static class ByteArrayHttpResponse extends HttpResponse {
-
- private final byte[] data;
-
- public ByteArrayHttpResponse(byte[] data) {
- super(200);
- this.data = data;
- }
-
- @Override
- public void render(OutputStream out) throws IOException {
- out.write(data);
- }
-
- @Override
- public String getContentType() {
- return "application/json";
- }
+ Slime statusPageResponse = client.get(path.get("page"), since);
+ return new SlimeJsonResponse(statusPageResponse);
}
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java
index 53f32dbcacd..cc275b0636f 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java
@@ -7,18 +7,21 @@ import com.yahoo.application.container.handler.Response;
import com.yahoo.component.ComponentSpecification;
import com.yahoo.component.Version;
import com.yahoo.container.http.filter.FilterChainRepository;
-import com.yahoo.io.IOUtils;
import com.yahoo.jdisc.http.filter.SecurityRequestFilter;
import com.yahoo.jdisc.http.filter.SecurityRequestFilterChain;
-import com.yahoo.vespa.hosted.controller.integration.ConfigServerMock;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.application.SystemApplication;
+import com.yahoo.vespa.hosted.controller.integration.ConfigServerMock;
import com.yahoo.vespa.hosted.controller.versions.VersionStatus;
import org.junit.ComparisonFailure;
import java.io.File;
import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.charset.CharacterCodingException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.regex.Pattern;
@@ -65,26 +68,31 @@ public class ContainerTester {
computeVersionStatus();
}
- public void assertResponse(Supplier<Request> request, File responseFile) throws IOException {
+ public void assertResponse(Supplier<Request> request, File responseFile) {
assertResponse(request.get(), responseFile);
}
- public void assertResponse(Request request, File responseFile) throws IOException {
+ public void assertResponse(Request request, File responseFile) {
assertResponse(request, responseFile, 200);
}
- public void assertResponse(Supplier<Request> request, File responseFile, int expectedStatusCode) throws IOException {
+ public void assertResponse(Supplier<Request> request, File responseFile, int expectedStatusCode) {
assertResponse(request.get(), responseFile, expectedStatusCode);
}
- public void assertResponse(Request request, File responseFile, int expectedStatusCode) throws IOException {
- String expectedResponse = IOUtils.readFile(new File(responseFilePath + responseFile.toString()));
+ public void assertResponse(Request request, File responseFile, int expectedStatusCode) {
+ String expectedResponse = readTestFile(responseFile.toString());
expectedResponse = include(expectedResponse);
expectedResponse = expectedResponse.replaceAll("(\"[^\"]*\")|\\s*", "$1"); // Remove whitespace
FilterResult filterResult = invokeSecurityFilters(request);
request = filterResult.request;
Response response = filterResult.response != null ? filterResult.response : container.handleRequest(request);
- String responseString = response.getBodyAsString();
+ String responseString;
+ try {
+ responseString = response.getBodyAsString();
+ } catch (CharacterCodingException e) {
+ throw new UncheckedIOException(e);
+ }
if (expectedResponse.contains("(ignore)")) {
// Convert expected response to a literal pattern and replace any ignored field with a pattern that matches
// until the first stop character
@@ -141,7 +149,7 @@ public class ContainerTester {
}
/** Replaces @include(localFile) with the content of the file */
- private String include(String response) throws IOException {
+ private String include(String response) {
// Please don't look at this code
int includeIndex = response.indexOf("@include(");
if (includeIndex < 0) return response;
@@ -149,14 +157,22 @@ public class ContainerTester {
String rest = response.substring(includeIndex + "@include(".length());
int filenameEnd = rest.indexOf(")");
String includeFileName = rest.substring(0, filenameEnd);
- String includedContent = IOUtils.readFile(new File(responseFilePath + includeFileName));
+ String includedContent = readTestFile(includeFileName);
includedContent = include(includedContent);
String postFix = rest.substring(filenameEnd + 1);
postFix = include(postFix);
return prefix + includedContent + postFix;
}
- static class FilterResult {
+ private String readTestFile(String name) {
+ try {
+ return new String(Files.readAllBytes(Paths.get(responseFilePath, name)));
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ private static class FilterResult {
final Request request;
final Response response;
@@ -165,5 +181,6 @@ public class ContainerTester {
this.response = response;
}
}
+
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
index 46c882eaa55..45513c2294f 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
@@ -18,7 +18,6 @@ import com.yahoo.vespa.athenz.api.AthenzUser;
import com.yahoo.vespa.athenz.api.NToken;
import com.yahoo.vespa.config.SlimeUtils;
import com.yahoo.vespa.hosted.controller.Application;
-import com.yahoo.vespa.hosted.controller.integration.ConfigServerMock;
import com.yahoo.vespa.hosted.controller.api.identifiers.Property;
import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId;
import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId;
@@ -33,6 +32,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.organization.MockOrgani
import com.yahoo.vespa.hosted.controller.api.integration.organization.User;
import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneId;
import com.yahoo.vespa.hosted.controller.application.ApplicationPackage;
+import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.ClusterInfo;
import com.yahoo.vespa.hosted.controller.application.ClusterUtilization;
import com.yahoo.vespa.hosted.controller.application.Deployment;
@@ -43,6 +43,7 @@ import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzClientFactoryMock;
import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzDbMock;
import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder;
import com.yahoo.vespa.hosted.controller.deployment.BuildJob;
+import com.yahoo.vespa.hosted.controller.integration.ConfigServerMock;
import com.yahoo.vespa.hosted.controller.restapi.ContainerControllerTester;
import com.yahoo.vespa.hosted.controller.restapi.ContainerTester;
import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerTest;
@@ -97,6 +98,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
.build();
private static final AthenzDomain ATHENZ_TENANT_DOMAIN = new AthenzDomain("domain1");
+ private static final AthenzDomain ATHENZ_TENANT_DOMAIN_2 = new AthenzDomain("domain2");
private static final ScrewdriverId SCREWDRIVER_ID = new ScrewdriverId("12345");
private static final UserId USER_ID = new UserId("myuser");
private static final UserId HOSTED_VESPA_OPERATOR = new UserId("johnoperator");
@@ -145,7 +147,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
// Add another Athens domain, so we can try to create more tenants
- createAthenzDomainWithAdmin(new AthenzDomain("domain2"), USER_ID); // New domain to test tenant w/property ID
+ createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN_2, USER_ID); // New domain to test tenant w/property ID
// Add property info for that property id, as well, in the mock organization.
addPropertyData((MockOrganization) controllerTester.controller().organization(), "1234");
// POST (add) a tenant with property ID
@@ -194,7 +196,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
ATHENZ_TENANT_DOMAIN,
new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(id.application().value())); // (Necessary but not provided in this API)
- // Trigger deployment from completion of component job
+ // Pipeline notifies about completed component job
controllerTester.jobCompletion(JobType.component)
.application(id)
.projectId(screwdriverProjectId)
@@ -203,13 +205,13 @@ public class ApplicationApiTest extends ControllerContainerTest {
// ... systemtest
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/default/", POST)
- .data(createApplicationDeployData(applicationPackage, false))
+ .data(createApplicationDeployData(Optional.empty(), false))
.screwdriverIdentity(SCREWDRIVER_ID),
new File("deploy-result.json"));
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/default", DELETE)
.screwdriverIdentity(SCREWDRIVER_ID),
"Deactivated tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/default");
- // Called through the separate screwdriver/v1 API
+
controllerTester.jobCompletion(JobType.systemTest)
.application(id)
.projectId(screwdriverProjectId)
@@ -217,7 +219,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
// ... staging
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/staging/region/us-east-3/instance/default/", POST)
- .data(createApplicationDeployData(applicationPackage, false))
+ .data(createApplicationDeployData(Optional.empty(), false))
.screwdriverIdentity(SCREWDRIVER_ID),
new File("deploy-result.json"));
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/staging/region/us-east-3/instance/default", DELETE)
@@ -230,7 +232,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
// ... prod zone
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/corp-us-east-1/instance/default/", POST)
- .data(createApplicationDeployData(applicationPackage, false))
+ .data(createApplicationDeployData(Optional.empty(), false))
.screwdriverIdentity(SCREWDRIVER_ID),
new File("deploy-result.json"));
controllerTester.jobCompletion(JobType.productionCorpUsEast1)
@@ -239,6 +241,43 @@ public class ApplicationApiTest extends ControllerContainerTest {
.unsuccessful()
.submit();
+ // POST (create) another application
+ ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
+ .environment(Environment.prod)
+ .region("us-west-1")
+ .build();
+
+ tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2", POST)
+ .userIdentity(USER_ID)
+ .nToken(N_TOKEN),
+ new File("application-reference-2.json"));
+
+ ApplicationId app2 = ApplicationId.from("tenant2", "application2", "default");
+ long screwdriverProjectId2 = 456;
+ addScrewdriverUserToDeployRole(SCREWDRIVER_ID,
+ ATHENZ_TENANT_DOMAIN_2,
+ new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(app2.application().value()));
+
+ // Trigger upgrade and then application change
+ controllerTester.controller().applications().deploymentTrigger().triggerChange(app2, Change.of(Version.fromString("7.0")));
+
+ controllerTester.jobCompletion(JobType.component)
+ .application(app2)
+ .projectId(screwdriverProjectId2)
+ .uploadArtifact(applicationPackage)
+ .submit();
+
+ // GET application having both change and outstanding change
+ tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2", GET)
+ .userIdentity(USER_ID),
+ new File("application2.json"));
+
+ // DELETE application
+ tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2", DELETE)
+ .userIdentity(USER_ID)
+ .nToken(N_TOKEN),
+ "");
+
// GET tenant screwdriver projects
tester.assertResponse(request("/application/v4/tenant-pipeline/", GET)
.userIdentity(USER_ID),
@@ -383,8 +422,6 @@ public class ApplicationApiTest extends ControllerContainerTest {
tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE).userIdentity(USER_ID)
.nToken(N_TOKEN),
new File("tenant-without-applications.json"));
-
- controllerTester.controller().deconstruct();
}
private void addIssues(ContainerControllerTester tester, ApplicationId id) {
@@ -395,7 +432,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
}
@Test
- public void testDeployDirectly() throws Exception {
+ public void testDeployDirectly() {
// Setup
ContainerControllerTester controllerTester = new ContainerControllerTester(container, responseFiles);
ContainerTester tester = controllerTester.containerTester();
@@ -427,11 +464,10 @@ public class ApplicationApiTest extends ControllerContainerTest {
new File("deploy-result.json"));
}
-
// Tests deployment to config server when using just on API call
// For now this depends on a switch in ApplicationController that does this for by- tenants in CD only
@Test
- public void testDeployDirectlyUsingOneCallForDeploy() throws Exception {
+ public void testDeployDirectlyUsingOneCallForDeploy() {
// Setup
ContainerControllerTester controllerTester = new ContainerControllerTester(container, responseFiles);
ContainerTester tester = controllerTester.containerTester();
@@ -813,7 +849,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
}
@Test
- public void deployment_succeeds_when_correct_domain_is_used() throws IOException {
+ public void deployment_succeeds_when_correct_domain_is_used() {
ContainerControllerTester controllerTester = new ContainerControllerTester(container, responseFiles);
ContainerTester tester = controllerTester.containerTester();
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
@@ -844,7 +880,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
}
@Test
- public void testJobStatusReporting() throws Exception {
+ public void testJobStatusReporting() {
ContainerControllerTester tester = new ContainerControllerTester(container, responseFiles);
addUserToHostedOperatorRole(HostedAthenzIdentities.from(HOSTED_VESPA_OPERATOR));
tester.containerTester().computeVersionStatus();
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-reference-2.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-reference-2.json
new file mode 100644
index 00000000000..a4026d6a812
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-reference-2.json
@@ -0,0 +1,5 @@
+{
+ "application": "application2",
+ "instance": "default",
+ "url": "http://localhost:8080/application/v4/tenant/tenant2/application/application2"
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application2.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application2.json
new file mode 100644
index 00000000000..fa51d645cfc
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application2.json
@@ -0,0 +1,78 @@
+{
+ "application": "application2",
+ "instance": "default",
+ "deploying": {
+ "version": "(ignore)"
+ },
+ "outstandingChange": {
+ "revision": {
+ "hash": "(ignore)",
+ "source": {
+ "gitRepository": "repository1",
+ "gitBranch": "master",
+ "gitCommit": "commit1"
+ }
+ }
+ },
+ "deploymentJobs": [
+ {
+ "type": "component",
+ "success": true,
+ "lastCompleted": {
+ "id": 42,
+ "version": "(ignore)",
+ "revision": {
+ "hash": "(ignore)",
+ "source": {
+ "gitRepository": "repository1",
+ "gitBranch": "master",
+ "gitCommit": "commit1"
+ }
+ },
+ "reason": "Application commit",
+ "at": "(ignore)"
+ },
+ "lastSuccess": {
+ "id": 42,
+ "version": "(ignore)",
+ "revision": {
+ "hash": "(ignore)",
+ "source": {
+ "gitRepository": "repository1",
+ "gitBranch": "master",
+ "gitCommit": "commit1"
+ }
+ },
+ "reason": "Application commit",
+ "at": "(ignore)"
+ }
+ },
+ {
+ "type": "system-test",
+ "success": false,
+ "lastTriggered": {
+ "id": -1,
+ "version": "7.0.0",
+ "revision": {
+ "hash": "1.0.42-commit1",
+ "source": {
+ "gitRepository": "repository1",
+ "gitBranch": "master",
+ "gitCommit": "commit1"
+ }
+ },
+ "reason": "Testing last changes outside prod",
+ "at": "(ignore)"
+ }
+ }
+ ],
+ "changeBlockers": [],
+ "compileVersion": "6.1.0",
+ "globalRotations": [],
+ "instances": [],
+ "metrics": {
+ "queryServiceQuality": 0.0,
+ "writeServiceQuality": 0.0
+ },
+ "activity": {}
+}