package io.eguan.vold.model;
/*
* #%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.configuration.MetaConfiguration;
import io.eguan.dtx.DtxManager;
import io.eguan.dtx.DtxTaskAdm;
import io.eguan.dtx.DtxTaskApi;
import io.eguan.dtx.DtxTaskFutureVoid;
import io.eguan.iscsisrv.IscsiServer;
import io.eguan.nbdsrv.NbdServer;
import io.eguan.nrs.NrsStorageConfigKey;
import io.eguan.proto.Common.OpCode;
import io.eguan.proto.Common.Type;
import io.eguan.proto.vvr.VvrRemote.RemoteOperation;
import io.eguan.vvr.configuration.keys.IbsIbpGenPathConfigKey;
import io.eguan.vvr.configuration.keys.IbsIbpPathConfigKey;
import io.eguan.vvr.configuration.keys.StartedConfigKey;
import io.eguan.vvr.persistence.repository.VvrTaskInfo;
import io.eguan.vvr.remote.VvrRemoteUtils;
import io.eguan.vvr.repository.core.api.Device;
import io.eguan.vvr.repository.core.api.FutureVoid;
import io.eguan.vvr.repository.core.api.Snapshot;
import io.eguan.vvr.repository.core.api.VersionedVolumeRepository;
import io.eguan.vvr.repository.core.api.VvrItem;
import io.eguan.vvr.repository.core.api.VersionedVolumeRepository.ItemChangedEvent;
import io.eguan.vvr.repository.core.api.VersionedVolumeRepository.ItemCreatedEvent;
import io.eguan.vvr.repository.core.api.VersionedVolumeRepository.ItemDeletedEvent;
import java.io.File;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nonnull;
import javax.management.AttributeChangeNotification;
import javax.management.JMException;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanServer;
import javax.management.MXBean;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
import javax.management.NotificationEmitter;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import org.slf4j.Logger;
import com.google.common.eventbus.Subscribe;
import com.google.protobuf.MessageLite;
/**
* The class {@link #Vvr()} encapsulates a {@link VersionedVolumeRepository} and is exported as a {@link MXBean}.
*
* @author oodrive
* @author llambert
* @author ebredzinski
* @author pwehrle
* @author jmcaba
*
*/
final class Vvr implements VvrMXBean, NotificationEmitter {
private static final Logger LOGGER = Constants.LOGGER;
/** Owner of the VVR */
private final UUID owner;
/** VVR instance */
private final VersionedVolumeRepository vvrInstance;
/** Transaction manager */
private final AtomicReference<DtxTaskApi> dtxTaskApiRef;
/** Server to register new items */
private volatile MBeanServer server;
/** Associated iSCSI server */
private final IscsiServer iscsiServer;
/** Associated NBD server */
private final NbdServer nbdServer;
/** Current node */
private final UUID node;
/** Device MXBeans */
private final Map<UUID, WeakReference<VvrDevice>> devicesMXBeans = new ConcurrentHashMap<>();
/** JMX notifications support */
private final NotificationBroadcasterSupport notificationEmitter = new NotificationBroadcasterSupport();
/** JMX notification sequence number */
private final AtomicInteger notificationSequenceNumber = new AtomicInteger(1);
Vvr(final UUID owner, final VersionedVolumeRepository vvrInstance,
@Nonnull final AtomicReference<DtxTaskApi> dtxTaskApiRef, @Nonnull final IscsiServer iscsiServer,
@Nonnull final NbdServer nbdServer, @Nonnull final UUID node) {
super();
this.owner = owner;
this.vvrInstance = vvrInstance;
this.dtxTaskApiRef = Objects.requireNonNull(dtxTaskApiRef);
this.iscsiServer = Objects.requireNonNull(iscsiServer);
this.nbdServer = Objects.requireNonNull(nbdServer);
this.node = Objects.requireNonNull(node);
}
final void init(final MBeanServer server) {
this.server = server;
vvrInstance.init();
vvrInstance.registerItemEvents(this);
}
final void fini() {
try {
vvrInstance.unregisterItemEvents(this);
}
catch (final Throwable t) {
LOGGER.warn("Error while unregistering from " + toString() + " event bus", t);
}
vvrInstance.fini();
this.server = null;
}
final void dtxRegister(@Nonnull final DtxManager dtxManager) {
dtxManager.registerResourceManager(vvrInstance);
}
final void dtxUnregister(@Nonnull final DtxManager dtxManager) {
dtxManager.unregisterResourceManager(vvrInstance.getId());
}
/**
* Register the snapshots and the devices to the given {@link MBeanServer}.
*
* @param server
* @throws JMException
*/
private final void registerElements() throws JMException {
final UUID vvrUuid = getUuidUuid();
// Register snapshots
{
final Set<UUID> snapshots = vvrInstance.getSnapshots();
for (final UUID uuid : snapshots) {
final Snapshot snapshot = vvrInstance.getSnapshot(uuid);
final VvrSnapshot mbean = new VvrSnapshot(snapshot);
final ObjectName objectName = VvrObjectNameFactory.newSnapshotObjectName(owner, vvrUuid, uuid);
server.registerMBean(mbean, objectName);
}
}
// Register devices
{
final Set<UUID> devices = vvrInstance.getDevices();
for (final UUID uuid : devices) {
final Device device = vvrInstance.getDevice(uuid);
final VvrDevice mbean = VvrDevice.loadVvrDevice(device, iscsiServer, nbdServer, node);
devicesMXBeans.put(uuid, new WeakReference<>(mbean));
final ObjectName objectName = VvrObjectNameFactory.newDeviceObjectName(owner, vvrUuid, uuid);
server.registerMBean(mbean, objectName);
}
}
}
private final void unregisterElements() throws JMException {
final UUID vvrUuid = getUuidUuid();
// Unregister snapshots
{
final Set<UUID> snapshots = vvrInstance.getSnapshots();
for (final UUID uuid : snapshots) {
try {
final ObjectName objectName = VvrObjectNameFactory.newSnapshotObjectName(owner, vvrUuid, uuid);
server.unregisterMBean(objectName);
}
catch (final Throwable t) {
LOGGER.warn(toString() + ": error while unregistering snapshot " + uuid, t);
}
}
}
// Unregister devices
{
final Set<UUID> devices = vvrInstance.getDevices();
for (final UUID uuid : devices) {
try {
final ObjectName objectName = VvrObjectNameFactory.newDeviceObjectName(owner, vvrUuid, uuid);
server.unregisterMBean(objectName);
// Must deactivate the device (keep state if activated)
final WeakReference<VvrDevice> deviceRef = devicesMXBeans.remove(uuid);
final VvrDevice device = deviceRef.get();
if (device != null) {
device.doDeactivate();
}
}
catch (final Throwable t) {
LOGGER.warn(toString() + ": error while unregistering device " + uuid, t);
}
}
}
}
final boolean wasStarted() {
return StartedConfigKey.getInstance().getTypedValue(vvrInstance.getConfiguration()).booleanValue();
}
final ArrayList<File> getIbp() {
final MetaConfiguration configuration = vvrInstance.getConfiguration();
return IbsIbpPathConfigKey.getInstance().getTypedValue(configuration);
}
final File getIbpGen() {
final MetaConfiguration configuration = vvrInstance.getConfiguration();
return IbsIbpGenPathConfigKey.getInstance().getTypedValue(configuration);
}
final File getNrsStorage() {
final MetaConfiguration configuration = vvrInstance.getConfiguration();
return NrsStorageConfigKey.getInstance().getTypedValue(configuration);
}
/*
* Should be getUuidString(), but, in this case, the JMX attribute would be 'uuidString'
*
* @see io.eguan.vold.model.VvrMXBean#getUuid()
*/
@Override
public final String getUuid() {
return getUuidUuid().toString();
}
final UUID getUuidUuid() {
return vvrInstance.getUuid();
}
@Override
public final String getOwnerUuid() {
return vvrInstance.getOwnerId().toString();
}
@Override
public final String getName() {
return vvrInstance.getName();
}
@Override
public final void setName(final String name) {
final FutureVoid futureTask = vvrInstance.setName(name);
if (futureTask == null) {
return;
}
try {
futureTask.get();
}
catch (InterruptedException | ExecutionException e) {
// propagate failure
throw new IllegalStateException(e);
}
}
@Override
public final String getDescription() {
return vvrInstance.getDescription();
}
@Override
public final void setDescription(final String description) {
final FutureVoid futureTask = vvrInstance.setDescription(description);
if (futureTask == null) {
return;
}
try {
futureTask.get();
}
catch (InterruptedException | ExecutionException e) {
// propagate failure
throw new IllegalStateException(e);
}
}
@Override
public final boolean isInitialized() {
return vvrInstance.isInitialized();
}
@Override
public final boolean isStarted() {
return vvrInstance.isStarted();
}
@Override
public final void start() {
startStopVvr(true);
}
@Override
public final String startNoWait() {
return startStopVvrNoWait(true).toString();
}
final void doStart() throws JMException {
doStart(true);
}
final void doStart(final boolean saveState) throws JMException {
vvrInstance.start(saveState);
// Expose the children of the VVR
try {
registerElements();
}
catch (RuntimeException | JMException e) {
LOGGER.error("Failed to register the children of " + toString(), e);
// Start failed: unregister and stop VVR
doStop(false);
throw e;
}
}
@Override
public final void stop() {
startStopVvr(false);
}
@Override
public final String stopNoWait() {
return startStopVvrNoWait(false).toString();
}
final void doStop() {
// Can not stop VVR if a device is activated
for (final WeakReference<VvrDevice> deviceRef : devicesMXBeans.values()) {
final VvrDevice device = deviceRef.get();
if (device != null && device.isActiveLocally()) {
throw new IllegalStateException("Device " + device.getUuid() + " is activated");
}
}
doStop(true);
}
final void doStop(final boolean saveState) {
// Unregister the children of the VVR
try {
unregisterElements();
}
catch (final Throwable t) {
// Ignored
LOGGER.warn("Error while unregistering " + toString() + " elements", t);
}
vvrInstance.stop(saveState);
}
private final void startStopVvr(final boolean start) {
final UUID taskId = startStopVvrNoWait(start);
// Wait for task end
final DtxTaskFutureVoid future = new DtxTaskFutureVoid(taskId, dtxTaskApiRef.get());
try {
future.get();
}
catch (InterruptedException | ExecutionException e) {
throw new IllegalStateException(e);
}
}
private final UUID startStopVvrNoWait(final boolean start) {
// Build payload
final RemoteOperation.Builder opBuilder = RemoteOperation.newBuilder();
// UUID of the VVR to start/stop
final UUID vvrUuid = vvrInstance.getId();
opBuilder.setUuid(VvrRemoteUtils.newUuid(vvrUuid));
// Submit transaction
final DtxTaskApi dtxTaskApi = dtxTaskApiRef.get();
final UUID taskId = VvrRemoteUtils.submitTransaction(opBuilder, dtxTaskApi, owner,
VvrRemoteUtils.newUuid(node), Type.VVR, start ? OpCode.START : OpCode.STOP);
return taskId;
}
@Override
public final VvrTask getVvrTask(final String taskId) {
final UUID taskUuid = UUID.fromString(taskId);
final DtxTaskApi dtxTaskApi = dtxTaskApiRef.get();
final VvrTaskInfo taskInfo = (VvrTaskInfo) dtxTaskApi.getDtxTaskInfo(taskUuid);
final DtxTaskAdm taskAdm = dtxTaskApi.getTask(taskUuid);
// check the resource ID
if (getUuid().equals(taskAdm.getResourceId()))
return new VvrTask(taskAdm.getTaskId(), taskAdm.getStatus(), taskInfo);
else
return null;
}
@Override
public final VvrTask[] getVvrTasks() {
final DtxTaskApi dtxTaskApi = dtxTaskApiRef.get();
final DtxTaskAdm[] tasksAdm = dtxTaskApi.getResourceManagerTasks(getUuidUuid());
final VvrTask[] tasks = new VvrTask[tasksAdm.length];
for (int i = 0; i < tasksAdm.length; i++) {
final VvrTaskInfo taskInfo = (VvrTaskInfo) dtxTaskApi.getDtxTaskInfo(UUID.fromString(tasksAdm[i]
.getTaskId()));
tasks[i] = new VvrTask(tasksAdm[i].getTaskId(), tasksAdm[i].getStatus(), taskInfo);
}
return tasks;
}
/**
* Persist the state <code>deleted</code>.
*/
final void delete() {
this.vvrInstance.delete();
}
final boolean isDeleted() {
return vvrInstance.isDeleted();
}
@Subscribe
public final void recordItemCreation(final ItemCreatedEvent e) {
assert vvrInstance == e.getRepository();
// server should not be null, be may be null if so event have been added before unregistering
if (server == null) {
return;
}
// Ignore event if the VVR is not started (probably a remote event)
if (!vvrInstance.isStarted()) {
return;
}
final VvrItem item = e.getItem();
final UUID uuid = item.getUuid();
try {
if (item instanceof Snapshot) {
final Snapshot snapshot = (Snapshot) item;
final VvrSnapshot mbean = new VvrSnapshot(snapshot);
final ObjectName objectName = VvrObjectNameFactory.newSnapshotObjectName(owner, getUuidUuid(), uuid);
server.registerMBean(mbean, objectName);
LOGGER.info(toString() + ": new snapshot " + uuid);
}
else if (item instanceof Device) {
final Device device = (Device) item;
final VvrDevice mbean = VvrDevice.loadVvrDevice(device, iscsiServer, nbdServer, node);
devicesMXBeans.put(uuid, new WeakReference<>(mbean));
final ObjectName objectName = VvrObjectNameFactory.newDeviceObjectName(owner, getUuidUuid(), uuid);
server.registerMBean(mbean, objectName);
LOGGER.info(toString() + ": new device " + uuid);
}
else {
LOGGER.error("Unexpected item " + item.getClass().getCanonicalName());
}
}
catch (final Throwable t) {
LOGGER.warn(toString() + ": failed to register item " + uuid);
}
}
@Subscribe
public final void recordItemDeletion(final ItemDeletedEvent e) {
assert vvrInstance == e.getRepository();
// server should not be null, be may be null if so event have been added before unregistering
if (server == null) {
return;
}
// Ignore event if the VVR is not started (probably a remote event)
if (!vvrInstance.isStarted()) {
return;
}
final UUID uuid = e.getItemUuid();
final Class<? extends VvrItem> clazz = e.getClazz();
try {
if (Snapshot.class.isAssignableFrom(clazz)) {
final ObjectName objectName = VvrObjectNameFactory.newSnapshotObjectName(owner, getUuidUuid(), uuid);
server.unregisterMBean(objectName);
LOGGER.info(toString() + ": snapshot " + uuid + " deleted");
}
else if (Device.class.isAssignableFrom(clazz)) {
final ObjectName objectName = VvrObjectNameFactory.newDeviceObjectName(owner, getUuidUuid(), uuid);
server.unregisterMBean(objectName);
devicesMXBeans.remove(uuid);
LOGGER.info(toString() + ": device " + uuid + " deleted");
}
else {
LOGGER.error("Unexpected item " + clazz.getCanonicalName());
}
}
catch (final Throwable t) {
LOGGER.warn(toString() + ": failed to register item " + uuid);
}
}
@Subscribe
public final void recordItemChange(final ItemChangedEvent e) {
assert vvrInstance == e.getRepository();
// server should not be null, be may be null if so event have been added before unregistering
if (server == null) {
return;
}
final UUID itemUuid = e.getItemUuid();
final ObjectName objectName;
final String oldValue = e.getOldValue();
final String newValue = e.getNewValue();
// Find item type
if (itemUuid.equals(getUuidUuid())) {
objectName = VvrObjectNameFactory.newVvrObjectName(owner, getUuidUuid());
}
else {
// Ignore event if the VVR is not started (probably a remote event)
if (!vvrInstance.isStarted()) {
return;
}
if (devicesMXBeans.containsKey(itemUuid)) {
objectName = VvrObjectNameFactory.newDeviceObjectName(owner, getUuidUuid(), itemUuid);
}
else {
objectName = VvrObjectNameFactory.newSnapshotObjectName(owner, getUuidUuid(), itemUuid);
}
}
// Notify
final Notification n;
switch (e.getType()) {
case NAME:
n = new AttributeChangeNotification(objectName, notificationSequenceNumber.getAndIncrement(),
System.currentTimeMillis(), "Name changed", "Name", "string", oldValue, newValue);
break;
case DESCRIPTION:
n = new AttributeChangeNotification(objectName, notificationSequenceNumber.getAndIncrement(),
System.currentTimeMillis(), "Description changed", "Description", "string", oldValue, newValue);
break;
default:
n = null;
break;
}
if (n != null) {
notificationEmitter.sendNotification(n);
}
}
/*
* (non-Javadoc)
*
* @see javax.management.NotificationBroadcaster#addNotificationListener(javax.management.NotificationListener,
* javax.management.NotificationFilter, java.lang.Object)
*/
@Override
public final void addNotificationListener(final NotificationListener listener, final NotificationFilter filter,
final Object handback) throws IllegalArgumentException {
notificationEmitter.addNotificationListener(listener, filter, handback);
}
/*
* (non-Javadoc)
*
* @see javax.management.NotificationEmitter#removeNotificationListener(javax.management.NotificationListener,
* javax.management.NotificationFilter, java.lang.Object)
*/
@Override
public final void removeNotificationListener(final NotificationListener listener, final NotificationFilter filter,
final Object handback) throws ListenerNotFoundException {
notificationEmitter.removeNotificationListener(listener, filter, handback);
}
/*
* (non-Javadoc)
*
* @see javax.management.NotificationBroadcaster#removeNotificationListener(javax.management.NotificationListener)
*/
@Override
public final void removeNotificationListener(final NotificationListener listener) throws ListenerNotFoundException {
notificationEmitter.removeNotificationListener(listener);
}
/*
* (non-Javadoc)
*
* @see javax.management.NotificationBroadcaster#getNotificationInfo()
*/
@Override
public final MBeanNotificationInfo[] getNotificationInfo() {
final String[] types = new String[] { AttributeChangeNotification.ATTRIBUTE_CHANGE };
final String name = AttributeChangeNotification.class.getName();
final String description = "An attribute has changed";
final MBeanNotificationInfo info = new MBeanNotificationInfo(types, name, description);
return new MBeanNotificationInfo[] { info };
}
final MessageLite handleMsg(final RemoteOperation op) {
return vvrInstance.handleMsg(op);
}
@Override
public final String toString() {
return "VVR[" + getName() + ", " + getUuid() + "]";
}
}