/*
* Copyright (c) 2016 Red Hat, Inc. 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.ovsdb.utils.mdsal.utils;
import org.opendaylight.controller.md.sal.binding.api.DataBroker;
import org.opendaylight.controller.md.sal.binding.api.DataChangeListener;
import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker;
import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
import org.opendaylight.yangtools.concepts.ListenerRegistration;
import org.opendaylight.yangtools.yang.binding.DataObject;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* This class provides methods for checking or waiting for various md-sal operations to complete.
* Once an instance is created one must invoke the registerDataChangeListener method
* with a DataBroker.
*/
public class NotifyingDataChangeListener implements AutoCloseable, DataChangeListener {
private static final Logger LOG = LoggerFactory.getLogger(NotifyingDataChangeListener.class);
private LogicalDatastoreType type;
private final Set<InstanceIdentifier<?>> createdIids = new HashSet<>();
private final Set<InstanceIdentifier<?>> removedIids = new HashSet<>();
private final Set<InstanceIdentifier<?>> updatedIids = new HashSet<>();
private InstanceIdentifier<?> iid;
private final static int RETRY_WAIT = 100;
private final static int MDSAL_TIMEOUT_OPERATIONAL = 10000;
private final static int MDSAL_TIMEOUT_CONFIG = 1000;
private ListenerRegistration<?> listenerRegistration;
private List<NotifyingDataChangeListener> waitList = null;
private int mdsalTimeout = MDSAL_TIMEOUT_OPERATIONAL;
private Boolean listen;
public static final int BIT_CREATE = 1;
public static final int BIT_UPDATE = 2;
public static final int BIT_DELETE = 4;
public static final int BIT_ALL = 7;
private int mask;
public NotifyingDataChangeListener(LogicalDatastoreType type, int mask,
InstanceIdentifier<?> iid, List<NotifyingDataChangeListener> waitList) {
this(type, iid, waitList);
this.mask = mask;
}
/**
* Create a new NotifyingDataChangeListener
* @param type DataStore type
* @param iid of the md-sal object we're waiting for
* @param waitList for tracking outstanding changes
*/
public NotifyingDataChangeListener(LogicalDatastoreType type,
InstanceIdentifier<?> iid, List<NotifyingDataChangeListener> waitList) {
this.type = type;
this.iid = iid;
this.waitList = waitList;
if (this.waitList != null) {
this.waitList.add(this);
}
mdsalTimeout = MDSAL_TIMEOUT_OPERATIONAL;
if (type == LogicalDatastoreType.CONFIGURATION) {
mdsalTimeout = MDSAL_TIMEOUT_CONFIG;
}
listen = true;
mask = BIT_ALL;
}
/**
* Completely reset the state of this NotifyingDataChangeListener.
* @param type DataStore type
* @param iid of the md-sal object we're waiting for
* @throws Exception
*/
public void modify(LogicalDatastoreType type, InstanceIdentifier<?> iid) throws Exception {
this.close();
this.clear();
this.type = type;
this.iid = iid;
}
public void setlisten(Boolean listen) {
this.listen = listen;
}
public void setMask(int mask) {
this.mask = mask;
}
@Override
public void onDataChanged(
AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> asyncDataChangeEvent) {
if (!listen) {
return;
}
if ((mask & BIT_CREATE) == BIT_CREATE) {
LOG.info("{} DataChanged: created {}", type, asyncDataChangeEvent.getCreatedData().keySet());
createdIids.addAll(asyncDataChangeEvent.getCreatedData().keySet());
}
if ((mask & BIT_UPDATE) == BIT_UPDATE) {
LOG.info("{} DataChanged: updated {}", type, asyncDataChangeEvent.getUpdatedData().keySet());
updatedIids.addAll(asyncDataChangeEvent.getUpdatedData().keySet());
}
if ((mask & BIT_DELETE) == BIT_DELETE) {
LOG.info("{} DataChanged: removed {}", type, asyncDataChangeEvent.getRemovedPaths());
removedIids.addAll(asyncDataChangeEvent.getRemovedPaths());
}
synchronized (this) {
notifyAll();
}
}
public boolean isCreated(InstanceIdentifier<?> iid) {
return createdIids.remove(iid);
}
public boolean isUpdated(InstanceIdentifier<?> iid) {
return updatedIids.remove(iid);
}
public boolean isRemoved(InstanceIdentifier<?> iid) {
return removedIids.remove(iid);
}
public void clear() {
createdIids.clear();
updatedIids.clear();
removedIids.clear();
}
public void registerDataChangeListener(DataBroker dataBroker) {
listenerRegistration = dataBroker.registerDataChangeListener(type, iid, this,
AsyncDataBroker.DataChangeScope.SUBTREE);
}
public void waitForCreation() throws InterruptedException {
waitForCreation(mdsalTimeout);
}
public void waitForCreation(long timeout) throws InterruptedException {
synchronized (this) {
long _start = System.currentTimeMillis();
LOG.info("Waiting for {} DataChanged creation on {}", type, iid);
while (!isCreated(iid) && (System.currentTimeMillis() - _start) < timeout) {
wait(RETRY_WAIT);
}
LOG.info("Woke up, waited {}ms for creation of {}", (System.currentTimeMillis() - _start), iid);
}
}
public void waitForUpdate() throws InterruptedException {
waitForUpdate(mdsalTimeout);
}
public void waitForUpdate(long timeout) throws InterruptedException {
synchronized (this) {
long _start = System.currentTimeMillis();
LOG.info("Waiting for {} DataChanged update on {}", type, iid);
while (!isUpdated(iid) && (System.currentTimeMillis() - _start) < timeout) {
wait(RETRY_WAIT);
}
LOG.info("Woke up, waited {}ms for update of {}", (System.currentTimeMillis() - _start), iid);
}
}
public void waitForDeletion() throws InterruptedException {
waitForDeletion(mdsalTimeout);
}
public void waitForDeletion(long timeout) throws InterruptedException {
synchronized (this) {
long _start = System.currentTimeMillis();
LOG.info("Waiting for {} DataChanged deletion on {}", type, iid);
while (!isRemoved(iid) && (System.currentTimeMillis() - _start) < timeout) {
wait(RETRY_WAIT);
}
LOG.info("Woke up, waited {}ms for deletion of {}", (System.currentTimeMillis() - _start), iid);
}
}
@Override
public void close() throws Exception {
if (listenerRegistration != null) {
try {
listenerRegistration.close();
} catch (final Exception ex) {
LOG.warn("Failed to close registration {}, iid {}", listenerRegistration, iid, ex);
}
}
if (waitList != null) {
waitList.remove(this);
}
listenerRegistration = null;
}
}