HDFS-12519. Ozone: Lease Manager framework. Contributed by Nandakumar.
This commit is contained in:
parent
ebc4d4ebc3
commit
8881309002
|
@ -0,0 +1,189 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with this
|
||||
* work for additional information regarding copyright ownership. The ASF
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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.apache.hadoop.ozone.lease;
|
||||
|
||||
import org.apache.hadoop.util.Time;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* This class represents the lease created on a resource. Callback can be
|
||||
* registered on the lease which will be executed in case of timeout.
|
||||
*
|
||||
* @param <T> Resource type for which the lease can be associated
|
||||
*/
|
||||
public class Lease<T> {
|
||||
|
||||
/**
|
||||
* The resource for which this lease is created.
|
||||
*/
|
||||
private final T resource;
|
||||
|
||||
private final long creationTime;
|
||||
|
||||
/**
|
||||
* Lease lifetime in milliseconds.
|
||||
*/
|
||||
private volatile long leaseTimeout;
|
||||
|
||||
private boolean expired;
|
||||
|
||||
/**
|
||||
* Functions to be called in case of timeout.
|
||||
*/
|
||||
private List<Callable<Void>> callbacks;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a lease on the specified resource with given timeout.
|
||||
*
|
||||
* @param resource
|
||||
* Resource for which the lease has to be created
|
||||
* @param timeout
|
||||
* Lease lifetime in milliseconds
|
||||
*/
|
||||
public Lease(T resource, long timeout) {
|
||||
this.resource = resource;
|
||||
this.leaseTimeout = timeout;
|
||||
this.callbacks = Collections.synchronizedList(new ArrayList<>());
|
||||
this.creationTime = Time.monotonicNow();
|
||||
this.expired = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the lease has expired, else false.
|
||||
*
|
||||
* @return true if expired, else false
|
||||
*/
|
||||
public boolean hasExpired() {
|
||||
return expired;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback which will be executed in case of timeout. Callbacks
|
||||
* are executed in a separate Thread.
|
||||
*
|
||||
* @param callback
|
||||
* The Callable which has to be executed
|
||||
* @throws LeaseExpiredException
|
||||
* If the lease has already timed out
|
||||
*/
|
||||
public void registerCallBack(Callable<Void> callback)
|
||||
throws LeaseExpiredException {
|
||||
if(hasExpired()) {
|
||||
throw new LeaseExpiredException("Resource: " + resource);
|
||||
}
|
||||
callbacks.add(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time elapsed since the creation of lease.
|
||||
*
|
||||
* @return elapsed time in milliseconds
|
||||
* @throws LeaseExpiredException
|
||||
* If the lease has already timed out
|
||||
*/
|
||||
public long getElapsedTime() throws LeaseExpiredException {
|
||||
if(hasExpired()) {
|
||||
throw new LeaseExpiredException("Resource: " + resource);
|
||||
}
|
||||
return Time.monotonicNow() - creationTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time available before timeout.
|
||||
*
|
||||
* @return remaining time in milliseconds
|
||||
* @throws LeaseExpiredException
|
||||
* If the lease has already timed out
|
||||
*/
|
||||
public long getRemainingTime() throws LeaseExpiredException {
|
||||
if(hasExpired()) {
|
||||
throw new LeaseExpiredException("Resource: " + resource);
|
||||
}
|
||||
return leaseTimeout - getElapsedTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns total lease lifetime.
|
||||
*
|
||||
* @return total lifetime of lease in milliseconds
|
||||
* @throws LeaseExpiredException
|
||||
* If the lease has already timed out
|
||||
*/
|
||||
public long getLeaseLifeTime() throws LeaseExpiredException {
|
||||
if(hasExpired()) {
|
||||
throw new LeaseExpiredException("Resource: " + resource);
|
||||
}
|
||||
return leaseTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renews the lease timeout period.
|
||||
*
|
||||
* @param timeout
|
||||
* Time to be added to the lease in milliseconds
|
||||
* @throws LeaseExpiredException
|
||||
* If the lease has already timed out
|
||||
*/
|
||||
public void renew(long timeout) throws LeaseExpiredException {
|
||||
if(hasExpired()) {
|
||||
throw new LeaseExpiredException("Resource: " + resource);
|
||||
}
|
||||
leaseTimeout += timeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return resource.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if(obj instanceof Lease) {
|
||||
return resource.equals(((Lease) obj).resource);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Lease<" + resource.toString() + ">";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the callbacks to be executed for the lease in case of timeout.
|
||||
*
|
||||
* @return callbacks to be executed
|
||||
*/
|
||||
List<Callable<Void>> getCallbacks() {
|
||||
return callbacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expires/Invalidates the lease.
|
||||
*/
|
||||
void invalidate() {
|
||||
callbacks = null;
|
||||
expired = true;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with this
|
||||
* work for additional information regarding copyright ownership. The ASF
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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.apache.hadoop.ozone.lease;
|
||||
|
||||
/**
|
||||
* This exception represents that there is already a lease acquired on the
|
||||
* same resource.
|
||||
*/
|
||||
public class LeaseAlreadyExistException extends LeaseException {
|
||||
|
||||
/**
|
||||
* Constructs an {@code LeaseAlreadyExistException} with {@code null}
|
||||
* as its error detail message.
|
||||
*/
|
||||
public LeaseAlreadyExistException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an {@code LeaseAlreadyExistException} with the specified
|
||||
* detail message.
|
||||
*
|
||||
* @param message
|
||||
* The detail message (which is saved for later retrieval
|
||||
* by the {@link #getMessage()} method)
|
||||
*/
|
||||
public LeaseAlreadyExistException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with this
|
||||
* work for additional information regarding copyright ownership. The ASF
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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.apache.hadoop.ozone.lease;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* This class is responsible for executing the callbacks of a lease in case of
|
||||
* timeout.
|
||||
*/
|
||||
public class LeaseCallbackExecutor<T> implements Runnable {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Lease.class);
|
||||
|
||||
private final T resource;
|
||||
private final List<Callable<Void>> callbacks;
|
||||
|
||||
/**
|
||||
* Constructs LeaseCallbackExecutor instance with list of callbacks.
|
||||
*
|
||||
* @param resource
|
||||
* The resource for which the callbacks are executed
|
||||
* @param callbacks
|
||||
* Callbacks to be executed by this executor
|
||||
*/
|
||||
public LeaseCallbackExecutor(T resource, List<Callable<Void>> callbacks) {
|
||||
this.resource = resource;
|
||||
this.callbacks = callbacks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if(LOG.isDebugEnabled()) {
|
||||
LOG.debug("Executing callbacks for lease on {}", resource);
|
||||
}
|
||||
for(Callable<Void> callback : callbacks) {
|
||||
try {
|
||||
callback.call();
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Exception while executing callback for lease on {}",
|
||||
resource, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with this
|
||||
* work for additional information regarding copyright ownership. The ASF
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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.apache.hadoop.ozone.lease;
|
||||
|
||||
/**
|
||||
* This exception represents all lease related exceptions.
|
||||
*/
|
||||
public class LeaseException extends Exception {
|
||||
|
||||
/**
|
||||
* Constructs an {@code LeaseException} with {@code null}
|
||||
* as its error detail message.
|
||||
*/
|
||||
public LeaseException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an {@code LeaseException} with the specified
|
||||
* detail message.
|
||||
*
|
||||
* @param message
|
||||
* The detail message (which is saved for later retrieval
|
||||
* by the {@link #getMessage()} method)
|
||||
*/
|
||||
public LeaseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with this
|
||||
* work for additional information regarding copyright ownership. The ASF
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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.apache.hadoop.ozone.lease;
|
||||
|
||||
/**
|
||||
* This exception represents that the lease that is being accessed has expired.
|
||||
*/
|
||||
public class LeaseExpiredException extends LeaseException {
|
||||
|
||||
/**
|
||||
* Constructs an {@code LeaseExpiredException} with {@code null}
|
||||
* as its error detail message.
|
||||
*/
|
||||
public LeaseExpiredException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an {@code LeaseExpiredException} with the specified
|
||||
* detail message.
|
||||
*
|
||||
* @param message
|
||||
* The detail message (which is saved for later retrieval
|
||||
* by the {@link #getMessage()} method)
|
||||
*/
|
||||
public LeaseExpiredException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,247 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with this
|
||||
* work for additional information regarding copyright ownership. The ASF
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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.apache.hadoop.ozone.lease;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* LeaseManager is someone who can provide you leases based on your
|
||||
* requirement. If you want to return the lease back before it expires,
|
||||
* you can give it back to Lease Manager. He is the one responsible for
|
||||
* the lifecycle of leases. The resource for which lease is created
|
||||
* should have proper {@code equals} method implementation, resource
|
||||
* equality is checked while the lease is created.
|
||||
*
|
||||
* @param <T> Type of leases that this lease manager can create
|
||||
*/
|
||||
public class LeaseManager<T> {
|
||||
|
||||
private static final Logger LOG =
|
||||
LoggerFactory.getLogger(LeaseManager.class);
|
||||
|
||||
private final long defaultTimeout;
|
||||
private Map<T, Lease<T>> activeLeases;
|
||||
private LeaseMonitor leaseMonitor;
|
||||
private Thread leaseMonitorThread;
|
||||
private boolean isRunning;
|
||||
|
||||
/**
|
||||
* Creates an instance of lease manager.
|
||||
*
|
||||
* @param defaultTimeout
|
||||
* Default timeout value to be used for lease creation.
|
||||
*/
|
||||
public LeaseManager(long defaultTimeout) {
|
||||
this.defaultTimeout = defaultTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the lease manager service.
|
||||
*/
|
||||
public void start() {
|
||||
LOG.debug("Starting LeaseManager service");
|
||||
activeLeases = new ConcurrentHashMap<>();
|
||||
leaseMonitor = new LeaseMonitor();
|
||||
leaseMonitorThread = new Thread(leaseMonitor);
|
||||
leaseMonitorThread.setName("LeaseManager#LeaseMonitor");
|
||||
leaseMonitorThread.setDaemon(true);
|
||||
leaseMonitorThread.setUncaughtExceptionHandler((thread, throwable) -> {
|
||||
// Let us just restart this thread after logging an error.
|
||||
// if this thread is not running we cannot handle Lease expiry.
|
||||
LOG.error("LeaseMonitor thread encountered an error. Thread: {}",
|
||||
thread.toString(), throwable);
|
||||
leaseMonitorThread.start();
|
||||
});
|
||||
LOG.debug("Starting LeaseManager#LeaseMonitor Thread");
|
||||
leaseMonitorThread.start();
|
||||
isRunning = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a lease for the specified resource with default timeout.
|
||||
*
|
||||
* @param resource
|
||||
* Resource for which lease has to be created
|
||||
* @throws LeaseAlreadyExistException
|
||||
* If there is already a lease on the resource
|
||||
*/
|
||||
public synchronized Lease<T> acquire(T resource)
|
||||
throws LeaseAlreadyExistException {
|
||||
return acquire(resource, defaultTimeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a lease for the specified resource with the timeout provided.
|
||||
*
|
||||
* @param resource
|
||||
* Resource for which lease has to be created
|
||||
* @param timeout
|
||||
* The timeout in milliseconds which has to be set on the lease
|
||||
* @throws LeaseAlreadyExistException
|
||||
* If there is already a lease on the resource
|
||||
*/
|
||||
public synchronized Lease<T> acquire(T resource, long timeout)
|
||||
throws LeaseAlreadyExistException {
|
||||
checkStatus();
|
||||
if(LOG.isDebugEnabled()) {
|
||||
LOG.debug("Acquiring lease on {} for {} milliseconds", resource, timeout);
|
||||
}
|
||||
if(activeLeases.containsKey(resource)) {
|
||||
throw new LeaseAlreadyExistException("Resource: " + resource);
|
||||
}
|
||||
Lease<T> lease = new Lease<>(resource, timeout);
|
||||
activeLeases.put(resource, lease);
|
||||
leaseMonitorThread.interrupt();
|
||||
return lease;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a lease associated with the specified resource.
|
||||
*
|
||||
* @param resource
|
||||
* Resource for which the lease has to be returned
|
||||
* @throws LeaseNotFoundException
|
||||
* If there is no active lease on the resource
|
||||
*/
|
||||
public Lease<T> get(T resource) throws LeaseNotFoundException {
|
||||
checkStatus();
|
||||
Lease<T> lease = activeLeases.get(resource);
|
||||
if(lease != null) {
|
||||
return lease;
|
||||
}
|
||||
throw new LeaseNotFoundException("Resource: " + resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases the lease associated with the specified resource.
|
||||
*
|
||||
* @param resource
|
||||
* The for which the lease has to be released
|
||||
* @throws LeaseNotFoundException
|
||||
* If there is no active lease on the resource
|
||||
*/
|
||||
public synchronized void release(T resource)
|
||||
throws LeaseNotFoundException {
|
||||
checkStatus();
|
||||
if(LOG.isDebugEnabled()) {
|
||||
LOG.debug("Releasing lease on {}", resource);
|
||||
}
|
||||
Lease<T> lease = activeLeases.remove(resource);
|
||||
if(lease == null) {
|
||||
throw new LeaseNotFoundException("Resource: " + resource);
|
||||
}
|
||||
lease.invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuts down the LeaseManager and releases the resources. All the active
|
||||
* {@link Lease} will be released (callbacks on leases will not be
|
||||
* executed).
|
||||
*/
|
||||
public void shutdown() {
|
||||
checkStatus();
|
||||
LOG.debug("Shutting down LeaseManager service");
|
||||
leaseMonitor.disable();
|
||||
leaseMonitorThread.interrupt();
|
||||
for(T resource : activeLeases.keySet()) {
|
||||
try {
|
||||
release(resource);
|
||||
} catch(LeaseNotFoundException ex) {
|
||||
//Ignore the exception, someone might have released the lease
|
||||
}
|
||||
}
|
||||
isRunning = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws {@link LeaseManagerNotRunningException} if the service is not
|
||||
* running.
|
||||
*/
|
||||
private void checkStatus() {
|
||||
if(!isRunning) {
|
||||
throw new LeaseManagerNotRunningException("LeaseManager not running.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitors the leases and expires them based on the timeout, also
|
||||
* responsible for executing the callbacks of expired leases.
|
||||
*/
|
||||
private final class LeaseMonitor implements Runnable {
|
||||
|
||||
private boolean monitor = true;
|
||||
private ExecutorService executorService;
|
||||
|
||||
private LeaseMonitor() {
|
||||
this.monitor = true;
|
||||
this.executorService = Executors.newCachedThreadPool();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while(monitor) {
|
||||
LOG.debug("LeaseMonitor: checking for lease expiry");
|
||||
long sleepTime = Long.MAX_VALUE;
|
||||
|
||||
for (T resource : activeLeases.keySet()) {
|
||||
try {
|
||||
Lease<T> lease = get(resource);
|
||||
long remainingTime = lease.getRemainingTime();
|
||||
if (remainingTime <= 0) {
|
||||
//Lease has timed out
|
||||
List<Callable<Void>> leaseCallbacks = lease.getCallbacks();
|
||||
release(resource);
|
||||
executorService.execute(
|
||||
new LeaseCallbackExecutor(resource, leaseCallbacks));
|
||||
} else {
|
||||
sleepTime = remainingTime > sleepTime ?
|
||||
sleepTime : remainingTime;
|
||||
}
|
||||
} catch (LeaseNotFoundException | LeaseExpiredException ex) {
|
||||
//Ignore the exception, someone might have released the lease
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if(!Thread.interrupted()) {
|
||||
Thread.sleep(sleepTime);
|
||||
}
|
||||
} catch (InterruptedException ignored) {
|
||||
// This means a new lease is added to activeLeases.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables lease monitor, next interrupt call on the thread
|
||||
* will stop lease monitor.
|
||||
*/
|
||||
public void disable() {
|
||||
monitor = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with this
|
||||
* work for additional information regarding copyright ownership. The ASF
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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.apache.hadoop.ozone.lease;
|
||||
|
||||
/**
|
||||
* This exception represents that there LeaseManager service is not running.
|
||||
*/
|
||||
public class LeaseManagerNotRunningException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Constructs an {@code LeaseManagerNotRunningException} with {@code null}
|
||||
* as its error detail message.
|
||||
*/
|
||||
public LeaseManagerNotRunningException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an {@code LeaseManagerNotRunningException} with the specified
|
||||
* detail message.
|
||||
*
|
||||
* @param message
|
||||
* The detail message (which is saved for later retrieval
|
||||
* by the {@link #getMessage()} method)
|
||||
*/
|
||||
public LeaseManagerNotRunningException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with this
|
||||
* work for additional information regarding copyright ownership. The ASF
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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.apache.hadoop.ozone.lease;
|
||||
|
||||
/**
|
||||
* This exception represents that the lease that is being accessed does not
|
||||
* exist.
|
||||
*/
|
||||
public class LeaseNotFoundException extends LeaseException {
|
||||
|
||||
/**
|
||||
* Constructs an {@code LeaseNotFoundException} with {@code null}
|
||||
* as its error detail message.
|
||||
*/
|
||||
public LeaseNotFoundException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an {@code LeaseNotFoundException} with the specified
|
||||
* detail message.
|
||||
*
|
||||
* @param message
|
||||
* The detail message (which is saved for later retrieval
|
||||
* by the {@link #getMessage()} method)
|
||||
*/
|
||||
public LeaseNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with this
|
||||
* work for additional information regarding copyright ownership. The ASF
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A generic lease management API which can be used if a service
|
||||
* needs any kind of lease management.
|
||||
*/
|
||||
|
||||
package org.apache.hadoop.ozone.lease;
|
||||
/*
|
||||
This package contains lease management related classes.
|
||||
*/
|
|
@ -0,0 +1,374 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with this
|
||||
* work for additional information regarding copyright ownership. The ASF
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A generic lease management API which can be used if a service
|
||||
* needs any kind of lease management.
|
||||
*/
|
||||
|
||||
package org.apache.hadoop.ozone.lease;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Test class to check functionality and consistency of LeaseManager.
|
||||
*/
|
||||
public class TestLeaseManager {
|
||||
|
||||
@Rule
|
||||
public ExpectedException exception = ExpectedException.none();
|
||||
|
||||
/**
|
||||
* Dummy resource on which leases can be acquired.
|
||||
*/
|
||||
private final class DummyResource {
|
||||
|
||||
private final String name;
|
||||
|
||||
private DummyResource(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return name.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if(obj instanceof DummyResource) {
|
||||
return name.equals(((DummyResource) obj).name);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLeaseAcquireAndRelease() throws LeaseException {
|
||||
//It is assumed that the test case execution won't take more than 5 seconds,
|
||||
//if it takes more time increase the defaultTimeout value of LeaseManager.
|
||||
LeaseManager<DummyResource> manager = new LeaseManager<>(5000);
|
||||
manager.start();
|
||||
DummyResource resourceOne = new DummyResource("one");
|
||||
DummyResource resourceTwo = new DummyResource("two");
|
||||
DummyResource resourceThree = new DummyResource("three");
|
||||
Lease<DummyResource> leaseOne = manager.acquire(resourceOne);
|
||||
Lease<DummyResource> leaseTwo = manager.acquire(resourceTwo);
|
||||
Lease<DummyResource> leaseThree = manager.acquire(resourceThree);
|
||||
Assert.assertEquals(leaseOne, manager.get(resourceOne));
|
||||
Assert.assertEquals(leaseTwo, manager.get(resourceTwo));
|
||||
Assert.assertEquals(leaseThree, manager.get(resourceThree));
|
||||
Assert.assertFalse(leaseOne.hasExpired());
|
||||
Assert.assertFalse(leaseTwo.hasExpired());
|
||||
Assert.assertFalse(leaseThree.hasExpired());
|
||||
//The below releases should not throw LeaseNotFoundException.
|
||||
manager.release(resourceOne);
|
||||
manager.release(resourceTwo);
|
||||
manager.release(resourceThree);
|
||||
Assert.assertTrue(leaseOne.hasExpired());
|
||||
Assert.assertTrue(leaseTwo.hasExpired());
|
||||
Assert.assertTrue(leaseThree.hasExpired());
|
||||
manager.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLeaseAlreadyExist() throws LeaseException {
|
||||
LeaseManager<DummyResource> manager = new LeaseManager<>(5000);
|
||||
manager.start();
|
||||
DummyResource resourceOne = new DummyResource("one");
|
||||
DummyResource resourceTwo = new DummyResource("two");
|
||||
Lease<DummyResource> leaseOne = manager.acquire(resourceOne);
|
||||
Lease<DummyResource> leaseTwo = manager.acquire(resourceTwo);
|
||||
Assert.assertEquals(leaseOne, manager.get(resourceOne));
|
||||
Assert.assertEquals(leaseTwo, manager.get(resourceTwo));
|
||||
|
||||
exception.expect(LeaseAlreadyExistException.class);
|
||||
exception.expectMessage("Resource: " + resourceOne);
|
||||
manager.acquire(resourceOne);
|
||||
|
||||
manager.release(resourceOne);
|
||||
manager.release(resourceTwo);
|
||||
manager.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLeaseNotFound() throws LeaseException, InterruptedException {
|
||||
LeaseManager<DummyResource> manager = new LeaseManager<>(5000);
|
||||
manager.start();
|
||||
DummyResource resourceOne = new DummyResource("one");
|
||||
DummyResource resourceTwo = new DummyResource("two");
|
||||
DummyResource resourceThree = new DummyResource("three");
|
||||
|
||||
//Case 1: lease was never acquired.
|
||||
exception.expect(LeaseNotFoundException.class);
|
||||
exception.expectMessage("Resource: " + resourceOne);
|
||||
manager.get(resourceOne);
|
||||
|
||||
//Case 2: lease is acquired and released.
|
||||
Lease<DummyResource> leaseTwo = manager.acquire(resourceTwo);
|
||||
Assert.assertEquals(leaseTwo, manager.get(resourceTwo));
|
||||
Assert.assertFalse(leaseTwo.hasExpired());
|
||||
manager.release(resourceTwo);
|
||||
Assert.assertTrue(leaseTwo.hasExpired());
|
||||
exception.expect(LeaseNotFoundException.class);
|
||||
exception.expectMessage("Resource: " + resourceTwo);
|
||||
manager.get(resourceTwo);
|
||||
|
||||
//Case 3: lease acquired and timed out.
|
||||
Lease<DummyResource> leaseThree = manager.acquire(resourceThree);
|
||||
Assert.assertEquals(leaseThree, manager.get(resourceThree));
|
||||
Assert.assertFalse(leaseThree.hasExpired());
|
||||
long sleepTime = leaseThree.getRemainingTime() + 5;
|
||||
try {
|
||||
Thread.sleep(sleepTime);
|
||||
} catch (InterruptedException ex) {
|
||||
//even in case of interrupt we have to wait till lease times out.
|
||||
Thread.sleep(sleepTime);
|
||||
}
|
||||
Assert.assertTrue(leaseThree.hasExpired());
|
||||
exception.expect(LeaseNotFoundException.class);
|
||||
exception.expectMessage("Resource: " + resourceThree);
|
||||
manager.get(resourceThree);
|
||||
manager.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomLeaseTimeout() throws LeaseException {
|
||||
LeaseManager<DummyResource> manager = new LeaseManager<>(5000);
|
||||
manager.start();
|
||||
DummyResource resourceOne = new DummyResource("one");
|
||||
DummyResource resourceTwo = new DummyResource("two");
|
||||
DummyResource resourceThree = new DummyResource("three");
|
||||
Lease<DummyResource> leaseOne = manager.acquire(resourceOne);
|
||||
Lease<DummyResource> leaseTwo = manager.acquire(resourceTwo, 10000);
|
||||
Lease<DummyResource> leaseThree = manager.acquire(resourceThree, 50000);
|
||||
Assert.assertEquals(leaseOne, manager.get(resourceOne));
|
||||
Assert.assertEquals(leaseTwo, manager.get(resourceTwo));
|
||||
Assert.assertEquals(leaseThree, manager.get(resourceThree));
|
||||
Assert.assertFalse(leaseOne.hasExpired());
|
||||
Assert.assertFalse(leaseTwo.hasExpired());
|
||||
Assert.assertFalse(leaseThree.hasExpired());
|
||||
Assert.assertEquals(5000, leaseOne.getLeaseLifeTime());
|
||||
Assert.assertEquals(10000, leaseTwo.getLeaseLifeTime());
|
||||
Assert.assertEquals(50000, leaseThree.getLeaseLifeTime());
|
||||
// Releasing of leases is done in shutdown, so don't have to worry about
|
||||
// lease release
|
||||
manager.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLeaseCallback() throws LeaseException, InterruptedException {
|
||||
Map<DummyResource, String> leaseStatus = new HashMap<>();
|
||||
LeaseManager<DummyResource> manager = new LeaseManager<>(5000);
|
||||
manager.start();
|
||||
DummyResource resourceOne = new DummyResource("one");
|
||||
Lease<DummyResource> leaseOne = manager.acquire(resourceOne);
|
||||
leaseStatus.put(resourceOne, "lease in use");
|
||||
leaseOne.registerCallBack(() -> {
|
||||
leaseStatus.put(resourceOne, "lease expired");
|
||||
return null;
|
||||
});
|
||||
// wait for lease to expire
|
||||
long sleepTime = leaseOne.getRemainingTime() + 5;
|
||||
try {
|
||||
Thread.sleep(sleepTime);
|
||||
} catch (InterruptedException ex) {
|
||||
//even in case of interrupt we have to wait till lease times out.
|
||||
Thread.sleep(sleepTime);
|
||||
}
|
||||
Assert.assertTrue(leaseOne.hasExpired());
|
||||
exception.expect(LeaseNotFoundException.class);
|
||||
exception.expectMessage("Resource: " + resourceOne);
|
||||
manager.get(resourceOne);
|
||||
// check if callback has been executed
|
||||
Assert.assertEquals("lease expired", leaseStatus.get(resourceOne));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCallbackExecutionInCaseOfLeaseRelease()
|
||||
throws LeaseException, InterruptedException {
|
||||
// Callbacks should not be executed in case of lease release
|
||||
Map<DummyResource, String> leaseStatus = new HashMap<>();
|
||||
LeaseManager<DummyResource> manager = new LeaseManager<>(5000);
|
||||
manager.start();
|
||||
DummyResource resourceOne = new DummyResource("one");
|
||||
Lease<DummyResource> leaseOne = manager.acquire(resourceOne);
|
||||
leaseStatus.put(resourceOne, "lease in use");
|
||||
leaseOne.registerCallBack(() -> {
|
||||
leaseStatus.put(resourceOne, "lease expired");
|
||||
return null;
|
||||
});
|
||||
leaseStatus.put(resourceOne, "lease released");
|
||||
manager.release(resourceOne);
|
||||
Assert.assertTrue(leaseOne.hasExpired());
|
||||
exception.expect(LeaseNotFoundException.class);
|
||||
exception.expectMessage("Resource: " + resourceOne);
|
||||
manager.get(resourceOne);
|
||||
Assert.assertEquals("lease released", leaseStatus.get(resourceOne));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLeaseCallbackWithMultipleLeases()
|
||||
throws LeaseException, InterruptedException {
|
||||
Map<DummyResource, String> leaseStatus = new HashMap<>();
|
||||
LeaseManager<DummyResource> manager = new LeaseManager<>(5000);
|
||||
manager.start();
|
||||
DummyResource resourceOne = new DummyResource("one");
|
||||
DummyResource resourceTwo = new DummyResource("two");
|
||||
DummyResource resourceThree = new DummyResource("three");
|
||||
DummyResource resourceFour = new DummyResource("four");
|
||||
DummyResource resourceFive = new DummyResource("five");
|
||||
Lease<DummyResource> leaseOne = manager.acquire(resourceOne);
|
||||
Lease<DummyResource> leaseTwo = manager.acquire(resourceTwo);
|
||||
Lease<DummyResource> leaseThree = manager.acquire(resourceThree);
|
||||
Lease<DummyResource> leaseFour = manager.acquire(resourceFour);
|
||||
Lease<DummyResource> leaseFive = manager.acquire(resourceFive);
|
||||
leaseStatus.put(resourceOne, "lease in use");
|
||||
leaseStatus.put(resourceTwo, "lease in use");
|
||||
leaseStatus.put(resourceThree, "lease in use");
|
||||
leaseStatus.put(resourceFour, "lease in use");
|
||||
leaseStatus.put(resourceFive, "lease in use");
|
||||
leaseOne.registerCallBack(() -> {
|
||||
leaseStatus.put(resourceOne, "lease expired");
|
||||
return null;
|
||||
});
|
||||
leaseTwo.registerCallBack(() -> {
|
||||
leaseStatus.put(resourceTwo, "lease expired");
|
||||
return null;
|
||||
});
|
||||
leaseThree.registerCallBack(() -> {
|
||||
leaseStatus.put(resourceThree, "lease expired");
|
||||
return null;
|
||||
});
|
||||
leaseFour.registerCallBack(() -> {
|
||||
leaseStatus.put(resourceFour, "lease expired");
|
||||
return null;
|
||||
});
|
||||
leaseFive.registerCallBack(() -> {
|
||||
leaseStatus.put(resourceFive, "lease expired");
|
||||
return null;
|
||||
});
|
||||
|
||||
// release lease one, two and three
|
||||
leaseStatus.put(resourceOne, "lease released");
|
||||
manager.release(resourceOne);
|
||||
leaseStatus.put(resourceTwo, "lease released");
|
||||
manager.release(resourceTwo);
|
||||
leaseStatus.put(resourceThree, "lease released");
|
||||
manager.release(resourceThree);
|
||||
|
||||
// wait for other leases to expire
|
||||
long sleepTime = leaseFive.getRemainingTime() + 10;
|
||||
|
||||
try {
|
||||
Thread.sleep(sleepTime);
|
||||
} catch (InterruptedException ex) {
|
||||
//even in case of interrupt we have to wait till lease times out.
|
||||
Thread.sleep(sleepTime);
|
||||
}
|
||||
Assert.assertTrue(leaseOne.hasExpired());
|
||||
Assert.assertTrue(leaseTwo.hasExpired());
|
||||
Assert.assertTrue(leaseThree.hasExpired());
|
||||
Assert.assertTrue(leaseFour.hasExpired());
|
||||
Assert.assertTrue(leaseFive.hasExpired());
|
||||
|
||||
Assert.assertEquals("lease released", leaseStatus.get(resourceOne));
|
||||
Assert.assertEquals("lease released", leaseStatus.get(resourceTwo));
|
||||
Assert.assertEquals("lease released", leaseStatus.get(resourceThree));
|
||||
Assert.assertEquals("lease expired", leaseStatus.get(resourceFour));
|
||||
Assert.assertEquals("lease expired", leaseStatus.get(resourceFive));
|
||||
manager.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReuseReleasedLease() throws LeaseException {
|
||||
LeaseManager<DummyResource> manager = new LeaseManager<>(5000);
|
||||
manager.start();
|
||||
DummyResource resourceOne = new DummyResource("one");
|
||||
Lease<DummyResource> leaseOne = manager.acquire(resourceOne);
|
||||
Assert.assertEquals(leaseOne, manager.get(resourceOne));
|
||||
Assert.assertFalse(leaseOne.hasExpired());
|
||||
|
||||
manager.release(resourceOne);
|
||||
Assert.assertTrue(leaseOne.hasExpired());
|
||||
|
||||
Lease<DummyResource> sameResourceLease = manager.acquire(resourceOne);
|
||||
Assert.assertEquals(sameResourceLease, manager.get(resourceOne));
|
||||
Assert.assertFalse(sameResourceLease.hasExpired());
|
||||
|
||||
manager.release(resourceOne);
|
||||
Assert.assertTrue(sameResourceLease.hasExpired());
|
||||
manager.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReuseTimedOutLease()
|
||||
throws LeaseException, InterruptedException {
|
||||
LeaseManager<DummyResource> manager = new LeaseManager<>(5000);
|
||||
manager.start();
|
||||
DummyResource resourceOne = new DummyResource("one");
|
||||
Lease<DummyResource> leaseOne = manager.acquire(resourceOne);
|
||||
Assert.assertEquals(leaseOne, manager.get(resourceOne));
|
||||
Assert.assertFalse(leaseOne.hasExpired());
|
||||
|
||||
// wait for lease to expire
|
||||
long sleepTime = leaseOne.getRemainingTime() + 5;
|
||||
try {
|
||||
Thread.sleep(sleepTime);
|
||||
} catch (InterruptedException ex) {
|
||||
//even in case of interrupt we have to wait till lease times out.
|
||||
Thread.sleep(sleepTime);
|
||||
}
|
||||
Assert.assertTrue(leaseOne.hasExpired());
|
||||
|
||||
Lease<DummyResource> sameResourceLease = manager.acquire(resourceOne);
|
||||
Assert.assertEquals(sameResourceLease, manager.get(resourceOne));
|
||||
Assert.assertFalse(sameResourceLease.hasExpired());
|
||||
|
||||
manager.release(resourceOne);
|
||||
Assert.assertTrue(sameResourceLease.hasExpired());
|
||||
manager.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRenewLease() throws LeaseException, InterruptedException {
|
||||
LeaseManager<DummyResource> manager = new LeaseManager<>(5000);
|
||||
manager.start();
|
||||
DummyResource resourceOne = new DummyResource("one");
|
||||
Lease<DummyResource> leaseOne = manager.acquire(resourceOne);
|
||||
Assert.assertEquals(leaseOne, manager.get(resourceOne));
|
||||
Assert.assertFalse(leaseOne.hasExpired());
|
||||
|
||||
// add 5 more seconds to the lease
|
||||
leaseOne.renew(5000);
|
||||
|
||||
Thread.sleep(5000);
|
||||
|
||||
// lease should still be active
|
||||
Assert.assertEquals(leaseOne, manager.get(resourceOne));
|
||||
Assert.assertFalse(leaseOne.hasExpired());
|
||||
manager.release(resourceOne);
|
||||
manager.shutdown();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with this
|
||||
* work for additional information regarding copyright ownership. The ASF
|
||||
* 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.apache.hadoop.ozone.lease;
|
||||
/*
|
||||
This package contains lease management unit test classes.
|
||||
*/
|
Loading…
Reference in New Issue