/**
* 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 io.airlift.airship.agent;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import io.airlift.airship.shared.Installation;
import io.airlift.airship.shared.SlotLifecycleState;
import io.airlift.airship.shared.SlotStatus;
import io.airlift.log.Logger;
import io.airlift.units.Duration;
import java.net.URI;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
import static io.airlift.airship.shared.SlotLifecycleState.RESTARTING;
import static io.airlift.airship.shared.SlotLifecycleState.RUNNING;
import static io.airlift.airship.shared.SlotLifecycleState.STOPPED;
import static io.airlift.airship.shared.SlotLifecycleState.TERMINATED;
import static io.airlift.airship.shared.SlotLifecycleState.UNKNOWN;
import static io.airlift.airship.shared.SlotStatus.createSlotStatus;
public class DeploymentSlot
implements Slot
{
private static final Logger log = Logger.get(DeploymentSlot.class);
private final UUID id;
private final String location;
private final URI self;
private final URI externalUri;
private final Duration lockWait;
private final DeploymentManager deploymentManager;
private final LifecycleManager lifecycleManager;
private final AtomicReference<SlotStatus> lastSlotStatus;
private boolean terminated;
private final ReentrantLock lock = new ReentrantLock();
private volatile Thread lockOwner;
private volatile List<StackTraceElement> lockAcquisitionLocation;
public DeploymentSlot(URI self,
URI externalUri,
DeploymentManager deploymentManager,
LifecycleManager lifecycleManager,
Duration maxLockWait)
{
Preconditions.checkNotNull(self, "self is null");
Preconditions.checkNotNull(externalUri, "externalUri is null");
Preconditions.checkNotNull(deploymentManager, "deploymentManager is null");
Preconditions.checkNotNull(lifecycleManager, "lifecycleManager is null");
Preconditions.checkNotNull(maxLockWait, "maxLockWait is null");
this.location = deploymentManager.getLocation();
this.deploymentManager = deploymentManager;
this.lifecycleManager = lifecycleManager;
lockWait = maxLockWait;
id = deploymentManager.getSlotId();
this.self = self;
this.externalUri = externalUri;
Deployment deployment = deploymentManager.getDeployment();
if (deployment == null) {
SlotStatus slotStatus = createSlotStatus(id,
self,
externalUri,
null,
location,
UNKNOWN,
null,
deploymentManager.hackGetDataDir().getAbsolutePath(),
ImmutableMap.<String, Integer>of());
lastSlotStatus = new AtomicReference<>(slotStatus);
return;
}
SlotLifecycleState state = lifecycleManager.status(deployment);
SlotStatus slotStatus = createSlotStatus(id,
self,
externalUri,
null,
location,
state,
deployment.getAssignment(),
deployment.getDataDir().getAbsolutePath(),
deployment.getResources());
lastSlotStatus = new AtomicReference<SlotStatus>(slotStatus);
}
public DeploymentSlot(URI self,
URI externalUri,
DeploymentManager deploymentManager,
LifecycleManager lifecycleManager,
Installation installation,
Duration maxLockWait)
{
Preconditions.checkNotNull(deploymentManager, "deploymentManager is null");
Preconditions.checkNotNull(lifecycleManager, "lifecycleManager is null");
Preconditions.checkNotNull(installation, "installation is null");
Preconditions.checkNotNull(maxLockWait, "maxLockWait is null");
this.location = deploymentManager.getLocation();
this.deploymentManager = deploymentManager;
this.lifecycleManager = lifecycleManager;
this.lockWait = maxLockWait;
this.id = deploymentManager.getSlotId();
this.self = self;
this.externalUri = externalUri;
// install the software
try {
// deploy new server
deploymentManager.install(installation);
// create node config file
Deployment deployment = deploymentManager.getDeployment();
lifecycleManager.updateNodeConfig(deployment);
// set initial status
lastSlotStatus = new AtomicReference<SlotStatus>(createSlotStatus(id,
self,
externalUri,
null,
location,
STOPPED,
installation.getAssignment(),
deployment.getDataDir().getAbsolutePath(),
deployment.getResources()));
}
catch (Exception e) {
deploymentManager.terminate();
throw Throwables.propagate(e);
}
}
@Override
public UUID getId()
{
return id;
}
@Override
public URI getSelf()
{
return self;
}
@Override
public URI getExternalUri()
{
return externalUri;
}
@Override
public SlotStatus assign(Installation installation)
{
Preconditions.checkNotNull(installation, "installation is null");
lock();
try {
Preconditions.checkState(!terminated, "Slot has been terminated");
log.info("Becoming %s with %s", installation.getAssignment().getBinary(), installation.getAssignment().getConfig());
// stop current server
Deployment oldDeployment = deploymentManager.getDeployment();
boolean shouldStart = false;
if (oldDeployment != null) {
// if deployment is running, we will restart it after the upgrade
SlotLifecycleState currentState = lifecycleManager.status(oldDeployment);
shouldStart = currentState == RUNNING || currentState == RESTARTING;
// stop old deployment
try {
lifecycleManager.stop(oldDeployment);
}
catch (Exception e) {
// stop failed, just kill the process
lifecycleManager.kill(oldDeployment);
}
}
// deploy new server
Deployment deployment = deploymentManager.install(installation);
// create node config file
lifecycleManager.updateNodeConfig(deployment);
// restart the server if it was previously running
SlotLifecycleState state;
if (shouldStart) {
state = lifecycleManager.start(deployment);
}
else {
state = STOPPED;
}
SlotStatus slotStatus = createSlotStatus(id,
self,
externalUri,
null,
location,
state,
installation.getAssignment(),
deployment.getDataDir().getAbsolutePath(),
deployment.getResources());
lastSlotStatus.set(slotStatus);
return slotStatus;
}
finally {
unlock();
}
}
@Override
public SlotStatus terminate()
{
lock();
try {
if (!terminated) {
SlotStatus status = status();
if ((status.getState() != STOPPED) && (deploymentManager.getDeployment() != null)) {
// slot is not stopped and deployment still exists
return status;
}
// terminate the slot
deploymentManager.terminate();
terminated = true;
}
SlotStatus slotStatus = lastSlotStatus.get().changeState(TERMINATED);
lastSlotStatus.set(slotStatus);
return slotStatus;
}
finally {
unlock();
}
}
@Override
public SlotStatus getLastSlotStatus()
{
return lastSlotStatus.get();
}
@Override
public SlotStatus status()
{
try {
lock();
}
catch (LockTimeoutException e) {
// could not get the lock because there is an operation in progress
// just return the last state we saw
// todo consider adding "in-process" states like starting
return lastSlotStatus.get();
}
try {
if (terminated) {
return lastSlotStatus.get().changeState(TERMINATED);
}
Deployment activeDeployment = deploymentManager.getDeployment();
if (activeDeployment == null) {
return lastSlotStatus.get().changeAssignment(UNKNOWN, null, ImmutableMap.<String, Integer>of());
}
SlotStatus slotStatus = lastSlotStatus.get().changeState(lifecycleManager.status(activeDeployment));
lastSlotStatus.set(slotStatus);
return slotStatus;
}
finally {
unlock();
}
}
@Override
public SlotStatus start()
{
lock();
try {
Preconditions.checkState(!terminated, "Slot has been terminated");
Deployment activeDeployment = deploymentManager.getDeployment();
if (activeDeployment == null) {
throw new IllegalStateException("Slot can not be started because the slot is not assigned");
}
SlotLifecycleState state = lifecycleManager.start(activeDeployment);
SlotStatus slotStatus = lastSlotStatus.get().changeState(state);
lastSlotStatus.set(slotStatus);
return slotStatus;
}
finally {
unlock();
}
}
@Override
public SlotStatus restart()
{
lock();
try {
Preconditions.checkState(!terminated, "Slot has been terminated");
Deployment activeDeployment = deploymentManager.getDeployment();
if (activeDeployment == null) {
throw new IllegalStateException("Slot can not be restarted because the slot is not assigned");
}
SlotLifecycleState state = lifecycleManager.restart(activeDeployment);
SlotStatus slotStatus = lastSlotStatus.get().changeState(state);
lastSlotStatus.set(slotStatus);
return slotStatus;
}
finally {
unlock();
}
}
@Override
public SlotStatus stop()
{
lock();
try {
Preconditions.checkState(!terminated, "Slot has been terminated");
Deployment activeDeployment = deploymentManager.getDeployment();
if (activeDeployment == null) {
throw new IllegalStateException("Slot can not be stopped because the slot is not assigned");
}
SlotLifecycleState state = lifecycleManager.stop(activeDeployment);
SlotStatus slotStatus = lastSlotStatus.get().changeState(state);
lastSlotStatus.set(slotStatus);
return slotStatus;
}
finally {
unlock();
}
}
@Override
public SlotStatus kill()
{
lock();
try {
Preconditions.checkState(!terminated, "Slot has been terminated");
Deployment activeDeployment = deploymentManager.getDeployment();
if (activeDeployment == null) {
throw new IllegalStateException("Slot can not be killed because the slot is not assigned");
}
SlotLifecycleState state = lifecycleManager.kill(activeDeployment);
SlotStatus slotStatus = lastSlotStatus.get().changeState(state);
lastSlotStatus.set(slotStatus);
return slotStatus;
}
finally {
unlock();
}
}
private void lock()
{
try {
if (!lock.tryLock((long) lockWait.toMillis(), TimeUnit.MILLISECONDS)) {
throw new LockTimeoutException(lockOwner, lockWait, lockAcquisitionLocation);
}
// capture the location where the lock was acquired
lockAcquisitionLocation = ImmutableList.copyOf(new Exception("lock acquired HERE").fillInStackTrace().getStackTrace());
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}
private void unlock()
{
lockOwner = null;
lockAcquisitionLocation = null;
lock.unlock();
}
@Override
public boolean equals(Object o)
{
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DeploymentSlot slot = (DeploymentSlot) o;
if (!id.equals(slot.id)) {
return false;
}
return true;
}
@Override
public int hashCode()
{
return id.hashCode();
}
@Override
public String toString()
{
final StringBuilder sb = new StringBuilder();
sb.append("Slot");
sb.append("{slotId=").append(id);
sb.append(", location='").append(location).append('\'');
sb.append('}');
return sb.toString();
}
}