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
This commit is contained in:
Leander Bessa Beernaert 2012-11-15 10:38:21 +00:00 committed by Adrian Cole
parent 718874e348
commit 99375844a6
7 changed files with 228 additions and 0 deletions

View File

@ -18,6 +18,7 @@
*/ */
package org.jclouds.openstack.nova.v2_0.features; package org.jclouds.openstack.nova.v2_0.features;
import com.google.common.base.Optional;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -261,4 +262,23 @@ public interface ServerApi {
*/ */
void deleteMetadata(String id, String key); 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
* <a href="http://api.openstack.org/api-ref.html"> OpenStack API
* reference</a>. <br/>
* 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<Map<String, String>> getDiagnostics(String id);
} }

View File

@ -18,6 +18,7 @@
*/ */
package org.jclouds.openstack.nova.v2_0.features; package org.jclouds.openstack.nova.v2_0.features;
import com.google.common.base.Optional;
import java.util.Map; import java.util.Map;
import javax.ws.rs.Consumes; 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.ReturnFalseOnNotFoundOr404;
import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404; import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404;
import org.jclouds.rest.functions.ReturnVoidOnNotFoundOr404; import org.jclouds.rest.functions.ReturnVoidOnNotFoundOr404;
import org.jclouds.rest.functions.ReturnAbsentOn403Or404Or500;
import com.google.common.util.concurrent.ListenableFuture; 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. * Provides asynchronous access to Server via their REST API.
@ -319,4 +322,14 @@ public interface ServerAsyncApi {
@ExceptionParser(ReturnVoidOnNotFoundOr404.class) @ExceptionParser(ReturnVoidOnNotFoundOr404.class)
ListenableFuture<Void> deleteMetadata(@PathParam("id") String id, @PathParam("key") String key); ListenableFuture<Void> 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<Optional<Map<String, String>>> getDiagnostics(@PathParam("id") String id);
} }

View File

@ -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<HttpResponse, Optional <Map<String,String>>> {
private final ParseJson<Optional <Map<String,String>>> parser;
@Inject
public ParseDiagnostics(ParseJson<Optional <Map<String,String>>> parser) {
this.parser = parser;
}
@Override
public Optional <Map<String,String>> apply(HttpResponse response) {
checkNotNull(response, "response");
return parser.apply(response);
}
}

View File

@ -36,6 +36,7 @@ import org.testng.annotations.Test;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import org.jclouds.openstack.nova.v2_0.parse.*;
/** /**
* Tests annotation parsing of {@code ServerAsyncApi} * 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); 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());
}
}
} }

View File

@ -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<Optional<Map<String,String>>> {
@Override
public Optional<Map<String,String>> expected() {
return Optional.<Map<String,String>>of(
new ImmutableMap.Builder<String,String>()
.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());
}
}

View File

@ -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
}

View File

@ -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<Exception, Object> {
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);
}
}