org.opendaylight.genius.alivenessmonitor.internal.AlivenessMonitor.java Source code

Java tutorial

Introduction

Here is the source code for org.opendaylight.genius.alivenessmonitor.internal.AlivenessMonitor.java

Source

/*
 * Copyright (c) 2016 Ericsson India Global Services Pvt Ltd. and others.  All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 */
package org.opendaylight.genius.alivenessmonitor.internal;

import static org.opendaylight.genius.alivenessmonitor.internal.AlivenessMonitorUtil.getInterfaceMonitorMapId;
import static org.opendaylight.genius.alivenessmonitor.internal.AlivenessMonitorUtil.getMonitorMapId;
import static org.opendaylight.genius.alivenessmonitor.internal.AlivenessMonitorUtil.getMonitorProfileId;
import static org.opendaylight.genius.alivenessmonitor.internal.AlivenessMonitorUtil.getMonitorStateId;
import static org.opendaylight.genius.alivenessmonitor.internal.AlivenessMonitorUtil.getMonitoringInfoId;

import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.JdkFutureAdapters;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.opendaylight.controller.liblldp.NetUtils;
import org.opendaylight.controller.liblldp.Packet;
import org.opendaylight.controller.liblldp.PacketException;
import org.opendaylight.controller.md.sal.binding.api.DataBroker;
import org.opendaylight.controller.md.sal.binding.api.NotificationPublishService;
import org.opendaylight.controller.md.sal.binding.api.NotificationService;
import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction;
import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
import org.opendaylight.genius.alivenessmonitor.protocols.AlivenessProtocolHandler;
import org.opendaylight.genius.alivenessmonitor.protocols.AlivenessProtocolHandlerRegistry;
import org.opendaylight.genius.mdsalutil.packet.Ethernet;
import org.opendaylight.infrautils.utils.concurrent.ThreadFactoryProvider;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.AlivenessMonitorService;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.EtherTypes;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.LivenessState;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.MonitorEvent;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.MonitorEventBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.MonitorPauseInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.MonitorProfileCreateInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.MonitorProfileCreateOutput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.MonitorProfileCreateOutputBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.MonitorProfileDeleteInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.MonitorProfileGetInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.MonitorProfileGetOutput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.MonitorProfileGetOutputBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.MonitorStartInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.MonitorStartOutput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.MonitorStartOutputBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.MonitorStatus;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.MonitorStopInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.MonitorUnpauseInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.MonitoringMode;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411._interface.monitor.map.InterfaceMonitorEntry;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411._interface.monitor.map.InterfaceMonitorEntryBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411._interface.monitor.map.InterfaceMonitorEntryKey;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.endpoint.EndpointType;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.endpoint.endpoint.type.Interface;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.endpoint.endpoint.type.IpAddress;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.monitor.configs.MonitoringInfo;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.monitor.configs.MonitoringInfoBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.monitor.event.EventData;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.monitor.event.EventDataBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.monitor.profile.create.input.Profile;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.monitor.profiles.MonitorProfile;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.monitor.profiles.MonitorProfileBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.monitor.start.input.Config;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.monitorid.key.map.MonitoridKeyEntry;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.monitorid.key.map.MonitoridKeyEntryBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.monitoring.states.MonitoringState;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.monitoring.states.MonitoringStateBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.AllocateIdInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.AllocateIdInputBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.AllocateIdOutput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.CreateIdPoolInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.CreateIdPoolInputBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.IdManagerService;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.ReleaseIdInput;
import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.ReleaseIdInputBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketInReason;
import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketProcessingListener;
import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketReceived;
import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.SendToController;
import org.opendaylight.yangtools.concepts.ListenerRegistration;
import org.opendaylight.yangtools.yang.binding.DataObject;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
import org.opendaylight.yangtools.yang.common.RpcError.ErrorType;
import org.opendaylight.yangtools.yang.common.RpcResult;
import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Singleton
public class AlivenessMonitor
        implements AlivenessMonitorService, PacketProcessingListener, InterfaceStateListener, AutoCloseable {

    private static final Logger LOG = LoggerFactory.getLogger(AlivenessMonitor.class);

    private static final int THREAD_POOL_SIZE = 4;
    private static final boolean INTERRUPT_TASK = true;
    private static final int NO_DELAY = 0;
    private static final Long INITIAL_COUNT = 0L;
    private static final boolean CREATE_MISSING_PARENT = true;
    private static final int INVALID_ID = 0;

    private final DataBroker dataBroker;
    private final IdManagerService idManager;
    private final NotificationPublishService notificationPublishService;
    private final NotificationService notificationService;
    private final AlivenessProtocolHandlerRegistry alivenessProtocolHandlerRegistry;
    private final ConcurrentMap<Long, ScheduledFuture<?>> monitoringTasks;
    private final ScheduledExecutorService monitorService;
    private final ExecutorService callbackExecutorService;
    private final LoadingCache<Long, String> monitorIdKeyCache;
    private final ListenerRegistration<AlivenessMonitor> listenerRegistration;
    // TODO clean up: visibility package local instead of private because accessed in HwVtepTunnelsStateHandler
    final ConcurrentMap<String, Semaphore> lockMap = new ConcurrentHashMap<>();

    private static class FutureCallbackImpl implements FutureCallback<Void> {
        private final String message;

        FutureCallbackImpl(String message) {
            this.message = message;
        }

        @Override
        public void onFailure(Throwable error) {
            LOG.warn("Error in Datastore operation - {}", message, error);
        }

        @Override
        public void onSuccess(Void result) {
            LOG.debug("Success in Datastore operation - {}", message);
        }
    }

    private class AlivenessMonitorTask implements Runnable {
        private final MonitoringInfo monitoringInfo;

        AlivenessMonitorTask(MonitoringInfo monitoringInfo) {
            this.monitoringInfo = monitoringInfo;
        }

        @Override
        public void run() {
            LOG.trace("send monitor packet - {}", monitoringInfo);
            sendMonitorPacket(monitoringInfo);
        }
    }

    @Inject
    public AlivenessMonitor(final DataBroker dataBroker, final IdManagerService idManager,
            final NotificationPublishService notificationPublishService,
            final NotificationService notificationService,
            AlivenessProtocolHandlerRegistry alivenessProtocolHandlerRegistry) {
        this.dataBroker = dataBroker;
        this.idManager = idManager;
        this.notificationPublishService = notificationPublishService;
        this.notificationService = notificationService;
        this.alivenessProtocolHandlerRegistry = alivenessProtocolHandlerRegistry;

        monitorService = Executors.newScheduledThreadPool(THREAD_POOL_SIZE,
                ThreadFactoryProvider.builder().namePrefix("Aliveness Monitoring Task").logger(LOG).build().get());
        callbackExecutorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE,
                ThreadFactoryProvider.builder().namePrefix("Aliveness Callback Handler").logger(LOG).build().get());
        monitoringTasks = new ConcurrentHashMap<>();

        createIdPool();
        monitorIdKeyCache = CacheBuilder.newBuilder().build(new CacheLoader<Long, String>() {
            @Override
            public String load(Long monitorId) {
                return read(LogicalDatastoreType.OPERATIONAL, getMonitorMapId(monitorId))
                        .transform(MonitoridKeyEntry::getMonitorKey).orNull();
            }
        });

        listenerRegistration = notificationService.registerNotificationListener(this);
        LOG.info("{} started", getClass().getSimpleName());
    }

    @Override
    @PreDestroy
    public void close() {
        monitorIdKeyCache.cleanUp();
        monitorService.shutdown();
        callbackExecutorService.shutdown();
        if (listenerRegistration != null) {
            listenerRegistration.close();
        }
        LOG.info("{} close", getClass().getSimpleName());
    }

    private ThreadFactory getMonitoringThreadFactory(String threadNameFormat) {
        ThreadFactoryBuilder builder = new ThreadFactoryBuilder();
        builder.setNameFormat(threadNameFormat);
        builder.setUncaughtExceptionHandler(
                (thread, ex) -> LOG.error("Received Uncaught Exception event in Thread: {}", thread.getName(), ex));
        return builder.build();
    }

    private void createIdPool() {
        CreateIdPoolInput createPool = new CreateIdPoolInputBuilder()
                .setPoolName(AlivenessMonitorConstants.MONITOR_IDPOOL_NAME)
                .setLow(AlivenessMonitorConstants.MONITOR_IDPOOL_START)
                .setHigh(AlivenessMonitorConstants.MONITOR_IDPOOL_SIZE).build();
        Future<RpcResult<Void>> result = idManager.createIdPool(createPool);
        Futures.addCallback(JdkFutureAdapters.listenInPoolThread(result), new FutureCallback<RpcResult<Void>>() {

            @Override
            public void onFailure(Throwable error) {
                LOG.error("Failed to create idPool for Aliveness Monitor Service", error);
            }

            @Override
            public void onSuccess(RpcResult<Void> result) {
                if (result.isSuccessful()) {
                    LOG.debug("Created IdPool for Aliveness Monitor Service");
                } else {
                    LOG.error("RPC to create Idpool failed {}", result.getErrors());
                }
            }
        });
    }

    private int getUniqueId(final String idKey) {
        AllocateIdInput getIdInput = new AllocateIdInputBuilder()
                .setPoolName(AlivenessMonitorConstants.MONITOR_IDPOOL_NAME).setIdKey(idKey).build();

        Future<RpcResult<AllocateIdOutput>> result = idManager.allocateId(getIdInput);

        try {
            RpcResult<AllocateIdOutput> rpcResult = result.get();
            if (rpcResult.isSuccessful()) {
                return rpcResult.getResult().getIdValue().intValue();
            } else {
                LOG.warn("RPC Call to Get Unique Id returned with Errors {}", rpcResult.getErrors());
            }
        } catch (InterruptedException | ExecutionException e) {
            LOG.warn("Exception when getting Unique Id for key {}", idKey, e);
        }
        return INVALID_ID;
    }

    private void releaseId(String idKey) {
        ReleaseIdInput idInput = new ReleaseIdInputBuilder()
                .setPoolName(AlivenessMonitorConstants.MONITOR_IDPOOL_NAME).setIdKey(idKey).build();
        try {
            Future<RpcResult<Void>> result = idManager.releaseId(idInput);
            RpcResult<Void> rpcResult = result.get();
            if (!rpcResult.isSuccessful()) {
                LOG.warn("RPC Call to release Id {} with Key {} returned with Errors {}", idKey,
                        rpcResult.getErrors());
            }
        } catch (InterruptedException | ExecutionException e) {
            LOG.warn("Exception when releasing Id for key {}", idKey, e);
        }
    }

    @Override
    public void onPacketReceived(PacketReceived packetReceived) {
        Class<? extends PacketInReason> pktInReason = packetReceived.getPacketInReason();
        if (LOG.isTraceEnabled()) {
            LOG.trace("Packet Received {}", packetReceived);
        }

        if (pktInReason == SendToController.class) {
            Packet packetInFormatted;
            byte[] data = packetReceived.getPayload();
            Ethernet res = new Ethernet();

            try {
                packetInFormatted = res.deserialize(data, 0, data.length * NetUtils.NumBitsInAByte);
            } catch (PacketException e) {
                LOG.warn("Failed to decode packet: ", e);
                return;
            }

            if (packetInFormatted == null) {
                LOG.warn("Failed to deserialize Received Packet from table {}",
                        packetReceived.getTableId().getValue());
                return;
            }

            Object objPayload = packetInFormatted.getPayload();

            if (objPayload == null) {
                LOG.trace("Unsupported packet type. Ignoring the packet...");
                return;
            }

            if (LOG.isTraceEnabled()) {
                LOG.trace("onPacketReceived packet: {}, packet class: {}", packetReceived, objPayload.getClass());
            }

            AlivenessProtocolHandler livenessProtocolHandler = alivenessProtocolHandlerRegistry
                    .getOpt(objPayload.getClass());
            if (livenessProtocolHandler == null) {
                return;
            }

            String monitorKey = livenessProtocolHandler.handlePacketIn(packetInFormatted.getPayload(),
                    packetReceived);

            if (monitorKey != null) {
                processReceivedMonitorKey(monitorKey);
            } else {
                LOG.debug("No monitorkey associated with received packet");
            }
        }
    }

    private void processReceivedMonitorKey(final String monitorKey) {
        Preconditions.checkNotNull(monitorKey, "Monitor Key required to process the state");

        LOG.debug("Processing monitorKey: {} for received packet", monitorKey);

        final Semaphore lock = lockMap.get(monitorKey);
        LOG.debug("Acquiring lock for monitor key : {} to process monitor packet", monitorKey);
        acquireLock(lock);

        final ReadWriteTransaction tx = dataBroker.newReadWriteTransaction();

        ListenableFuture<Optional<MonitoringState>> stateResult = tx.read(LogicalDatastoreType.OPERATIONAL,
                getMonitorStateId(monitorKey));

        // READ Callback
        Futures.addCallback(stateResult, new FutureCallback<Optional<MonitoringState>>() {

            @Override
            public void onSuccess(Optional<MonitoringState> optState) {

                if (optState.isPresent()) {
                    final MonitoringState currentState = optState.get();

                    if (LOG.isTraceEnabled()) {
                        LOG.trace("OnPacketReceived : Monitoring state from ODS : {} ", currentState);
                    }

                    Long responsePendingCount = currentState.getResponsePendingCount();

                    // Need to relook at the pending count logic to support N
                    // out of M scenarios
                    // if (currentState.getState() != LivenessState.Up) {
                    // //Reset responsePendingCount when state changes from DOWN
                    // to UP
                    // responsePendingCount = INITIAL_COUNT;
                    // }
                    //
                    // if (responsePendingCount > INITIAL_COUNT) {
                    // responsePendingCount =
                    // currentState.getResponsePendingCount() - 1;
                    // }
                    responsePendingCount = INITIAL_COUNT;

                    final boolean stateChanged = currentState.getState() == LivenessState.Down
                            || currentState.getState() == LivenessState.Unknown;

                    final MonitoringState state = new MonitoringStateBuilder().setMonitorKey(monitorKey)
                            .setState(LivenessState.Up).setResponsePendingCount(responsePendingCount).build();
                    tx.merge(LogicalDatastoreType.OPERATIONAL, getMonitorStateId(monitorKey), state);
                    ListenableFuture<Void> writeResult = tx.submit();

                    // WRITE Callback
                    Futures.addCallback(writeResult, new FutureCallback<Void>() {
                        @Override
                        public void onSuccess(Void noarg) {
                            releaseLock(lock);
                            if (stateChanged) {
                                // send notifications
                                if (LOG.isTraceEnabled()) {
                                    LOG.trace("Sending notification for monitor Id : {} with Current State: {}",
                                            currentState.getMonitorId(), LivenessState.Up);
                                }
                                publishNotification(currentState.getMonitorId(), LivenessState.Up);
                            } else {
                                if (LOG.isTraceEnabled()) {
                                    LOG.trace("Successful in writing monitoring state {} to ODS", state);
                                }
                            }
                        }

                        @Override
                        public void onFailure(Throwable error) {
                            releaseLock(lock);
                            LOG.warn("Error in writing monitoring state : {} to Datastore", monitorKey, error);
                            if (LOG.isTraceEnabled()) {
                                LOG.trace("Error in writing monitoring state: {} to Datastore", state);
                            }
                        }
                    });
                } else {
                    if (LOG.isTraceEnabled()) {
                        LOG.trace("Monitoring State not available for key: {} to process the Packet received",
                                monitorKey);
                    }
                    // Complete the transaction
                    tx.submit();
                    releaseLock(lock);
                }
            }

            @Override
            public void onFailure(Throwable error) {
                LOG.error("Error when reading Monitoring State for key: {} to process the Packet received",
                        monitorKey, error);
                // FIXME: Not sure if the transaction status is valid to cancel
                tx.cancel();
                releaseLock(lock);
            }
        });
    }

    private String getIpAddress(EndpointType endpoint) {
        String ipAddress = "";
        if (endpoint instanceof IpAddress) {
            ipAddress = ((IpAddress) endpoint).getIpAddress().getIpv4Address().getValue();
        } else if (endpoint instanceof Interface) {
            ipAddress = ((Interface) endpoint).getInterfaceIp().getIpv4Address().getValue();
        }
        return ipAddress;
    }

    private String getUniqueKey(String interfaceName, String ethType, EndpointType source,
            EndpointType destination) {
        StringBuilder builder = new StringBuilder().append(interfaceName)
                .append(AlivenessMonitorConstants.SEPERATOR).append(ethType);
        if (source != null) {
            builder.append(AlivenessMonitorConstants.SEPERATOR).append(getIpAddress(source));
        }

        if (destination != null) {
            builder.append(AlivenessMonitorConstants.SEPERATOR).append(getIpAddress(destination));
        }
        return builder.toString();
    }

    @Override
    public Future<RpcResult<MonitorStartOutput>> monitorStart(MonitorStartInput input) {
        RpcResultBuilder<MonitorStartOutput> rpcResultBuilder;
        final Config in = input.getConfig();
        Long profileId = in.getProfileId();
        LOG.debug("Monitor Start invoked with Config: {}, Profile Id: {}", in, profileId);

        try {
            if (in.getMode() != MonitoringMode.OneOne) {
                throw new UnsupportedConfigException(
                        "Unsupported Monitoring mode. Currently one-one mode is supported");
            }

            Optional<MonitorProfile> optProfile = read(LogicalDatastoreType.OPERATIONAL,
                    getMonitorProfileId(profileId));
            final MonitorProfile profile;
            if (!optProfile.isPresent()) {
                String errMsg = String.format("No monitoring profile associated with Id: %d", profileId);
                LOG.error("Monitor start failed. {}", errMsg);
                throw new RuntimeException(errMsg);
            } else {
                profile = optProfile.get();
            }

            final EtherTypes ethType = profile.getProtocolType();

            String interfaceName = null;
            EndpointType srcEndpointType = in.getSource().getEndpointType();

            if (srcEndpointType instanceof Interface) {
                Interface endPoint = (Interface) srcEndpointType;
                interfaceName = endPoint.getInterfaceName();
            } else {
                throw new UnsupportedConfigException(
                        "Unsupported source Endpoint type. Only Interface Endpoint currently supported for monitoring");
            }

            if (Strings.isNullOrEmpty(interfaceName)) {
                throw new RuntimeException("Interface Name not defined in the source Endpoint");
            }

            // Initially the support is for one monitoring per interface.
            // Revisit the retrieving monitor id logic when the multiple
            // monitoring for same interface is needed.
            EndpointType destEndpointType = null;
            if (in.getDestination() != null) {
                destEndpointType = in.getDestination().getEndpointType();
            }
            String idKey = getUniqueKey(interfaceName, ethType.toString(), srcEndpointType, destEndpointType);
            final long monitorId = getUniqueId(idKey);
            Optional<MonitoringInfo> optKey = read(LogicalDatastoreType.OPERATIONAL,
                    getMonitoringInfoId(monitorId));
            final AlivenessProtocolHandler handler;
            if (optKey.isPresent()) {
                String message = String.format(
                        "Monitoring for the interface %s with this configuration " + "is already registered.",
                        interfaceName);
                LOG.warn(message);
                MonitorStartOutput output = new MonitorStartOutputBuilder().setMonitorId(monitorId).build();
                rpcResultBuilder = RpcResultBuilder.success(output).withWarning(ErrorType.APPLICATION,
                        "config-exists", message);
                return Futures.immediateFuture(rpcResultBuilder.build());
            } else {
                // Construct the monitor key
                final MonitoringInfo monitoringInfo = new MonitoringInfoBuilder().setId(monitorId)
                        .setMode(in.getMode()).setProfileId(profileId).setDestination(in.getDestination())
                        .setSource(in.getSource()).build();
                // Construct the initial monitor state
                handler = alivenessProtocolHandlerRegistry.get(ethType);
                final String monitoringKey = handler.getUniqueMonitoringKey(monitoringInfo);

                MonitoringState monitoringState = null;
                if (ethType == EtherTypes.Bfd) {
                    monitoringState = new MonitoringStateBuilder().setMonitorKey(monitoringKey)
                            .setMonitorId(monitorId).setState(LivenessState.Unknown)
                            .setStatus(MonitorStatus.Started).build();
                } else {
                    monitoringState = new MonitoringStateBuilder().setMonitorKey(monitoringKey)
                            .setMonitorId(monitorId).setState(LivenessState.Unknown)
                            .setStatus(MonitorStatus.Started).setRequestCount(INITIAL_COUNT)
                            .setResponsePendingCount(INITIAL_COUNT).build();
                }

                WriteTransaction tx = dataBroker.newWriteOnlyTransaction();

                tx.put(LogicalDatastoreType.OPERATIONAL, getMonitoringInfoId(monitorId), monitoringInfo,
                        CREATE_MISSING_PARENT);
                LOG.debug("adding oper monitoring info {}", monitoringInfo);

                tx.put(LogicalDatastoreType.OPERATIONAL, getMonitorStateId(monitoringKey), monitoringState,
                        CREATE_MISSING_PARENT);
                LOG.debug("adding oper monitoring state {}", monitoringState);

                MonitoridKeyEntry mapEntry = new MonitoridKeyEntryBuilder().setMonitorId(monitorId)
                        .setMonitorKey(monitoringKey).build();
                tx.put(LogicalDatastoreType.OPERATIONAL, getMonitorMapId(monitorId), mapEntry,
                        CREATE_MISSING_PARENT);
                LOG.debug("adding oper map entry {}", mapEntry);

                Futures.addCallback(tx.submit(), new FutureCallback<Void>() {
                    @Override
                    public void onFailure(Throwable error) {
                        String errorMsg = String.format("Adding Monitoring info: %s in Datastore failed",
                                monitoringInfo);
                        LOG.warn(errorMsg, error);
                        throw new RuntimeException(errorMsg, error);
                    }

                    @Override
                    public void onSuccess(Void noarg) {
                        lockMap.put(monitoringKey, new Semaphore(1, true));
                        if (ethType == EtherTypes.Bfd) {
                            handler.startMonitoringTask(monitoringInfo);
                            return;
                        }
                        // Schedule task
                        LOG.debug("Scheduling monitor task for config: {}", in);
                        scheduleMonitoringTask(monitoringInfo, profile.getMonitorInterval());
                    }
                });
            }

            associateMonitorIdWithInterface(monitorId, interfaceName);

            MonitorStartOutput output = new MonitorStartOutputBuilder().setMonitorId(monitorId).build();

            rpcResultBuilder = RpcResultBuilder.success(output);
        } catch (UnsupportedConfigException e) {
            LOG.error("Start Monitoring Failed. {}", e.getMessage(), e);
            rpcResultBuilder = RpcResultBuilder.<MonitorStartOutput>failed().withError(ErrorType.APPLICATION,
                    e.getMessage(), e);
        }
        return Futures.immediateFuture(rpcResultBuilder.build());
    }

    private void associateMonitorIdWithInterface(final Long monitorId, final String interfaceName) {
        LOG.debug("associate monitor Id {} with interface {}", monitorId, interfaceName);
        final ReadWriteTransaction tx = dataBroker.newReadWriteTransaction();
        ListenableFuture<Optional<InterfaceMonitorEntry>> readFuture = tx.read(LogicalDatastoreType.OPERATIONAL,
                getInterfaceMonitorMapId(interfaceName));
        ListenableFuture<Void> updateFuture = Futures.transform(readFuture,
                (AsyncFunction<Optional<InterfaceMonitorEntry>, Void>) optEntry -> {
                    if (optEntry.isPresent()) {
                        InterfaceMonitorEntry entry = optEntry.get();
                        List<Long> monitorIds1 = entry.getMonitorIds();
                        monitorIds1.add(monitorId);
                        InterfaceMonitorEntry newEntry1 = new InterfaceMonitorEntryBuilder()
                                .setKey(new InterfaceMonitorEntryKey(interfaceName)).setMonitorIds(monitorIds1)
                                .build();
                        tx.merge(LogicalDatastoreType.OPERATIONAL, getInterfaceMonitorMapId(interfaceName),
                                newEntry1);
                    } else {
                        // Create new monitor entry
                        LOG.debug("Adding new interface-monitor association for interface {} with id {}",
                                interfaceName, monitorId);
                        List<Long> monitorIds2 = new ArrayList<>();
                        monitorIds2.add(monitorId);
                        InterfaceMonitorEntry newEntry2 = new InterfaceMonitorEntryBuilder()
                                .setInterfaceName(interfaceName).setMonitorIds(monitorIds2).build();
                        tx.put(LogicalDatastoreType.OPERATIONAL, getInterfaceMonitorMapId(interfaceName), newEntry2,
                                CREATE_MISSING_PARENT);
                    }
                    return tx.submit();
                });

        Futures.addCallback(updateFuture, new FutureCallbackImpl(
                String.format("Association of monitorId %d with Interface %s", monitorId, interfaceName)));
    }

    private void scheduleMonitoringTask(MonitoringInfo monitoringInfo, long monitorInterval) {
        AlivenessMonitorTask monitorTask = new AlivenessMonitorTask(monitoringInfo);
        ScheduledFuture<?> scheduledFutureResult = monitorService.scheduleAtFixedRate(monitorTask, NO_DELAY,
                monitorInterval, TimeUnit.MILLISECONDS);
        monitoringTasks.put(monitoringInfo.getId(), scheduledFutureResult);
    }

    @Override
    public Future<RpcResult<Void>> monitorPause(MonitorPauseInput input) {
        LOG.debug("Monitor Pause operation invoked for monitor id: {}", input.getMonitorId());
        SettableFuture<RpcResult<Void>> result = SettableFuture.create();
        final Long monitorId = input.getMonitorId();

        // Set the monitoring status to Paused
        updateMonitorStatusTo(monitorId, MonitorStatus.Paused,
                currentStatus -> currentStatus == MonitorStatus.Started);

        if (stopMonitoringTask(monitorId)) {
            result.set(RpcResultBuilder.<Void>success().build());
        } else {
            String errorMsg = String.format("No Monitoring Task availble to pause for the given monitor id : %d",
                    monitorId);
            LOG.error("Monitor Pause operation failed- {}", errorMsg);
            result.set(RpcResultBuilder.<Void>failed().withError(ErrorType.APPLICATION, errorMsg).build());
        }

        return result;
    }

    @Override
    public Future<RpcResult<Void>> monitorUnpause(MonitorUnpauseInput input) {
        LOG.debug("Monitor Unpause operation invoked for monitor id: {}", input.getMonitorId());
        final SettableFuture<RpcResult<Void>> result = SettableFuture.create();

        final Long monitorId = input.getMonitorId();
        final ReadOnlyTransaction tx = dataBroker.newReadOnlyTransaction();
        ListenableFuture<Optional<MonitoringInfo>> readInfoResult = tx.read(LogicalDatastoreType.OPERATIONAL,
                getMonitoringInfoId(monitorId));

        Futures.addCallback(readInfoResult, new FutureCallback<Optional<MonitoringInfo>>() {

            @Override
            public void onFailure(Throwable error) {
                String msg = String.format("Unable to read monitoring info associated with monitor id %d",
                        monitorId);
                LOG.error("Monitor unpause Failed. {}", msg, error);
                result.set(RpcResultBuilder.<Void>failed().withError(ErrorType.APPLICATION, msg, error).build());
            }

            @Override
            public void onSuccess(Optional<MonitoringInfo> optInfo) {
                if (optInfo.isPresent()) {
                    final MonitoringInfo info = optInfo.get();
                    ListenableFuture<Optional<MonitorProfile>> readProfile = tx
                            .read(LogicalDatastoreType.OPERATIONAL, getMonitorProfileId(info.getProfileId()));
                    Futures.addCallback(readProfile, new FutureCallback<Optional<MonitorProfile>>() {

                        @Override
                        public void onFailure(Throwable error) {
                            String msg = String.format("Unable to read Monitoring profile associated with id %d",
                                    info.getProfileId());
                            LOG.warn("Monitor unpause Failed. {}", msg, error);
                            result.set(RpcResultBuilder.<Void>failed().withError(ErrorType.APPLICATION, msg, error)
                                    .build());
                        }

                        @Override
                        public void onSuccess(Optional<MonitorProfile> optProfile) {
                            tx.close();
                            if (optProfile.isPresent()) {
                                updateMonitorStatusTo(monitorId, MonitorStatus.Started,
                                        currentStatus -> (currentStatus == MonitorStatus.Paused
                                                || currentStatus == MonitorStatus.Stopped));
                                MonitorProfile profile = optProfile.get();
                                LOG.debug("Monitor Resume - Scheduling monitoring task with Id: {}", monitorId);
                                EtherTypes protocolType = profile.getProtocolType();
                                if (protocolType == EtherTypes.Bfd) {
                                    LOG.debug("disabling bfd for hwvtep tunnel montior id {}", monitorId);
                                    ((HwVtepTunnelsStateHandler) alivenessProtocolHandlerRegistry.get(protocolType))
                                            .resetMonitoringTask(info, true);
                                } else {
                                    scheduleMonitoringTask(info, profile.getMonitorInterval());
                                }
                                result.set(RpcResultBuilder.<Void>success().build());
                            } else {
                                String msg = String.format(
                                        "Monitoring profile associated with id %d is not present",
                                        info.getProfileId());
                                LOG.warn("Monitor unpause Failed. {}", msg);
                                result.set(RpcResultBuilder.<Void>failed().withError(ErrorType.APPLICATION, msg)
                                        .build());
                            }
                        }
                    });
                } else {
                    tx.close();
                    String msg = String.format("Monitoring info associated with id %d is not present", monitorId);
                    LOG.warn("Monitor unpause Failed. {}", msg);
                    result.set(RpcResultBuilder.<Void>failed().withError(ErrorType.APPLICATION, msg).build());
                }
            }
        }, callbackExecutorService);

        return result;
    }

    private boolean stopMonitoringTask(Long monitorId) {
        return stopMonitoringTask(monitorId, INTERRUPT_TASK);
    }

    private boolean stopMonitoringTask(Long monitorId, boolean interruptTask) {
        Optional<MonitoringInfo> optInfo = read(LogicalDatastoreType.OPERATIONAL, getMonitoringInfoId(monitorId));
        if (!optInfo.isPresent()) {
            LOG.warn("There is no monitoring info present for monitor id {}", monitorId);
            return false;
        }
        MonitoringInfo monitoringInfo = optInfo.get();
        Optional<MonitorProfile> optProfile = read(LogicalDatastoreType.OPERATIONAL,
                getMonitorProfileId(monitoringInfo.getProfileId()));
        EtherTypes protocolType = optProfile.get().getProtocolType();
        if (protocolType == EtherTypes.Bfd) {
            LOG.debug("disabling bfd for hwvtep tunnel montior id {}", monitorId);
            ((HwVtepTunnelsStateHandler) alivenessProtocolHandlerRegistry.get(protocolType))
                    .resetMonitoringTask(monitoringInfo, false);
            return true;
        }
        ScheduledFuture<?> scheduledFutureResult = monitoringTasks.get(monitorId);
        if (scheduledFutureResult != null) {
            scheduledFutureResult.cancel(interruptTask);
            return true;
        }
        return false;
    }

    Optional<MonitorProfile> getMonitorProfile(Long profileId) {
        return read(LogicalDatastoreType.OPERATIONAL, getMonitorProfileId(profileId));
    }

    void acquireLock(Semaphore lock) {
        if (lock == null) {
            return;
        }

        boolean acquiredLock = false;
        try {
            acquiredLock = lock.tryAcquire(50, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            LOG.warn("Thread interrupted when waiting to acquire the lock");
        }

        if (!acquiredLock) {
            LOG.warn("Previous transaction did not complete in time. Releasing the lock to proceed");
            lock.release();
            try {
                lock.acquire();
                LOG.trace("Lock acquired successfully");
            } catch (InterruptedException e) {
                LOG.warn("Acquire failed");
            }
        } else {
            LOG.trace("Lock acquired successfully");
        }
    }

    void releaseLock(Semaphore lock) {
        if (lock != null) {
            lock.release();
        }
    }

    private void sendMonitorPacket(final MonitoringInfo monitoringInfo) {
        // TODO: Handle interrupts
        final Long monitorId = monitoringInfo.getId();
        final String monitorKey = monitorIdKeyCache.getUnchecked(monitorId);
        if (monitorKey == null) {
            LOG.warn("No monitor Key associated with id {} to send the monitor packet", monitorId);
            return;
        } else {
            LOG.debug("Sending monitoring packet for key: {}", monitorKey);
        }

        final MonitorProfile profile;
        Optional<MonitorProfile> optProfile = getMonitorProfile(monitoringInfo.getProfileId());
        if (optProfile.isPresent()) {
            profile = optProfile.get();
        } else {
            LOG.warn(
                    "No monitor profile associated with id {}. "
                            + "Could not send Monitor packet for monitor-id {}",
                    monitoringInfo.getProfileId(), monitorId);
            return;
        }

        final Semaphore lock = lockMap.get(monitorKey);
        LOG.debug("Acquiring lock for monitor key : {} to send monitor packet", monitorKey);
        acquireLock(lock);

        final ReadWriteTransaction tx = dataBroker.newReadWriteTransaction();
        ListenableFuture<Optional<MonitoringState>> readResult = tx.read(LogicalDatastoreType.OPERATIONAL,
                getMonitorStateId(monitorKey));
        ListenableFuture<Void> writeResult = Futures.transform(readResult,
                (AsyncFunction<Optional<MonitoringState>, Void>) optState -> {
                    if (optState.isPresent()) {
                        MonitoringState state = optState.get();

                        // Increase the request count
                        Long requestCount = state.getRequestCount() + 1;

                        // Check with the monitor window
                        LivenessState currentLivenessState = state.getState();

                        // Increase the pending response count
                        long responsePendingCount = state.getResponsePendingCount();
                        if (responsePendingCount < profile.getMonitorWindow()) {
                            responsePendingCount = responsePendingCount + 1;
                        }

                        // Check with the failure threshold
                        if (responsePendingCount >= profile.getFailureThreshold()) {
                            // Change the state to down and notify
                            if (currentLivenessState != LivenessState.Down) {
                                LOG.debug("Response pending Count: {}, Failure threshold: {} for monitorId {}",
                                        responsePendingCount, profile.getFailureThreshold(), state.getMonitorId());
                                LOG.info("Sending notification for monitor Id : {} with State: {}",
                                        state.getMonitorId(), LivenessState.Down);
                                publishNotification(monitorId, LivenessState.Down);
                                currentLivenessState = LivenessState.Down;
                                // Reset requestCount when state changes
                                // from UP to DOWN
                                requestCount = INITIAL_COUNT;
                            }
                        }

                        // Update the ODS with state
                        MonitoringState updatedState = new MonitoringStateBuilder(/* state */)
                                .setMonitorKey(state.getMonitorKey()).setRequestCount(requestCount)
                                .setResponsePendingCount(responsePendingCount).setState(currentLivenessState)
                                .build();
                        tx.merge(LogicalDatastoreType.OPERATIONAL, getMonitorStateId(state.getMonitorKey()),
                                updatedState);
                        return tx.submit();
                    } else {
                        // Close the transaction
                        tx.submit();
                        String errorMsg = String.format(
                                "Monitoring State associated with id %d is not present to send packet out.",
                                monitorId);
                        return Futures.immediateFailedFuture(new RuntimeException(errorMsg));
                    }
                });

        Futures.addCallback(writeResult, new FutureCallback<Void>() {
            @Override
            public void onSuccess(Void noarg) {
                // invoke packetout on protocol handler
                AlivenessProtocolHandler handler = alivenessProtocolHandlerRegistry
                        .getOpt(profile.getProtocolType());
                if (handler != null) {
                    LOG.debug("Sending monitoring packet {}", monitoringInfo);
                    handler.startMonitoringTask(monitoringInfo);
                }
                releaseLock(lock);
            }

            @Override
            public void onFailure(Throwable error) {
                LOG.warn("Updating monitoring state for key: {} failed. Monitoring packet is not sent", monitorKey,
                        error);
                releaseLock(lock);
            }
        });
    }

    void publishNotification(final Long monitorId, final LivenessState state) {
        LOG.debug("Sending notification for id {}  - state {}", monitorId, state);
        EventData data = new EventDataBuilder().setMonitorId(monitorId).setMonitorState(state).build();
        MonitorEvent event = new MonitorEventBuilder().setEventData(data).build();
        final ListenableFuture<?> eventFuture = notificationPublishService.offerNotification(event);
        Futures.addCallback(eventFuture, new FutureCallback<Object>() {
            @Override
            public void onFailure(Throwable error) {
                LOG.warn("Error in notifying listeners for id {} - state {}", monitorId, state, error);
            }

            @Override
            public void onSuccess(Object arg) {
                LOG.trace("Successful in notifying listeners for id {} - state {}", monitorId, state);
            }
        });
    }

    @Override
    public Future<RpcResult<MonitorProfileCreateOutput>> monitorProfileCreate(
            final MonitorProfileCreateInput input) {
        LOG.debug("Monitor Profile Create operation - {}", input.getProfile());
        final SettableFuture<RpcResult<MonitorProfileCreateOutput>> result = SettableFuture.create();
        Profile profile = input.getProfile();
        final Long failureThreshold = profile.getFailureThreshold();
        final Long monitorInterval = profile.getMonitorInterval();
        final Long monitorWindow = profile.getMonitorWindow();
        final EtherTypes ethType = profile.getProtocolType();
        String idKey = getUniqueProfileKey(failureThreshold, monitorInterval, monitorWindow, ethType);
        final Long profileId = (long) getUniqueId(idKey);

        final ReadWriteTransaction tx = dataBroker.newReadWriteTransaction();
        ListenableFuture<Optional<MonitorProfile>> readFuture = tx.read(LogicalDatastoreType.OPERATIONAL,
                getMonitorProfileId(profileId));
        ListenableFuture<RpcResult<MonitorProfileCreateOutput>> resultFuture = Futures.transform(readFuture,
                (AsyncFunction<Optional<MonitorProfile>, RpcResult<MonitorProfileCreateOutput>>) optProfile -> {
                    if (optProfile.isPresent()) {
                        tx.cancel();
                        MonitorProfileCreateOutput output = new MonitorProfileCreateOutputBuilder()
                                .setProfileId(profileId).build();
                        String msg = String.format("Monitor profile %s already present for the given input", input);
                        LOG.warn(msg);
                        result.set(RpcResultBuilder.success(output)
                                .withWarning(ErrorType.PROTOCOL, "profile-exists", msg).build());
                    } else {
                        final MonitorProfile monitorProfile = new MonitorProfileBuilder().setId(profileId)
                                .setFailureThreshold(failureThreshold).setMonitorInterval(monitorInterval)
                                .setMonitorWindow(monitorWindow).setProtocolType(ethType).build();
                        tx.put(LogicalDatastoreType.OPERATIONAL, getMonitorProfileId(profileId), monitorProfile,
                                CREATE_MISSING_PARENT);
                        Futures.addCallback(tx.submit(), new FutureCallback<Void>() {
                            @Override
                            public void onFailure(Throwable error) {
                                String msg = String.format("Error when storing monitorprofile %s in datastore",
                                        monitorProfile);
                                LOG.error(msg, error);
                                result.set(RpcResultBuilder.<MonitorProfileCreateOutput>failed()
                                        .withError(ErrorType.APPLICATION, msg, error).build());
                            }

                            @Override
                            public void onSuccess(Void noarg) {
                                MonitorProfileCreateOutput output = new MonitorProfileCreateOutputBuilder()
                                        .setProfileId(profileId).build();
                                result.set(RpcResultBuilder.success(output).build());
                            }
                        });
                    }
                    return result;
                }, callbackExecutorService);
        Futures.addCallback(resultFuture, new FutureCallback<RpcResult<MonitorProfileCreateOutput>>() {
            @Override
            public void onFailure(Throwable error) {
                // This would happen when any error happens during reading for
                // monitoring profile
                String msg = String.format("Error in creating monitorprofile - %s", input);
                result.set(RpcResultBuilder.<MonitorProfileCreateOutput>failed()
                        .withError(ErrorType.APPLICATION, msg, error).build());
                LOG.error(msg, error);
            }

            @Override
            public void onSuccess(RpcResult<MonitorProfileCreateOutput> result) {
                LOG.debug("Successfully created monitor Profile {} ", input);
            }
        }, callbackExecutorService);
        return result;
    }

    @Override
    public Future<RpcResult<MonitorProfileGetOutput>> monitorProfileGet(MonitorProfileGetInput input) {
        LOG.debug("Monitor Profile Get operation for input profile- {}", input.getProfile());
        RpcResultBuilder<MonitorProfileGetOutput> rpcResultBuilder;
        final Long profileId = getExistingProfileId(input);

        MonitorProfileGetOutputBuilder output = new MonitorProfileGetOutputBuilder().setProfileId(profileId);
        rpcResultBuilder = RpcResultBuilder.success();
        rpcResultBuilder.withResult(output.build());
        return Futures.immediateFuture(rpcResultBuilder.build());
    }

    private Long getExistingProfileId(MonitorProfileGetInput input) {
        org.opendaylight.yang.gen.v1.urn.opendaylight.genius.alivenessmonitor.rev160411.monitor.profile.get.input.Profile profile = input
                .getProfile();
        final Long failureThreshold = profile.getFailureThreshold();
        final Long monitorInterval = profile.getMonitorInterval();
        final Long monitorWindow = profile.getMonitorWindow();
        final EtherTypes ethType = profile.getProtocolType();
        LOG.debug("getExistingProfileId for profile : {}", input.getProfile());
        String idKey = getUniqueProfileKey(failureThreshold, monitorInterval, monitorWindow, ethType);
        LOG.debug("Obtained existing profile ID for profile : {}", input.getProfile());
        return (long) getUniqueId(idKey);
    }

    private String getUniqueProfileKey(Long failureThreshold, Long monitorInterval, Long monitorWindow,
            EtherTypes ethType) {
        return String.valueOf(failureThreshold) + AlivenessMonitorConstants.SEPERATOR + monitorInterval
                + AlivenessMonitorConstants.SEPERATOR + monitorWindow + AlivenessMonitorConstants.SEPERATOR
                + ethType + AlivenessMonitorConstants.SEPERATOR;
    }

    @Override
    public Future<RpcResult<Void>> monitorProfileDelete(final MonitorProfileDeleteInput input) {
        LOG.debug("Monitor Profile delete for Id: {}", input.getProfileId());
        final SettableFuture<RpcResult<Void>> result = SettableFuture.create();
        final Long profileId = input.getProfileId();
        final ReadWriteTransaction tx = dataBroker.newReadWriteTransaction();
        ListenableFuture<Optional<MonitorProfile>> readFuture = tx.read(LogicalDatastoreType.OPERATIONAL,
                getMonitorProfileId(profileId));
        ListenableFuture<RpcResult<Void>> writeFuture = Futures.transform(readFuture,
                (AsyncFunction<Optional<MonitorProfile>, RpcResult<Void>>) optProfile -> {
                    if (optProfile.isPresent()) {
                        tx.delete(LogicalDatastoreType.OPERATIONAL, getMonitorProfileId(profileId));
                        Futures.addCallback(tx.submit(), new FutureCallback<Void>() {
                            @Override
                            public void onFailure(Throwable error) {
                                String msg = String.format("Error when removing monitor profile %d from datastore",
                                        profileId);
                                LOG.error(msg, error);
                                result.set(RpcResultBuilder.<Void>failed()
                                        .withError(ErrorType.APPLICATION, msg, error).build());
                            }

                            @Override
                            public void onSuccess(Void noarg) {
                                MonitorProfile profile = optProfile.get();
                                String id = getUniqueProfileKey(profile.getFailureThreshold(),
                                        profile.getMonitorInterval(), profile.getMonitorWindow(),
                                        profile.getProtocolType());
                                releaseId(id);
                                result.set(RpcResultBuilder.<Void>success().build());
                            }
                        });
                    } else {
                        String msg = String.format("Monitor profile with Id: %d does not exist", profileId);
                        LOG.info(msg);
                        result.set(RpcResultBuilder.<Void>success()
                                .withWarning(ErrorType.PROTOCOL, "invalid-value", msg).build());
                    }
                    return result;
                }, callbackExecutorService);

        Futures.addCallback(writeFuture, new FutureCallback<RpcResult<Void>>() {

            @Override
            public void onFailure(Throwable error) {
                String msg = String.format("Error when removing monitor profile %d from datastore", profileId);
                LOG.error(msg, error);
                result.set(RpcResultBuilder.<Void>failed().withError(ErrorType.APPLICATION, msg, error).build());
            }

            @Override
            public void onSuccess(RpcResult<Void> noarg) {
                LOG.debug("Successfully removed Monitor Profile {}", profileId);
            }
        }, callbackExecutorService);
        return result;
    }

    @Override
    public Future<RpcResult<Void>> monitorStop(MonitorStopInput input) {
        LOG.debug("Monitor Stop operation for monitor id - {}", input.getMonitorId());
        SettableFuture<RpcResult<Void>> result = SettableFuture.create();

        final Long monitorId = input.getMonitorId();
        Optional<MonitoringInfo> optInfo = read(LogicalDatastoreType.OPERATIONAL, getMonitoringInfoId(monitorId));
        if (optInfo.isPresent()) {
            // Stop the monitoring task
            stopMonitoringTask(monitorId);

            // Cleanup the Data store
            WriteTransaction tx = dataBroker.newWriteOnlyTransaction();
            String monitorKey = monitorIdKeyCache.getUnchecked(monitorId);
            if (monitorKey != null) {
                tx.delete(LogicalDatastoreType.OPERATIONAL, getMonitorStateId(monitorKey));
                monitorIdKeyCache.invalidate(monitorId);
            }

            tx.delete(LogicalDatastoreType.OPERATIONAL, getMonitoringInfoId(monitorId));
            Futures.addCallback(tx.submit(),
                    new FutureCallbackImpl(String.format("Delete monitor state with Id %d", monitorId)));

            MonitoringInfo info = optInfo.get();
            String interfaceName = getInterfaceName(info.getSource().getEndpointType());
            if (interfaceName != null) {
                removeMonitorIdFromInterfaceAssociation(monitorId, interfaceName);
            }
            releaseIdForMonitoringInfo(info);

            lockMap.remove(monitorKey);

            result.set(RpcResultBuilder.<Void>success().build());
        } else {
            String errorMsg = String.format("Do not have monitoring information associated with key %d", monitorId);
            LOG.error("Delete monitoring operation Failed - {}", errorMsg);
            result.set(RpcResultBuilder.<Void>failed().withError(ErrorType.APPLICATION, errorMsg).build());
        }

        return result;
    }

    private void removeMonitorIdFromInterfaceAssociation(final Long monitorId, final String interfaceName) {
        LOG.debug("Remove monitorId {} from Interface association {}", monitorId, interfaceName);
        final ReadWriteTransaction tx = dataBroker.newReadWriteTransaction();
        ListenableFuture<Optional<InterfaceMonitorEntry>> readFuture = tx.read(LogicalDatastoreType.OPERATIONAL,
                getInterfaceMonitorMapId(interfaceName));
        ListenableFuture<Void> updateFuture = Futures.transform(readFuture,
                (AsyncFunction<Optional<InterfaceMonitorEntry>, Void>) optEntry -> {
                    if (optEntry.isPresent()) {
                        InterfaceMonitorEntry entry = optEntry.get();
                        List<Long> monitorIds = entry.getMonitorIds();
                        monitorIds.remove(monitorId);
                        InterfaceMonitorEntry newEntry = new InterfaceMonitorEntryBuilder(entry)
                                .setKey(new InterfaceMonitorEntryKey(interfaceName)).setMonitorIds(monitorIds)
                                .build();
                        tx.put(LogicalDatastoreType.OPERATIONAL, getInterfaceMonitorMapId(interfaceName), newEntry,
                                CREATE_MISSING_PARENT);
                        return tx.submit();
                    } else {
                        LOG.warn("No Interface map entry found {} to remove monitorId {}", interfaceName,
                                monitorId);
                        tx.cancel();
                        return Futures.immediateFuture(null);
                    }
                });

        Futures.addCallback(updateFuture, new FutureCallbackImpl(
                String.format("Dis-association of monitorId %d with Interface %s", monitorId, interfaceName)));
    }

    private void releaseIdForMonitoringInfo(MonitoringInfo info) {
        Long monitorId = info.getId();
        EndpointType source = info.getSource().getEndpointType();
        String interfaceName = getInterfaceName(source);
        if (!Strings.isNullOrEmpty(interfaceName)) {
            Optional<MonitorProfile> optProfile = read(LogicalDatastoreType.OPERATIONAL,
                    getMonitorProfileId(info.getProfileId()));
            if (optProfile.isPresent()) {
                EtherTypes ethType = optProfile.get().getProtocolType();
                EndpointType destination = info.getDestination() != null ? info.getDestination().getEndpointType()
                        : null;
                String idKey = getUniqueKey(interfaceName, ethType.toString(), source, destination);
                releaseId(idKey);
            } else {
                LOG.warn("Could not release monitorId {}. No profile associated with it", monitorId);
            }
        }
    }

    private String getInterfaceName(EndpointType endpoint) {
        String interfaceName = null;
        if (endpoint instanceof Interface) {
            interfaceName = ((Interface) endpoint).getInterfaceName();
        }
        return interfaceName;
    }

    private void stopMonitoring(long monitorId) {
        updateMonitorStatusTo(monitorId, MonitorStatus.Stopped,
                currentStatus -> currentStatus != MonitorStatus.Stopped);
        if (!stopMonitoringTask(monitorId)) {
            LOG.warn("No monitoring task running to perform cancel operation for monitorId {}", monitorId);
        }
    }

    private void updateMonitorStatusTo(final Long monitorId, final MonitorStatus newStatus,
            final Predicate<MonitorStatus> isValidStatus) {
        final String monitorKey = monitorIdKeyCache.getUnchecked(monitorId);
        if (monitorKey == null) {
            LOG.warn("No monitor Key associated with id {} to change the monitor status to {}", monitorId,
                    newStatus);
            return;
        }
        final ReadWriteTransaction tx = dataBroker.newReadWriteTransaction();

        ListenableFuture<Optional<MonitoringState>> readResult = tx.read(LogicalDatastoreType.OPERATIONAL,
                getMonitorStateId(monitorKey));

        ListenableFuture<Void> writeResult = Futures.transform(readResult,
                (AsyncFunction<Optional<MonitoringState>, Void>) optState -> {
                    if (optState.isPresent()) {
                        MonitoringState state = optState.get();
                        if (isValidStatus.apply(state.getStatus())) {
                            MonitoringState updatedState = new MonitoringStateBuilder().setMonitorKey(monitorKey)
                                    .setStatus(newStatus).build();
                            tx.merge(LogicalDatastoreType.OPERATIONAL, getMonitorStateId(monitorKey), updatedState);
                        } else {
                            LOG.warn("Invalid Monitoring status {}, cannot be updated to {} for monitorId {}",
                                    state.getStatus(), newStatus, monitorId);
                        }
                    } else {
                        LOG.warn("No associated monitoring state data available to update the status to {} for {}",
                                newStatus, monitorId);
                    }
                    return tx.submit();
                });

        Futures.addCallback(writeResult, new FutureCallbackImpl(
                String.format("Monitor status update for %d to %s", monitorId, newStatus.toString())));
    }

    private void resumeMonitoring(final long monitorId) {
        final ReadOnlyTransaction tx = dataBroker.newReadOnlyTransaction();
        ListenableFuture<Optional<MonitoringInfo>> readInfoResult = tx.read(LogicalDatastoreType.OPERATIONAL,
                getMonitoringInfoId(monitorId));

        Futures.addCallback(readInfoResult, new FutureCallback<Optional<MonitoringInfo>>() {

            @Override
            public void onFailure(Throwable error) {
                String msg = String.format("Unable to read monitoring info associated with monitor id %d",
                        monitorId);
                LOG.error("Monitor resume Failed. {}", msg, error);
            }

            @Override
            public void onSuccess(Optional<MonitoringInfo> optInfo) {
                if (optInfo.isPresent()) {
                    final MonitoringInfo info = optInfo.get();
                    ListenableFuture<Optional<MonitorProfile>> readProfile = tx
                            .read(LogicalDatastoreType.OPERATIONAL, getMonitorProfileId(info.getProfileId()));
                    Futures.addCallback(readProfile, new FutureCallback<Optional<MonitorProfile>>() {

                        @Override
                        public void onFailure(Throwable error) {
                            String msg = String.format("Unable to read Monitoring profile associated with id %d",
                                    info.getProfileId());
                            LOG.warn("Monitor resume Failed. {}", msg, error);
                        }

                        @Override
                        public void onSuccess(Optional<MonitorProfile> optProfile) {
                            tx.close();
                            if (optProfile.isPresent()) {
                                updateMonitorStatusTo(monitorId, MonitorStatus.Started,
                                        currentStatus -> currentStatus != MonitorStatus.Started);
                                MonitorProfile profile = optProfile.get();
                                LOG.debug("Monitor Resume - Scheduling monitoring task for Id: {}", monitorId);
                                scheduleMonitoringTask(info, profile.getMonitorInterval());
                            } else {
                                String msg = String.format(
                                        "Monitoring profile associated with id %d is not present",
                                        info.getProfileId());
                                LOG.warn("Monitor resume Failed. {}", msg);
                            }
                        }
                    });
                } else {
                    tx.close();
                    String msg = String.format("Monitoring info associated with id %d is not present", monitorId);
                    LOG.warn("Monitor resume Failed. {}", msg);
                }
            }
        });
    }

    // DATA STORE OPERATIONS
    <T extends DataObject> Optional<T> read(LogicalDatastoreType datastoreType, InstanceIdentifier<T> path) {
        try (ReadOnlyTransaction tx = dataBroker.newReadOnlyTransaction()) {
            return tx.read(datastoreType, path).get();
        } catch (InterruptedException | ExecutionException e) {
            LOG.warn("Error reading data from path {} in datastore {}", path, datastoreType, e);
        }

        return Optional.absent();
    }

    @Override
    public void onInterfaceStateUp(String interfaceName) {
        List<Long> monitorIds = getMonitorIds(interfaceName);
        if (monitorIds.isEmpty()) {
            LOG.warn("Could not get monitorId for interface: {}", interfaceName);
            return;
        }
        for (Long monitorId : monitorIds) {
            LOG.debug("Resume monitoring on interface: {} with monitorId: {}", interfaceName, monitorId);
            resumeMonitoring(monitorId);
        }
    }

    @Override
    public void onInterfaceStateDown(String interfaceName) {
        List<Long> monitorIds = getMonitorIds(interfaceName);
        if (monitorIds.isEmpty()) {
            LOG.warn("Could not get monitorIds for interface: {}", interfaceName);
            return;
        }
        for (Long monitorId : monitorIds) {
            LOG.debug("Suspend monitoring on interface: {} with monitorId: {}", interfaceName, monitorId);
            stopMonitoring(monitorId);
        }
    }

    private List<Long> getMonitorIds(String interfaceName) {
        return read(LogicalDatastoreType.OPERATIONAL, getInterfaceMonitorMapId(interfaceName))
                .transform(InterfaceMonitorEntry::getMonitorIds).or(Collections.emptyList());
    }
}