From 99375844a642680cb4d1264ea45662f453f339f4 Mon Sep 17 00:00:00 2001 From: Leander Bessa Beernaert Date: Thu, 15 Nov 2012 10:38:21 +0000 Subject: [PATCH] Extends JClouds' OpenStack Nova API with the Diagnostics command The diagnostics command returns a collection of system information for the a given server. At the moment, there is no formal specification for this command. Therefore, it is returned as a Map of hypervisor specific entries and corresponding values. More information about the command can be viewed here [1] in the section "Server Diagnostics". [1] http://api.openstack.org/api-ref.html --- .../nova/v2_0/features/ServerApi.java | 20 ++++++++ .../nova/v2_0/features/ServerAsyncApi.java | 13 +++++ .../functions/internal/ParseDiagnostics.java | 50 +++++++++++++++++++ .../v2_0/features/ServerApiExpectTest.java | 43 ++++++++++++++++ .../v2_0/parse/ParseServerDiagnostics.java | 40 +++++++++++++++ .../test/resources/server_diagnostics.json | 17 +++++++ .../ReturnAbsentOn403Or404Or500.java | 45 +++++++++++++++++ 7 files changed, 228 insertions(+) create mode 100644 apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/functions/internal/ParseDiagnostics.java create mode 100644 apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/parse/ParseServerDiagnostics.java create mode 100644 apis/openstack-nova/src/test/resources/server_diagnostics.json create mode 100644 core/src/main/java/org/jclouds/rest/functions/ReturnAbsentOn403Or404Or500.java diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/features/ServerApi.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/features/ServerApi.java index f7ea8b251d..dfefcd1e25 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/features/ServerApi.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/features/ServerApi.java @@ -18,6 +18,7 @@ */ package org.jclouds.openstack.nova.v2_0.features; +import com.google.common.base.Optional; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -260,5 +261,24 @@ public interface ServerApi { * the name of the metadata item */ void deleteMetadata(String id, String key); + + + /** + * Get usage information about the server such as CPU usage, Memory and IO. + * The information returned by this method is dependent on the hypervisor + * in use by the OpenStack installation and whether that hypervisor supports + * this method. More information can be found in the + * OpenStack API + * reference.
+ * At the moment the returned response is a generic map. In future versions + * of OpenStack this might be subject to change. + * + * @param id + * id of the server + * @return A Map containing the collected values organized by key - value. + * @Beta + */ + Optional> getDiagnostics(String id); + } diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/features/ServerAsyncApi.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/features/ServerAsyncApi.java index ef3070728b..a01485026e 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/features/ServerAsyncApi.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/features/ServerAsyncApi.java @@ -18,6 +18,7 @@ */ package org.jclouds.openstack.nova.v2_0.features; +import com.google.common.base.Optional; import java.util.Map; import javax.ws.rs.Consumes; @@ -63,8 +64,10 @@ import org.jclouds.rest.functions.ReturnEmptyPagedIterableOnNotFoundOr404; import org.jclouds.rest.functions.ReturnFalseOnNotFoundOr404; import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404; import org.jclouds.rest.functions.ReturnVoidOnNotFoundOr404; +import org.jclouds.rest.functions.ReturnAbsentOn403Or404Or500; import com.google.common.util.concurrent.ListenableFuture; +import org.jclouds.openstack.nova.v2_0.functions.internal.*; /** * Provides asynchronous access to Server via their REST API. @@ -319,4 +322,14 @@ public interface ServerAsyncApi { @ExceptionParser(ReturnVoidOnNotFoundOr404.class) ListenableFuture deleteMetadata(@PathParam("id") String id, @PathParam("key") String key); + + /** + * @see ServerApi#getDiagnostics + */ + @GET + @Path("/servers/{id}/diagnostics") + @Consumes(MediaType.APPLICATION_JSON) + @ExceptionParser(ReturnAbsentOn403Or404Or500.class) + @ResponseParser(ParseDiagnostics.class) + ListenableFuture>> getDiagnostics(@PathParam("id") String id); } diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/functions/internal/ParseDiagnostics.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/functions/internal/ParseDiagnostics.java new file mode 100644 index 0000000000..d064dc8cd5 --- /dev/null +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/functions/internal/ParseDiagnostics.java @@ -0,0 +1,50 @@ +/* + * Copyright 2012 jclouds. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jclouds.openstack.nova.v2_0.functions.internal; + +import static com.google.common.base.Preconditions.checkNotNull; +import org.jclouds.http.HttpResponse; +import org.jclouds.http.functions.ParseJson; +import org.jclouds.json.internal.GsonWrapper; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.base.Supplier; +import com.google.common.collect.Iterables; +import com.google.inject.Inject; +import com.google.inject.TypeLiteral; +import java.util.Map; + +/** + * @author Leander Beernaert + */ +public class ParseDiagnostics implements Function>> { + + + private final ParseJson>> parser; + + @Inject + public ParseDiagnostics(ParseJson>> parser) { + this.parser = parser; + } + + @Override + public Optional > apply(HttpResponse response) { + checkNotNull(response, "response"); + return parser.apply(response); + } + +} diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/features/ServerApiExpectTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/features/ServerApiExpectTest.java index 6c36540257..8c94a486df 100644 --- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/features/ServerApiExpectTest.java +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/features/ServerApiExpectTest.java @@ -36,6 +36,7 @@ import org.testng.annotations.Test; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSet; +import org.jclouds.openstack.nova.v2_0.parse.*; /** * Tests annotation parsing of {@code ServerAsyncApi} @@ -577,4 +578,46 @@ public class ServerApiExpectTest extends BaseNovaApiExpectTest { apiWhenServerExists.getServerApiForZone("az-1.region-a.geo-1").deleteMetadata(serverId, key); } + + public void testGetDiagnosticsWhenResponseIs200() throws Exception { + + String serverId = "123"; + HttpRequest getDiagnostics = HttpRequest + .builder() + .method("GET") + .addHeader("Accept", "application/json") + .endpoint("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/servers/"+ serverId + "/diagnostics") + .addHeader("X-Auth-Token", authToken) + .build(); + + + HttpResponse serverDiagnosticsResponse = HttpResponse.builder().statusCode(202).message("HTTP/1.1 202 Accepted") + .payload(payloadFromResourceWithContentType("/server_diagnostics.json","application/json; charset=UTF-8")).build(); + + NovaApi apiWithNewServer = requestsSendResponses(keystoneAuthWithUsernameAndPasswordAndTenantName, + responseWithKeystoneAccess, getDiagnostics, serverDiagnosticsResponse); + assertEquals(apiWithNewServer.getServerApiForZone("az-1.region-a.geo-1").getDiagnostics(serverId), + new ParseServerDiagnostics().expected()); + } + + + public void testGetDiagnosticsWhenResponseIs403Or404Or500() throws Exception { + + String serverId = "123"; + HttpRequest getDiagnostics = HttpRequest + .builder() + .method("GET") + .addHeader("Accept", "application/json") + .endpoint("https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456/servers/"+ serverId + "/diagnostics") + .addHeader("X-Auth-Token", authToken) + .build(); + + for (int statusCode : ImmutableSet.of(403, 404, 500)) { + assertTrue(!requestsSendResponses(keystoneAuthWithUsernameAndPasswordAndTenantName, responseWithKeystoneAccess, getDiagnostics, + HttpResponse.builder().statusCode(statusCode).build()).getServerApiForZone("az-1.region-a.geo-1").getDiagnostics(serverId).isPresent()); + } + } + + + } diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/parse/ParseServerDiagnostics.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/parse/ParseServerDiagnostics.java new file mode 100644 index 0000000000..02383d9145 --- /dev/null +++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/parse/ParseServerDiagnostics.java @@ -0,0 +1,40 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.jclouds.openstack.nova.v2_0.parse; + +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableMap; +import java.util.Map; +import java.util.TreeMap; +import org.jclouds.json.BaseItemParserTest; + +/** + * + * @author Leander Beernaert + */ +public class ParseServerDiagnostics extends BaseItemParserTest>> { + + + @Override + public Optional> expected() { + return Optional.>of( + new ImmutableMap.Builder() + .put("vnet0_tx_errors", "0") + .put("vda_read","77364736") + .put("vda_write","415446016") + .put("vnet0_tx_packets","9701") + .put("vda_write_req","47278") + .put("cpu0_time","143150000000") + .put("vnet0_tx","1691221") + .put("vnet0_rx_drop","0") + .put("vda_errors","-1") + .put("vnet0_rx_errors","0") + .put("memory","524288") + .put("vnet0_rx_packets","11271") + .put("vda_read_req","9551") + .put("vnet0_rx","1805288") + .put("vnet0_tx_drop","0").build()); + } +} diff --git a/apis/openstack-nova/src/test/resources/server_diagnostics.json b/apis/openstack-nova/src/test/resources/server_diagnostics.json new file mode 100644 index 0000000000..d35988dfa4 --- /dev/null +++ b/apis/openstack-nova/src/test/resources/server_diagnostics.json @@ -0,0 +1,17 @@ +{ + "vnet0_tx_errors": 0, + "vda_read": 77364736, + "vda_write": 415446016, + "vnet0_tx_packets": 9701, + "vda_write_req": 47278, + "cpu0_time": 143150000000, + "vnet0_tx": 1691221, + "vnet0_rx_drop": 0, + "vda_errors": -1, + "vnet0_rx_errors": 0, + "memory": 524288, + "vnet0_rx_packets": 11271, + "vda_read_req": 9551, + "vnet0_rx": 1805288, + "vnet0_tx_drop": 0 +} \ No newline at end of file diff --git a/core/src/main/java/org/jclouds/rest/functions/ReturnAbsentOn403Or404Or500.java b/core/src/main/java/org/jclouds/rest/functions/ReturnAbsentOn403Or404Or500.java new file mode 100644 index 0000000000..4c4a87121a --- /dev/null +++ b/core/src/main/java/org/jclouds/rest/functions/ReturnAbsentOn403Or404Or500.java @@ -0,0 +1,45 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jclouds.rest.functions; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.base.Predicates; +import com.google.common.base.Throwables; +import com.google.common.primitives.Ints; +import javax.inject.Singleton; +import static org.jclouds.http.HttpUtils.returnValueOnCodeOrNull; + +/** + * + * @author Leander Beernaert + */ +@Singleton +public class ReturnAbsentOn403Or404Or500 implements Function { + + + public Object apply(Exception from) { + Boolean returnVal = returnValueOnCodeOrNull(from, true, Predicates.in(Ints.asList(403, 404, 500))); + if (returnVal != null) + return Optional.absent(); + + throw Throwables.propagate(from); + } + +}