package io.eguan.vold.rest.resources;
/*
* #%L
* Project eguan
* %%
* Copyright (C) 2012 - 2017 Oodrive
* %%
* 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.
* #L%
*/
import io.eguan.iscsisrv.IscsiServerMXBean;
import io.eguan.nbdsrv.NbdServerMXBean;
import io.eguan.srv.AbstractServerMXBean;
import io.eguan.vold.model.DeviceMXBean;
import io.eguan.vold.rest.errors.ClientErrorFactory;
import io.eguan.vold.rest.errors.CustomResourceException;
import io.eguan.vold.rest.errors.ServerErrorFactory;
import io.eguan.vold.rest.generated.model.ConnectionInfo;
import io.eguan.vold.rest.generated.model.Device;
import io.eguan.vold.rest.generated.resources.ActivateDeviceResource;
import io.eguan.vold.rest.generated.resources.CloneDeviceResource;
import io.eguan.vold.rest.generated.resources.ConnectionResource;
import io.eguan.vold.rest.generated.resources.DeactivateDeviceResource;
import io.eguan.vold.rest.generated.resources.DeviceResource;
import io.eguan.vold.rest.generated.resources.NewSnapshotResource;
import io.eguan.vold.rest.generated.resources.ResizeDeviceResource;
import java.net.InetAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.Objects;
import javax.management.JMX;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Strings;
/**
* {@link DeviceResource} implementation for JMX backend.
*
* @author oodrive
* @author pwehrle
* @author ebredzinski
*
*/
public final class DeviceResourceJmxImpl extends AbstractResource implements DeviceResource {
private static final Logger LOGGER = LoggerFactory.getLogger(DeviceResourceJmxImpl.class);
/**
* Enumeration of all supported client protocols.
*
*
*/
private enum ClientProtocol {
ISCSI, NBD;
}
private final DeviceMXBean deviceInstance;
private final VvrResourceJmxImpl vvrResource;
private final URI resourceUri;
public DeviceResourceJmxImpl(final DeviceMXBean deviceProxy, final VvrResourceJmxImpl vvrResource,
final URI resourceUri) {
this.deviceInstance = deviceProxy;
this.vvrResource = vvrResource;
this.resourceUri = resourceUri;
}
public final class ActivateDeviceResourceJmxImpl implements ActivateDeviceResource {
@Override
public Response activateDevice(final String ownerId, final boolean readOnly) {
final VvrTasksResourceJmxImpl tasksResource = vvrResource.getVvrTasksResource();
String taskId;
try {
if (readOnly) {
taskId = deviceInstance.activateRO();
}
else {
taskId = deviceInstance.activateRW();
}
}
catch (final IllegalStateException e) {
throw ClientErrorFactory.newForbiddenException(
e.getMessage() == null ? "Failed to activate device" : e.getMessage(),
"Illegal state to activate device");
}
catch (final Exception e) {
throw ServerErrorFactory.newInternalErrorException("Failed to activate the device",
"Exception activate device", e);
}
final URI taskUri = tasksResource.constructTaskUri(taskId);
return Response.status(Status.ACCEPTED).location(taskUri).build();
}
}
public final class DeactivateDeviceResourceJmxImpl implements DeactivateDeviceResource {
@Override
public Response deactivateDevice(final String ownerId) {
final VvrTasksResourceJmxImpl tasksResource = vvrResource.getVvrTasksResource();
final String taskId;
try {
taskId = deviceInstance.deActivate();
}
catch (final IllegalStateException e) {
throw ClientErrorFactory.newForbiddenException(e.getMessage() == null ? "Failed to de-activate device"
: e.getMessage(), "Illegal state to de-activate device");
}
catch (final Exception e) {
throw ServerErrorFactory.newInternalErrorException("Failed to de-activate device",
"Exception to activate device", e);
}
final URI taskUri = tasksResource.constructTaskUri(taskId);
return Response.status(Status.ACCEPTED).location(taskUri).build();
}
}
public final class ResizeDeviceResourceJmxImpl implements ResizeDeviceResource {
@Override
public Response resizeDevice(final String ownerId, final long size) throws CustomResourceException {
// input validation
if (size <= 0) {
throw ClientErrorFactory.newForbiddenException("Invalid size", "Size " + size
+ " requested for device " + deviceInstance.getUuid());
}
final long currentSize = deviceInstance.getSize();
if ((size < currentSize) && deviceInstance.isActive()) {
throw ClientErrorFactory.newForbiddenException("Device is active",
"Resize requested for active device " + deviceInstance.getUuid());
}
final VvrTasksResourceJmxImpl tasksResource = vvrResource.getVvrTasksResource();
final String taskId;
try {
taskId = deviceInstance.setSizeNoWait(size);
}
catch (final Exception e) {
throw ServerErrorFactory
.newInternalErrorException("Failed to set new size", "Exception to set size", e);
}
final URI taskUri = tasksResource.constructTaskUri(taskId);
return Response.status(Status.ACCEPTED).location(taskUri).build();
}
}
public final class ConnectionResourceJmxImpl implements ConnectionResource {
@Override
public ConnectionInfo getConnection(final String ownerId, final String ip, final String clientProtocol) {
if (Strings.isNullOrEmpty(ip)) {
throw ClientErrorFactory
.newBadRequestException("No client IP provided", "No client IP; ip=" + ip, null);
}
try {
InetAddress.getByName(ip);
}
catch (final UnknownHostException e) {
throw ClientErrorFactory.newBadRequestException("Invalid client IP provided", "Invalid client IP; ip="
+ ip, e);
}
if (!deviceInstance.isActive()) {
throw ClientErrorFactory.newForbiddenException("Can't connect to inactive device; ip=" + ip
+ ", protocol=" + clientProtocol, "Can't connect to device");
}
if (Strings.isNullOrEmpty(clientProtocol)) {
throw ClientErrorFactory.newBadRequestException("No client protocol provided",
"No client protocol; protocol=" + clientProtocol, null);
}
final ClientProtocol validClientProtocol;
try {
validClientProtocol = ClientProtocol.valueOf(clientProtocol.toUpperCase());
}
catch (final IllegalArgumentException e) {
throw ClientErrorFactory.newBadRequestException("Unsupported client protocol",
"Bad client protocol; protocol=" + clientProtocol, null);
}
final ConnectionInfo result = getObjectFactory().createConnectionInfo();
final MBeanServerConnection connection = vvrResource.getParentResource().getConnection();
final ObjectName serverObjName;
try {
switch (validClientProtocol) {
case NBD:
result.setDriverVolumeType("nbd");
serverObjName = new ObjectName(NbdServerMXBean.class.getPackage().getName() + ":type=Server");
result.setDevName(deviceInstance.getName());
break;
case ISCSI:
default:
result.setDriverVolumeType("iscsi");
serverObjName = new ObjectName(IscsiServerMXBean.class.getPackage().getName() + ":type=Server");
result.setIqn(deviceInstance.getIqn());
result.setIscsiAlias(deviceInstance.getIscsiAlias());
}
}
catch (final MalformedObjectNameException e) {
LOGGER.warn("");
throw ServerErrorFactory.newInternalErrorException("Server not found",
"Exception getting connection data", e);
}
final AbstractServerMXBean server = JMX
.newMBeanProxy(connection, serverObjName, AbstractServerMXBean.class);
result.setServerAddress(server.getAddress());
result.setServerPort(server.getPort());
return result;
}
}
/**
* Member {@link NewSnapshotResource} implementation.
*
*
*/
public final class NewSnapshotResourceJmxImpl implements NewSnapshotResource {
@Override
public final Response newSnapshot(final String ownerId, final String name, final String description,
final String uuid) {
final VvrTasksResourceJmxImpl tasksResource = vvrResource.getVvrTasksResource();
final String taskId;
try {
if (uuid == null) {
if (description == null) {
taskId = deviceInstance.takeSnapshot(name);
}
else {
taskId = deviceInstance.takeSnapshot(name, description);
}
}
else {
if (description == null) {
taskId = deviceInstance.takeSnapshotUuid(name, uuid);
}
else {
taskId = deviceInstance.takeSnapshotUuid(name, description, uuid);
}
}
}
catch (final IllegalArgumentException e) {
throw ClientErrorFactory.newBadRequestException(
e.getMessage() == null ? "Failed to create a new snapshot" : e.getMessage(),
"Illegal argument exception for takeSnaphot", e);
}
catch (final IllegalStateException e) {
throw ClientErrorFactory.newForbiddenException(
e.getMessage() == null ? "Failed to create a new snapshot" : e.getMessage(),
"Illegal State exception for takeSnaphot");
}
catch (final Exception e) {
throw ServerErrorFactory.newInternalErrorException("Failed to create a new snapshot",
"Exception create snapshot", e);
}
final URI taskUri = tasksResource.constructTaskUri(taskId);
return Response.status(Status.ACCEPTED).location(taskUri).build();
}
}
/**
* Member {@link CloneDeviceResource} implementation.
*
*
*/
public final class CloneDeviceResourceJmxImpl implements CloneDeviceResource {
@Override
public Response cloneDevice(final String ownerId, final String name, final String description, final String uuid) {
final VvrTasksResourceJmxImpl tasksResource = vvrResource.getVvrTasksResource();
final String taskId;
try {
if (uuid == null) {
if (description == null) {
taskId = deviceInstance.clone(name);
}
else {
taskId = deviceInstance.clone(name, description);
}
}
else {
if (description == null) {
taskId = deviceInstance.cloneUuid(name, uuid);
}
else {
taskId = deviceInstance.cloneUuid(name, description, uuid);
}
}
}
catch (final IllegalArgumentException e) {
throw ClientErrorFactory.newBadRequestException(e.getMessage() == null ? "Failed to clone a new device"
: e.getMessage(), "Illegal argument exception for clone", e);
}
catch (final IllegalStateException e) {
throw ClientErrorFactory.newForbiddenException(e.getMessage() == null ? "Failed to clone a new device"
: e.getMessage(), "Illegal State exception for clone");
}
catch (final Exception e) {
throw ServerErrorFactory.newInternalErrorException("Failed to clone a new device",
"Exception clone device", e);
}
final URI taskUri = tasksResource.constructTaskUri(taskId);
return Response.status(Status.ACCEPTED).location(taskUri).build();
}
}
@Override
public final Device getDevice(final String ownerId) {
return DevicesResourceJmxImpl.getDevicePojoFromMbeanProxy(deviceInstance);
}
@Override
public final Device postDevice(final String ownerId, final Device device) throws CustomResourceException {
Objects.requireNonNull(device);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("POST on Device " + deviceInstance.getUuid() + "; argument=" + device.toString());
}
boolean readOnlyChanged = false;
readOnlyChanged |= !deviceInstance.getUuid().equals(device.getUuid());
readOnlyChanged |= !deviceInstance.getParent().equals(device.getParent());
readOnlyChanged |= (deviceInstance.getSize() != device.getSize());
if (readOnlyChanged) {
LOGGER.warn("Detected read-only attribute change on Snapshot " + deviceInstance.getUuid());
throw ClientErrorFactory.newForbiddenException("Write on read-only attributes forbidden",
"Tried to modify read-only attributes for device " + deviceInstance.getUuid());
}
try {
deviceInstance.setName(device.getName());
}
catch (final IllegalArgumentException e) {
throw ClientErrorFactory.newBadRequestException(e.getMessage() == null ? "Illegal Name" : e.getMessage(),
"Illegal argument for name", e);
}
catch (final Exception e) {
throw ServerErrorFactory.newInternalErrorException("Failed to set name", "Exception set name", e);
}
try {
deviceInstance.setDescription(device.getDescription());
}
catch (final Exception e) {
throw ServerErrorFactory.newInternalErrorException("Failed to set description",
"Exception set description", e);
}
return DevicesResourceJmxImpl.getDevicePojoFromMbeanProxy(deviceInstance);
}
@Override
public final Response deleteDevice(final String ownerId) {
final VvrTasksResourceJmxImpl tasksResource = vvrResource.getVvrTasksResource();
final String taskId;
try {
taskId = deviceInstance.delete();
}
catch (final IllegalStateException e) {
throw ClientErrorFactory.newForbiddenException(e.getMessage() == null ? "Illegal state to delete device"
: e.getMessage(), "Illegal argument for name");
}
catch (final Exception e) {
throw ServerErrorFactory.newInternalErrorException("Failed to delete device", "Exception delete device", e);
}
final URI taskUri = tasksResource.constructTaskUri(taskId);
return Response.status(Status.ACCEPTED).location(taskUri).build();
}
@Override
public final ActivateDeviceResource getActivateDeviceResource(final String ownerId) {
return new ActivateDeviceResourceJmxImpl();
}
@Override
public final DeactivateDeviceResource getDeactivateDeviceResource(final String ownerId) {
return new DeactivateDeviceResourceJmxImpl();
}
@Override
public final ResizeDeviceResource getResizeDeviceResource(final String ownerId) {
return new ResizeDeviceResourceJmxImpl();
}
@Override
public final NewSnapshotResource getNewSnapshotResource(final String ownerId) {
return new NewSnapshotResourceJmxImpl();
}
@Override
public final ConnectionResource getConnectionResource(final String ownerId) {
return new ConnectionResourceJmxImpl();
}
@Override
public CloneDeviceResource getCloneDeviceResource(final String ownerId) {
return new CloneDeviceResourceJmxImpl();
}
@Override
protected final URI getResourceUri() {
return resourceUri;
}
}