+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- vi: set et smarttab sw=4 tabstop=4: -->
-<!--
- Copyright (c) 2013 Cisco Systems, 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
--->
-<snapshot>
- <configuration>
- <data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
- <modules xmlns="urn:opendaylight:params:xml:ns:yang:controller:config">
- <module>
- <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote:rpc">prefix:remote-zeromq-rpc-server</type>
- <name>remoter</name>
- <port xmlns="urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote:rpc">5666</port>
- <dom-broker xmlns="urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote:rpc">
- <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom">prefix:dom-broker-osgi-registry</type>
- <name>dom-broker</name>
- </dom-broker>
- </module>
- </modules>
- <services xmlns="urn:opendaylight:params:xml:ns:yang:controller:config">
- </services>
- </data>
- </configuration>
-
- <required-capabilities>
- <capability>urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom?module=opendaylight-md-sal-dom&revision=2013-10-28</capability>
- <capability>urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:impl?module=opendaylight-sal-dom-broker-impl&revision=2013-10-28</capability>
- <capability>urn:opendaylight:params:xml:ns:yang:controller:md:sal:common?module=opendaylight-md-sal-common&revision=2013-10-28</capability>
- <capability>urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote:rpc?module=odl-sal-dom-rpc-remote-cfg&revision=2013-10-28</capability>
- </required-capabilities>
-</snapshot>
-
</rpc-registry>
<data-broker>
- <type xmlns:binding="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding">binding:binding-data-broker</type>
+ <type xmlns:binding="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding">binding:binding-async-data-broker</type>
<name>binding-data-broker</name>
</data-broker>
--- /dev/null
+package org.opendaylight.controller.md.sal.binding.api;
+
+import org.opendaylight.controller.md.sal.common.api.data.TransactionChain;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+public interface BindingTransactionChain extends TransactionChain<InstanceIdentifier<?>, DataObject> {
+
+ @Override
+ ReadOnlyTransaction newReadOnlyTransaction();
+
+ @Override
+ ReadWriteTransaction newReadWriteTransaction();
+
+ @Override
+ WriteTransaction newWriteOnlyTransaction();
+
+}
import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker;
import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionChainFactory;
import org.opendaylight.yangtools.concepts.ListenerRegistration;
import org.opendaylight.yangtools.yang.binding.DataObject;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
* <p>
* For more information on usage, please see the documentation in {@link AsyncDataBroker}.
*/
-public interface DataBroker extends AsyncDataBroker<InstanceIdentifier<?>, DataObject, DataChangeListener>, BindingService {
+public interface DataBroker extends AsyncDataBroker<InstanceIdentifier<?>, DataObject, DataChangeListener>, BindingService, TransactionChainFactory<InstanceIdentifier<?>, DataObject> {
@Override
ReadOnlyTransaction newReadOnlyTransaction();
void unregisterFunctionality(ProviderFunctionality functionality);
}
+ /**
+ * Represents an RPC implementation registration. Users should call the
+ * {@link ObjectRegistration#close close} method when the registration is no longer needed.
+ *
+ * @param <T> the implemented RPC service interface
+ */
public interface RpcRegistration<T extends RpcService> extends ObjectRegistration<T> {
+ /**
+ * Returns the implemented RPC service interface.
+ */
Class<T> getServiceType();
}
+ /**
+ * Represents a routed RPC implementation registration. Users should call the
+ * {@link RoutedRegistration#close close} method when the registration is no longer needed.
+ *
+ * @param <T> the implemented RPC service interface
+ */
public interface RoutedRpcRegistration<T extends RpcService> extends RpcRegistration<T>,
RoutedRegistration<Class<? extends BaseIdentity>, InstanceIdentifier<?>, T> {
import org.opendaylight.yangtools.yang.binding.RpcService;
/**
- * Base interface defining contract for retrieving MD-SAL
- * version of RpcServices
+ * Provides access to registered Remote Procedure Call (RPC) service implementations. The RPCs are
+ * defined in YANG models.
+ * <p>
+ * RPC implementations are registered using the {@link RpcProviderRegistry}.
*
*/
public interface RpcConsumerRegistry extends BindingAwareService {
/**
- * Returns a session specific instance (implementation) of requested
- * YANG module implementation / service provided by consumer.
+ * Returns an implementation of a requested RPC service.
*
- * @return Session specific implementation of service
+ * <p>
+ * The returned instance is not an actual implementation of the RPC service
+ * interface, but a proxy implementation of the interface that forwards to
+ * an actual implementation, if any.
+ * <p>
+ *
+ * The following describes the behavior of the proxy when invoking RPC methods:
+ * <ul>
+ * <li>If an actual implementation is registered with the MD-SAL, all invocations are
+ * forwarded to the registered implementation.</li>
+ * <li>If no actual implementation is registered, all invocations will fail by
+ * throwing {@link IllegalStateException}.</li>
+ * <li>Prior to invoking the actual implementation, the method arguments are are validated.
+ * If any are invalid, an {@link IllegalArgumentException} is thrown.
+ * </ul>
+ *
+ * The returned proxy is automatically updated with the most recent
+ * registered implementation.
+ * <p>
+ * The generated RPC method APIs require implementors to return a {@link java.util.concurrent.Future Future}
+ * instance that wraps the {@link org.opendaylight.yangtools.yang.common.RpcResult RpcResult}. Since
+ * RPC methods may be implemented asynchronously, callers should avoid blocking on the
+ * {@link java.util.concurrent.Future Future} result. Instead, it is recommended to use
+ * {@link com.google.common.util.concurrent.JdkFutureAdapters#listenInPoolThread(java.util.concurrent.Future)}
+ * or {@link com.google.common.util.concurrent.JdkFutureAdapters#listenInPoolThread(java.util.concurrent.Future, java.util.concurrent.Executor)}
+ * to listen for Rpc Result. This will asynchronously listen for future result in executor and
+ * will not block current thread.
+ *
+ * <pre>
+ * final Future<RpcResult<SomeRpcOutput>> future = someRpcService.someRpc( ... );
+ * Futures.addCallback(JdkFutureAdapters.listenInThreadPool(future), new FutureCallback<RpcResult<SomeRpcOutput>>() {
+ *
+ * public void onSuccess(RpcResult<SomeRpcOutput> result) {
+ * // process result ...
+ * }
+ *
+ * public void onFailure(Throwable t) {
+ * // RPC failed
+ * }
+ * );
+ * </pre>
+ * @param serviceInterface the interface of the RPC Service. Typically this is an interface generated
+ * from a YANG model.
+ * @return the proxy for the requested RPC service. This method never returns null.
*/
- <T extends RpcService> T getRpcService(Class<T> module);
+ <T extends RpcService> T getRpcService(Class<T> serviceInterface);
}
import org.opendaylight.yangtools.yang.binding.RpcService;
/**
- * Interface defining provider's access to the Rpc Registry which could be used
- * to register their implementations of service to the MD-SAL.
+ * Provides a registry for Remote Procedure Call (RPC) service implementations. The RPCs are
+ * defined in YANG models.
+ * <p>
+ * There are 2 types of RPCs:
+ * <ul>
+ * <li>Global</li>
+ * <li>Routed</li>
+ * </ul>
*
- * @author ttkacik
+ * <h2>Global RPC</h2>
+ * <p>
+ * An RPC is global if there is intended to be only 1 registered implementation. A global RPC is not
+ * explicitly declared as such, essentially any RPC that is not defined to be routed is considered global.
+ * <p>
+ * Global RPCs are registered using the
+ * {@link #addRpcImplementation(Class, RpcService)} method.
*
+ * <h2>Routed RPC</h2>
+ * <p>
+ * MD-SAL supports routing of RPC between multiple implementations where the appropriate
+ * implementation is selected at run time based on the content of the RPC message as described in
+ * YANG model.
+ * <p>
+ * RPC routing is based on:
+ * <ul>
+ * <li><b>Route identifier</b> -
+ * An {@link org.opendaylight.yangtools.yang.binding.InstanceIdentifier InstanceIdentifier} value
+ * which is part of the RPC input. This value is used to select the correct
+ * implementation at run time.</li>
+ * <li><b>Context Type</b> - A YANG-defined construct which constrains the subset of
+ * valid route identifiers for a particular RPC.</li>
+ * </ul>
+ *
+ * <h3>Context type</h3>
+ * <p>
+ * A context type is modeled in YANG using a combination of a YANG <code>identity</code>
+ * and Opendaylight specific extensions from <code>yang-ext</code> module. These extensions are:
+ * <ul>
+ * <li><b>context-instance</b> - This is used in the data tree part of a YANG model to
+ * define a context type that associates nodes with a specified context <code>identity</code>.
+ * Instance identifiers that reference these nodes are valid route identifiers for RPCs that
+ * reference this context type.</li>
+ * <li><b>context-reference</b> - This is used in RPC input to mark a leaf of type
+ * <code>instance-identifier</code> as a reference to the particular context type defined by the
+ * specified context <code>identity</code>. The value of this
+ * leaf is used by the RPC broker at run time to route the RPC request to the correct implementation.
+ * Note that <code>context-reference</code> may only be used on leaf elements of type
+ * <code>instance-identifier</code> or a type derived from <code>instance-identifier</code>.</li>
+ * </ul>
+ *
+ *
+ * <h3>Routed RPC example</h3>
+ * <p>
+ * <h5>1. Defining a Context Type</h5>
+ * <p>
+ * The following snippet declares a simple YANG <code>identity</code> named <code>example-context</code>:
+ *
+ * <pre>
+ * module example {
+ * ...
+ * identity example-context {
+ * description "Identity used to define an example-context type";
+ * }
+ * ...
+ * }
+ * </pre>
+ * <p>
+ * We then use the declared identity to define a context type by using it in combination
+ * with the <code>context-instance</code> YANG extension. We'll associate the context type
+ * with a list element in the data tree. This defines the set of nodes whose instance
+ * identifiers are valid for the <code>example-context</code> context type.
+ * <p>
+ * The following YANG snippet imports the <code>yang-ext</code> module and defines the list
+ * element named <code>item</code> inside a container named <code>foo</code>:
+ *
+ * <pre>
+ * module foo {
+ * ...
+ * import yang-ext {prefix ext;}
+ * ...
+ * container foo {
+ * list item {
+ * key "id";
+ * leaf id {type string;}
+ * ext:context-instance "example-context";
+ * }
+ * }
+ * ...
+ * }
+ * </pre>
+ * <p>
+ * The statement <code>ext:context-instance "example-context";</code> inside the list element
+ * declares that any instance identifier referencing <code>item</code> in the data
+ * tree is valid for <code>example-context</code>. For example, the following instance
+ * identifier:
+ * <pre>
+ * InstanceIdentifier.create(Foo.class).child(Item.class,new ItemKey("Foo"))
+ * </pre>
+ * is valid for <code>example-context</code>. However the following:
+ * <pre>
+ * InstanceIdentifier.create(Example.class)
+ * </pre>
+ * is not valid.
+ * <p>
+ * So using an <code>identity</code> in combination with <code>context-instance</code> we
+ * have effectively defined a context type that can be referenced in a YANG RPC input.
+ *
+ * <h5>2. Defining an RPC to use the Context Type</h5>
+ * <p>
+ * To define an RPC to be routed based on the context type we need to add an input leaf element
+ * that references the context type which will hold an instance identifier value to be
+ * used to route the RPC.
+ * <p>
+ * The following snippet defines an RPC named <code>show-item</code> with 2 leaf elements
+ * as input: <code>item</code> of type <code>instance-identifier</code> and <code>description</code>:
+ *
+ * <pre>
+ * module foo {
+ * ...
+ * import yang-ext {prefix ext;}
+ * ...
+ * rpc show-item {
+ * input {
+ * leaf item {
+ * type instance-identifier;
+ * ext:context-reference example-context;
+ * }
+ * leaf description {
+ * type "string";
+ * }
+ * }
+ * }
+ * }
+ * </pre>
+ * <p>
+ * We mark the <code>item</code> leaf with a <code>context-reference</code> statement that
+ * references the <code>example-context</code> context type. RPC calls will then be routed
+ * based on the instance identifier value contained in <code>item</code>. Only instance
+ * identifiers that point to a <code>foo/item</code> node are valid as input.
+ * <p>
+ * The generated RPC Service interface for the module is:
+ *
+ * <pre>
+ * interface FooService implements RpcService {
+ * Future<RpcResult<Void>> showItem(ShowItemInput input);
+ * }
+ * </pre>
+ * <p>
+ * For constructing the RPC input, there are generated classes ShowItemInput and ShowItemInputBuilder.
+ *
+ * <h5>3. Registering a routed RPC implementation</h5>
+ * <p>
+ * To register a routed implementation for the <code>show-item</code> RPC, we must use the
+ * {@link #addRoutedRpcImplementation(Class, RpcService)} method. This
+ * will return a {@link RoutedRpcRegistration} instance which can then be used to register /
+ * unregister routed paths associated with the registered implementation.
+ * <p>
+ * The following snippet registers <code>myImpl</code> as the RPC implementation for an
+ * <code>item</code> with key <code>"foo"</code>:
+ * <pre>
+ * // Create the instance identifier path for item "foo"
+ * InstanceIdentifier path = InstanceIdentifier.create(Foo.class).child(Item.class, new ItemKey("foo"));
+ *
+ * // Register myImpl as the implementation for the FooService RPC interface
+ * RoutedRpcRegistration reg = rpcRegistry.addRoutedRpcImplementation(FooService.class, myImpl);
+ *
+ * // Now register for the context type and specific path ID. The context type is specified by the
+ * // YANG-generated class for the example-context identity.
+ * reg.registerPath(ExampleContext.class, path);
+ * </pre>
+ * <p>
+ * It is also possible to register the same implementation for multiple paths:
+ *
+ * <pre>
+ * InstanceIdentifier one = InstanceIdentifier.create(Foo.class).child(Item.class, new ItemKey("One"));
+ * InstanceIdentifier two = InstanceIdentifier.create(Foo.class).child(Item.class, new ItemKey("Two"));
+ *
+ * RoutedRpcRegistration reg = rpcRegistry.addRoutedRpcImplementation(FooService.class, myImpl);
+ * reg.registerPath(ExampleContext.class, one);
+ * reg.registerPath(ExampleContext.class, two);
+ * </pre>
+ *
+ * <p>
+ * When another client invokes the <code>showItem(ShowItemInput)</code> method on the proxy instance
+ * retrieved via {@link RpcConsumerRegistry#getRpcService(Class)}, the proxy will inspect the
+ * arguments in ShowItemInput, extract the InstanceIdentifier value of the <code>item</code> leaf and select
+ * the implementation whose registered path matches the InstanceIdentifier value of the <code>item</code> leaf.
+ *
+ * <h2>Notes for RPC Implementations</h2>
+ *
+ * <h3>RpcResult</h3>
+ * <p>
+ * The generated interfaces require implementors to return
+ * {@link java.util.concurrent.Future Future}<{@link org.opendaylight.yangtools.yang.common.RpcResult RpcResult}<{RpcName}Output>> instances.
+ *
+ * Implementations should do processing of RPC calls asynchronously and update the
+ * returned {@link java.util.concurrent.Future Future} instance when processing is complete.
+ * However using {@link com.google.common.util.concurrent.Futures#immediateFuture(Object) Futures.immediateFuture}
+ * is valid only if the result is immediately available and asynchronous processing is unnecessary and
+ * would only introduce additional complexity.
+ *
+ * <p>
+ * The {@link org.opendaylight.yangtools.yang.common.RpcResult RpcResult} is a generic
+ * wrapper for the RPC output payload, if any, and also allows for attaching error or
+ * warning information (possibly along with the payload) should the RPC processing partially
+ * or completely fail. This is intended to provide additional human readable information
+ * for users of the API and to transfer warning / error information across the system
+ * so it may be visible via other external APIs such as Restconf.
+ * <p>
+ * It is recommended to use the {@link org.opendaylight.yangtools.yang.common.RpcResult RpcResult}
+ * for conveying appropriate error information
+ * on failure rather than purposely throwing unchecked exceptions if at all possible.
+ * While unchecked exceptions will fail the returned {@link java.util.concurrent.Future Future},
+ * using the intended RpcResult to convey the error information is more user-friendly.
*/
public interface RpcProviderRegistry extends //
RpcConsumerRegistry, //
RouteChangePublisher<RpcContextIdentifier, InstanceIdentifier<?>> {
/**
- * Registers a global RpcService implementation.
+ * Registers a global implementation of the provided RPC service interface.
+ * All methods of the interface are required to be implemented.
+ *
+ * @param serviceInterface the YANG-generated interface of the RPC Service for which to register.
+ * @param implementation "the implementation of the RPC service interface.
+ * @return an RpcRegistration instance that should be used to unregister the RPC implementation
+ * when no longer needed by calling {@link RpcRegistration#close()}.
*
- * @param type
- * @param implementation
- * @return
+ * @throws IllegalStateException
+ * if the supplied RPC interface is a routed RPC type.
*/
- <T extends RpcService> RpcRegistration<T> addRpcImplementation(Class<T> type, T implementation)
+ <T extends RpcService> RpcRegistration<T> addRpcImplementation(Class<T> serviceInterface, T implementation)
throws IllegalStateException;
/**
+ * Registers an implementation of the given routed RPC service interface.
+ * <p>
+ * See the {@link RpcProviderRegistry class} documentation for information and example on
+ * how to use routed RPCs.
*
- * Register a Routed RpcService where routing is determined on annotated
- * (in YANG model) context-reference and value of annotated leaf.
- *
- * @param type
- * Type of RpcService, use generated interface class, not your
- * implementation class
- * @param implementation
- * Implementation of RpcService
- * @return Registration object for routed Rpc which could be used to unregister
+ * @param serviceInterface the YANG-generated interface of the RPC Service for which to register.
+ * @param implementation the implementation instance to register.
+ * @return a RoutedRpcRegistration instance which can be used to register paths for the RPC
+ * implementation via invoking {@link RoutedRpcRegistration#registerPath(....).
+ * {@link RoutedRpcRegistration#close()} should be called to unregister the implementation
+ * and all previously registered paths when no longer needed.
*
* @throws IllegalStateException
+ * if the supplied RPC interface is not a routed RPC type.
*/
- <T extends RpcService> RoutedRpcRegistration<T> addRoutedRpcImplementation(Class<T> type, T implementation)
+ <T extends RpcService> RoutedRpcRegistration<T> addRoutedRpcImplementation(Class<T> serviceInterface,
+ T implementation)
throws IllegalStateException;
}
*/
package org.opendaylight.controller.md.sal.binding.impl;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
+import com.google.common.collect.Iterables;
public abstract class AbstractForwardedDataBroker implements Delegator<DOMDataBroker>, DomForwardedBroker,
SchemaContextListener, AutoCloseable {
protected Map<InstanceIdentifier<?>, DataObject> toBinding(
final Map<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, ? extends NormalizedNode<?, ?>> normalized) {
Map<InstanceIdentifier<?>, DataObject> newMap = new HashMap<>();
- for (Map.Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, ? extends NormalizedNode<?, ?>> entry : normalized
- .entrySet()) {
+
+ for (Map.Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, ? extends NormalizedNode<?, ?>> entry : sortedEntries(normalized)) {
try {
Optional<Entry<InstanceIdentifier<? extends DataObject>, DataObject>> potential = getCodec().toBinding(
entry);
return newMap;
}
+ private static <T> Iterable<Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier,T>> sortedEntries(final Map<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, T> map) {
+ ArrayList<Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, T>> entries = new ArrayList<>(map.entrySet());
+ Collections.sort(entries, new Comparator<Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, T>>() {
+
+ @Override
+ public int compare(final Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, T> left,
+ final Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, T> right) {
+ int leftSize = Iterables.size(left.getKey().getPathArguments());
+ int rightSize = Iterables.size(right.getKey().getPathArguments());
+ return Integer.compare(leftSize, rightSize);
+ }
+ });
+ return entries;
+ }
+
protected Set<InstanceIdentifier<?>> toBinding(
final Set<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier> normalized) {
Set<InstanceIdentifier<?>> hashSet = new HashSet<>();
*/
package org.opendaylight.controller.md.sal.binding.impl;
-import java.util.ArrayList;
-import java.util.EnumMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map.Entry;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-
-import javax.annotation.Nullable;
-
-import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
import org.opendaylight.controller.md.sal.common.api.data.AsyncTransaction;
import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
-import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizationException;
-import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizationOperation;
import org.opendaylight.controller.md.sal.dom.api.DOMDataReadTransaction;
-import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
-import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
import org.opendaylight.yangtools.concepts.Delegator;
+import org.opendaylight.yangtools.concepts.Identifiable;
import org.opendaylight.yangtools.yang.binding.DataObject;
-import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
-import org.opendaylight.yangtools.yang.common.RpcResult;
-import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.opendaylight.yangtools.yang.data.impl.codec.DeserializationException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import com.google.common.base.Function;
import com.google.common.base.Optional;
-import com.google.common.cache.Cache;
-import com.google.common.cache.CacheBuilder;
+import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
-public class AbstractForwardedTransaction<T extends AsyncTransaction<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>>>
- implements Delegator<T> {
- private static final Logger LOG = LoggerFactory.getLogger(AbstractForwardedTransaction.class);
+abstract class AbstractForwardedTransaction<T extends AsyncTransaction<InstanceIdentifier, NormalizedNode<?, ?>>>
+ implements Delegator<T>, Identifiable<Object> {
+
private final T delegate;
- private final static CacheBuilder<Object, Object> CACHE_BUILDER = CacheBuilder.newBuilder()
- .expireAfterWrite(10, TimeUnit.MILLISECONDS).maximumSize(100);
private final BindingToNormalizedNodeCodec codec;
- private final EnumMap<LogicalDatastoreType, Cache<InstanceIdentifier<?>, DataObject>> cacheMap;
- protected AbstractForwardedTransaction(final T delegate, final BindingToNormalizedNodeCodec codec) {
- super();
- this.delegate = delegate;
- this.codec = codec;
+ public AbstractForwardedTransaction(final T delegateTx, final BindingToNormalizedNodeCodec codec) {
+ this.delegate = Preconditions.checkNotNull(delegateTx, "Delegate must not be null");
+ this.codec = Preconditions.checkNotNull(codec, "Codec must not be null");
+ }
- this.cacheMap = new EnumMap<>(LogicalDatastoreType.class);
- cacheMap.put(LogicalDatastoreType.OPERATIONAL, CACHE_BUILDER.<InstanceIdentifier<?>, DataObject> build());
- cacheMap.put(LogicalDatastoreType.CONFIGURATION, CACHE_BUILDER.<InstanceIdentifier<?>, DataObject> build());
+ @Override
+ public final Object getIdentifier() {
+ return delegate.getIdentifier();
}
@Override
- public T getDelegate() {
+ public final T getDelegate() {
return delegate;
}
- protected final BindingToNormalizedNodeCodec getCodec() {
- return codec;
- }
-
- protected ListenableFuture<Optional<DataObject>> transformFuture(final LogicalDatastoreType store,
- final InstanceIdentifier<?> path, final ListenableFuture<Optional<NormalizedNode<?, ?>>> future) {
- return Futures.transform(future, new Function<Optional<NormalizedNode<?, ?>>, Optional<DataObject>>() {
- @Nullable
- @Override
- public Optional<DataObject> apply(@Nullable final Optional<NormalizedNode<?, ?>> normalizedNode) {
- if (normalizedNode.isPresent()) {
- final DataObject dataObject;
- try {
- dataObject = codec.toBinding(path, normalizedNode.get());
- } catch (DeserializationException e) {
- LOG.warn("Failed to create dataobject from node {}", normalizedNode.get(), e);
- throw new IllegalStateException("Failed to create dataobject", e);
- }
-
- if (dataObject != null) {
- updateCache(store, path, dataObject);
- return Optional.of(dataObject);
- }
- }
- return Optional.absent();
- }
- });
- }
-
- protected void doPut(final DOMDataWriteTransaction writeTransaction, final LogicalDatastoreType store,
- final InstanceIdentifier<?> path, final DataObject data) {
- invalidateCache(store, path);
- final Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>> normalized = codec
- .toNormalizedNode(path, data);
- writeTransaction.put(store, normalized.getKey(), normalized.getValue());
- }
-
- protected void doPutWithEnsureParents(final DOMDataReadWriteTransaction writeTransaction,
- final LogicalDatastoreType store, final InstanceIdentifier<?> path, final DataObject data) {
- invalidateCache(store, path);
- final Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>> normalized = codec
- .toNormalizedNode(path, data);
-
- final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalizedPath = normalized.getKey();
- ensureParentsByMerge(writeTransaction, store, normalizedPath, path);
- LOG.debug("Tx: {} : Putting data {}", getDelegate().getIdentifier(), normalizedPath);
- writeTransaction.put(store, normalizedPath, normalized.getValue());
- }
-
- protected void doMergeWithEnsureParents(final DOMDataReadWriteTransaction writeTransaction,
- final LogicalDatastoreType store, final InstanceIdentifier<?> path, final DataObject data) {
- invalidateCache(store, path);
- final Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>> normalized = codec
- .toNormalizedNode(path, data);
-
- final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalizedPath = normalized.getKey();
- ensureParentsByMerge(writeTransaction, store, normalizedPath, path);
- LOG.debug("Tx: {} : Merge data {}",getDelegate().getIdentifier(),normalizedPath);
- writeTransaction.merge(store, normalizedPath, normalized.getValue());
+ @SuppressWarnings("unchecked")
+ protected final <S extends AsyncTransaction<InstanceIdentifier, NormalizedNode<?, ?>>> S getDelegateChecked(final Class<S> txType) {
+ Preconditions.checkState(txType.isInstance(delegate));
+ return (S) delegate;
}
- private void ensureParentsByMerge(final DOMDataReadWriteTransaction writeTransaction,
- final LogicalDatastoreType store,
- final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalizedPath,
- final InstanceIdentifier<?> path) {
- List<PathArgument> currentArguments = new ArrayList<>();
- DataNormalizationOperation<?> currentOp = codec.getDataNormalizer().getRootOperation();
- Iterator<PathArgument> iterator = normalizedPath.getPath().iterator();
- while (iterator.hasNext()) {
- PathArgument currentArg = iterator.next();
- try {
- currentOp = currentOp.getChild(currentArg);
- } catch (DataNormalizationException e) {
- throw new IllegalArgumentException(String.format("Invalid child encountered in path %s", path), e);
- }
- currentArguments.add(currentArg);
- org.opendaylight.yangtools.yang.data.api.InstanceIdentifier currentPath = new org.opendaylight.yangtools.yang.data.api.InstanceIdentifier(
- currentArguments);
-
- final Optional<NormalizedNode<?, ?>> d;
- try {
- d = writeTransaction.read(store, currentPath).get();
- } catch (InterruptedException | ExecutionException e) {
- LOG.error("Failed to read pre-existing data from store {} path {}", store, currentPath, e);
- throw new IllegalStateException("Failed to read pre-existing data", e);
- }
-
- if (!d.isPresent() && iterator.hasNext()) {
- writeTransaction.merge(store, currentPath, currentOp.createDefault(currentArg));
- }
- }
- }
-
- protected void doMerge(final DOMDataWriteTransaction writeTransaction, final LogicalDatastoreType store,
- final InstanceIdentifier<?> path, final DataObject data) {
- invalidateCache(store, path);
- final Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>> normalized = codec
- .toNormalizedNode(path, data);
- writeTransaction.merge(store, normalized.getKey(), normalized.getValue());
- }
-
- protected void doDelete(final DOMDataWriteTransaction writeTransaction, final LogicalDatastoreType store,
- final InstanceIdentifier<?> path) {
- invalidateCache(store, path);
- final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized = codec.toNormalized(path);
- writeTransaction.delete(store, normalized);
- }
-
- protected ListenableFuture<RpcResult<TransactionStatus>> doCommit(final DOMDataWriteTransaction writeTransaction) {
- return writeTransaction.commit();
- }
-
- protected boolean doCancel(final DOMDataWriteTransaction writeTransaction) {
- return writeTransaction.cancel();
- }
-
- protected ListenableFuture<Optional<DataObject>> doRead(final DOMDataReadTransaction readTransaction,
- final LogicalDatastoreType store, final InstanceIdentifier<?> path) {
- final DataObject dataObject = getFromCache(store, path);
- if (dataObject == null) {
- final ListenableFuture<Optional<NormalizedNode<?, ?>>> future = readTransaction.read(store,
- codec.toNormalized(path));
- return transformFuture(store, path, future);
- } else {
- return Futures.immediateFuture(Optional.of(dataObject));
- }
- }
-
- private DataObject getFromCache(final LogicalDatastoreType store, final InstanceIdentifier<?> path) {
- Cache<InstanceIdentifier<?>, DataObject> cache = cacheMap.get(store);
- if (cache != null) {
- return cache.getIfPresent(path);
- }
- return null;
- }
-
- private void updateCache(final LogicalDatastoreType store, final InstanceIdentifier<?> path,
- final DataObject dataObject) {
- // Check if cache exists. If not create one.
- Cache<InstanceIdentifier<?>, DataObject> cache = cacheMap.get(store);
- if (cache == null) {
- cache = CacheBuilder.newBuilder().maximumSize(1000).expireAfterWrite(1, TimeUnit.MINUTES).build();
-
- }
-
- cache.put(path, dataObject);
+ protected final BindingToNormalizedNodeCodec getCodec() {
+ return codec;
}
- private void invalidateCache(final LogicalDatastoreType store, final InstanceIdentifier<?> path) {
- // FIXME: Optimization: invalidate only parents and children of path
- Cache<InstanceIdentifier<?>, DataObject> cache = cacheMap.get(store);
- cache.invalidateAll();
- LOG.trace("Cache invalidated");
+ protected final ListenableFuture<Optional<DataObject>> doRead(final DOMDataReadTransaction readTx,
+ final LogicalDatastoreType store, final org.opendaylight.yangtools.yang.binding.InstanceIdentifier<?> path) {
+ return Futures.transform(readTx.read(store, codec.toNormalized(path)), codec.deserializeFunction(path));
}
-
}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.md.sal.binding.impl;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.concurrent.ExecutionException;
+
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizationException;
+import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizationOperation;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Optional;
+
+public class AbstractReadWriteTransaction extends AbstractWriteTransaction<DOMDataReadWriteTransaction> {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AbstractReadWriteTransaction.class);
+
+ public AbstractReadWriteTransaction(final DOMDataReadWriteTransaction delegate, final BindingToNormalizedNodeCodec codec) {
+ super(delegate, codec);
+ }
+
+ protected final void doPutWithEnsureParents(final LogicalDatastoreType store, final InstanceIdentifier<?> path, final DataObject data) {
+ final Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>> normalized = getCodec()
+ .toNormalizedNode(path, data);
+
+ final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalizedPath = normalized.getKey();
+ ensureParentsByMerge(store, normalizedPath, path);
+ LOG.debug("Tx: {} : Putting data {}", getDelegate().getIdentifier(), normalizedPath);
+ doPut(store, path, data);
+ }
+
+ protected final void doMergeWithEnsureParents(final LogicalDatastoreType store, final InstanceIdentifier<?> path, final DataObject data) {
+ final Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>> normalized = getCodec()
+ .toNormalizedNode(path, data);
+
+ final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalizedPath = normalized.getKey();
+ ensureParentsByMerge(store, normalizedPath, path);
+ LOG.debug("Tx: {} : Merge data {}", getDelegate().getIdentifier(), normalizedPath);
+ doMerge(store, path, data);
+ }
+
+ private final void ensureParentsByMerge(final LogicalDatastoreType store,
+ final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalizedPath,
+ final InstanceIdentifier<?> path) {
+ List<PathArgument> currentArguments = new ArrayList<>();
+ DataNormalizationOperation<?> currentOp = getCodec().getDataNormalizer().getRootOperation();
+ Iterator<PathArgument> iterator = normalizedPath.getPathArguments().iterator();
+ while (iterator.hasNext()) {
+ PathArgument currentArg = iterator.next();
+ try {
+ currentOp = currentOp.getChild(currentArg);
+ } catch (DataNormalizationException e) {
+ throw new IllegalArgumentException(String.format("Invalid child encountered in path %s", path), e);
+ }
+ currentArguments.add(currentArg);
+ org.opendaylight.yangtools.yang.data.api.InstanceIdentifier currentPath = org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.create(
+ currentArguments);
+
+ final Optional<NormalizedNode<?, ?>> d;
+ try {
+ d = getDelegate().read(store, currentPath).get();
+ } catch (InterruptedException | ExecutionException e) {
+ LOG.error("Failed to read pre-existing data from store {} path {}", store, currentPath, e);
+ throw new IllegalStateException("Failed to read pre-existing data", e);
+ }
+
+ if (!d.isPresent() && iterator.hasNext()) {
+ getDelegate().merge(store, currentPath, currentOp.createDefault(currentArg));
+ }
+ }
+ }
+
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.md.sal.binding.impl;
+
+import java.util.Map.Entry;
+
+import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ *
+ * Abstract Base Transaction for transactions which are backed by
+ * {@link DOMDataWriteTransaction}
+ */
+public class AbstractWriteTransaction<T extends DOMDataWriteTransaction> extends
+ AbstractForwardedTransaction<T> {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AbstractWriteTransaction.class);
+
+ protected AbstractWriteTransaction(final T delegate,
+ final BindingToNormalizedNodeCodec codec) {
+ super(delegate, codec);
+ }
+
+ protected final void doPut(final LogicalDatastoreType store,
+ final InstanceIdentifier<?> path, final DataObject data) {
+ final Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>> normalized = getCodec()
+ .toNormalizedNode(path, data);
+ getDelegate().put(store, normalized.getKey(), normalized.getValue());
+ }
+
+ protected final void doMerge(final LogicalDatastoreType store,
+ final InstanceIdentifier<?> path, final DataObject data) {
+ final Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>> normalized = getCodec()
+ .toNormalizedNode(path, data);
+ getDelegate().merge(store, normalized.getKey(), normalized.getValue());
+ }
+
+ protected final void doDelete(final LogicalDatastoreType store,
+ final InstanceIdentifier<?> path) {
+ final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized = getCodec().toNormalized(path);
+ getDelegate().delete(store, normalized);
+ }
+
+ protected final ListenableFuture<RpcResult<TransactionStatus>> doCommit() {
+ return getDelegate().commit();
+ }
+
+ protected final boolean doCancel() {
+ return getDelegate().cancel();
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.md.sal.binding.impl;
+
+import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.ListenableFuture;
+
+class BindingDataReadTransactionImpl extends AbstractForwardedTransaction<DOMDataReadOnlyTransaction> implements
+ ReadOnlyTransaction {
+
+ protected BindingDataReadTransactionImpl(final DOMDataReadOnlyTransaction delegate,
+ final BindingToNormalizedNodeCodec codec) {
+ super(delegate, codec);
+ }
+
+ @Override
+ public ListenableFuture<Optional<DataObject>> read(final LogicalDatastoreType store,
+ final InstanceIdentifier<?> path) {
+ return doRead(getDelegate(),store, path);
+ }
+
+ @Override
+ public void close() {
+ getDelegate().close();
+ }
+
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.md.sal.binding.impl;
+
+import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.ListenableFuture;
+
+class BindingDataReadWriteTransactionImpl extends
+ BindingDataWriteTransactionImpl<DOMDataReadWriteTransaction> implements ReadWriteTransaction {
+
+ protected BindingDataReadWriteTransactionImpl(final DOMDataReadWriteTransaction delegate,
+ final BindingToNormalizedNodeCodec codec) {
+ super(delegate, codec);
+ }
+
+ @Override
+ public ListenableFuture<Optional<DataObject>> read(final LogicalDatastoreType store,
+ final InstanceIdentifier<?> path) {
+ return doRead(getDelegate(), store, path);
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.md.sal.binding.impl;
+
+import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
+import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+class BindingDataWriteTransactionImpl<T extends DOMDataWriteTransaction> extends
+ AbstractWriteTransaction<T> implements WriteTransaction {
+
+ protected BindingDataWriteTransactionImpl(final T delegateTx, final BindingToNormalizedNodeCodec codec) {
+ super(delegateTx, codec);
+ }
+
+
+
+ @Override
+ public void put(final LogicalDatastoreType store, final InstanceIdentifier<?> path, final DataObject data) {
+ doPut(store, path, data);
+ }
+
+ @Override
+ public void merge(final LogicalDatastoreType store, final InstanceIdentifier<?> path, final DataObject data) {
+ doMerge(store, path, data);
+ }
+
+ @Override
+ public void delete(final LogicalDatastoreType store, final InstanceIdentifier<?> path) {
+ doDelete( store, path);
+ }
+
+ @Override
+ public ListenableFuture<RpcResult<TransactionStatus>> commit() {
+ return doCommit();
+ }
+
+ @Override
+ public boolean cancel() {
+ return doCancel();
+ }
+}
\ No newline at end of file
import java.util.List;
import java.util.Map.Entry;
+import javax.annotation.Nullable;
+
import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizationException;
import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizationOperation;
import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.base.Function;
import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
public Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>> toNormalizedNode(
final InstanceIdentifier<? extends DataObject> bindingPath, final DataObject bindingObject) {
- return toNormalizedNode(toEntry(bindingPath, bindingObject));
+ return toNormalizedNode(toBindingEntry(bindingPath, bindingObject));
}
.toNormalized(legacyEntry);
LOG.trace("Serialization of {}, Legacy Representation: {}, Normalized Representation: {}", binding,
legacyEntry, normalizedEntry);
- if (Augmentation.class.isAssignableFrom(binding.getKey().getTargetType())) {
+ if (isAugmentation(binding.getKey().getTargetType())) {
for (DataContainerChild<? extends PathArgument, ?> child : ((DataContainerNode<?>) normalizedEntry
.getValue()).getValue()) {
if (child instanceof AugmentationNode) {
ImmutableList<PathArgument> childArgs = ImmutableList.<PathArgument> builder()
- .addAll(normalizedEntry.getKey().getPath()).add(child.getIdentifier()).build();
- org.opendaylight.yangtools.yang.data.api.InstanceIdentifier childPath = new org.opendaylight.yangtools.yang.data.api.InstanceIdentifier(
+ .addAll(normalizedEntry.getKey().getPathArguments()).add(child.getIdentifier()).build();
+ org.opendaylight.yangtools.yang.data.api.InstanceIdentifier childPath = org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.create(
childArgs);
- return new SimpleEntry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>>(
- childPath, child);
+ return toDOMEntry(childPath, child);
}
}
final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized)
throws DeserializationException {
- PathArgument lastArgument = Iterables.getLast(normalized.getPath());
+ PathArgument lastArgument = Iterables.getLast(normalized.getPathArguments());
// Used instance-identifier codec do not support serialization of last
// path
// argument if it is AugmentationIdentifier (behaviour expected by old
}
int normalizedCount = getAugmentationCount(normalized);
- AugmentationIdentifier lastArgument = (AugmentationIdentifier) Iterables.getLast(normalized.getPath());
+ AugmentationIdentifier lastArgument = (AugmentationIdentifier) Iterables.getLast(normalized.getPathArguments());
// Here we employ small trick - Binding-aware Codec injects an pointer
// to augmentation class
LOG.trace("Looking for candidates to match {}", normalized);
for (QName child : lastArgument.getPossibleChildNames()) {
org.opendaylight.yangtools.yang.data.api.InstanceIdentifier childPath = new org.opendaylight.yangtools.yang.data.api.InstanceIdentifier(
- ImmutableList.<PathArgument> builder().addAll(normalized.getPath()).add(new NodeIdentifier(child))
+ ImmutableList.<PathArgument> builder().addAll(normalized.getPathArguments()).add(new NodeIdentifier(child))
.build());
try {
if (isNotRepresentable(childPath)) {
final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized)
throws DataNormalizationException {
DataNormalizationOperation<?> current = legacyToNormalized.getRootOperation();
- for (PathArgument arg : normalized.getPath()) {
+ for (PathArgument arg : normalized.getPathArguments()) {
current = current.getChild(arg);
}
return current;
}
- private static final Entry<org.opendaylight.yangtools.yang.binding.InstanceIdentifier<? extends DataObject>, DataObject> toEntry(
+ private static final Entry<org.opendaylight.yangtools.yang.binding.InstanceIdentifier<? extends DataObject>, DataObject> toBindingEntry(
final org.opendaylight.yangtools.yang.binding.InstanceIdentifier<? extends DataObject> key,
final DataObject value) {
return new SimpleEntry<org.opendaylight.yangtools.yang.binding.InstanceIdentifier<? extends DataObject>, DataObject>(
key, value);
}
+ private static final Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>> toDOMEntry(
+ final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier key,
+ final NormalizedNode<?, ?> value) {
+ return new SimpleEntry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>>(
+ key, value);
+ }
+
public DataObject toBinding(final InstanceIdentifier<?> path, final NormalizedNode<?, ?> normalizedNode)
throws DeserializationException {
CompositeNode legacy = null;
if (bindingData == null) {
LOG.warn("Failed to deserialize {} to Binding format. Binding path is: {}", normalized, bindingPath);
}
- return Optional.of(toEntry(bindingPath, bindingData));
+ return Optional.of(toBindingEntry(bindingPath, bindingData));
} else {
return Optional.absent();
}
final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized) {
int position = 0;
int foundPosition = -1;
- for (PathArgument arg : normalized.getPath()) {
+ for (PathArgument arg : normalized.getPathArguments()) {
position++;
if (arg instanceof AugmentationIdentifier) {
foundPosition = position;
}
}
if (foundPosition > 0) {
- return new org.opendaylight.yangtools.yang.data.api.InstanceIdentifier(normalized.getPath().subList(0,
- foundPosition));
+ Iterable<PathArgument> shortened = Iterables.limit(normalized.getPathArguments(), foundPosition);
+ return org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.create(shortened);
}
return null;
}
}
private boolean isAugmentationIdentifier(final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier processed) {
- return Iterables.getLast(processed.getPath()) instanceof AugmentationIdentifier;
+ return Iterables.getLast(processed.getPathArguments()) instanceof AugmentationIdentifier;
}
private static int getAugmentationCount(final InstanceIdentifier<?> potential) {
private static int getAugmentationCount(final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier potential) {
int count = 0;
- for(PathArgument arg : potential.getPath()) {
+ for(PathArgument arg : potential.getPathArguments()) {
if(arg instanceof AugmentationIdentifier) {
count++;
}
}
return count;
}
+
+ public Function<Optional<NormalizedNode<?, ?>>, Optional<DataObject>> deserializeFunction(final InstanceIdentifier<?> path) {
+ return new DeserializeFunction(this, path);
+ }
+
+ private static class DeserializeFunction implements Function<Optional<NormalizedNode<?, ?>>, Optional<DataObject>> {
+
+ private final BindingToNormalizedNodeCodec codec;
+ private final InstanceIdentifier<?> path;
+
+ public DeserializeFunction(final BindingToNormalizedNodeCodec codec, final InstanceIdentifier<?> path) {
+ super();
+ this.codec = Preconditions.checkNotNull(codec, "Codec must not be null");
+ this.path = Preconditions.checkNotNull(path, "Path must not be null");
+ }
+
+ @Nullable
+ @Override
+ public Optional<DataObject> apply(@Nullable final Optional<NormalizedNode<?, ?>> normalizedNode) {
+ if (normalizedNode.isPresent()) {
+ final DataObject dataObject;
+ try {
+ dataObject = codec.toBinding(path, normalizedNode.get());
+ } catch (DeserializationException e) {
+ LOG.warn("Failed to create dataobject from node {}", normalizedNode.get(), e);
+ throw new IllegalStateException("Failed to create dataobject", e);
+ }
+
+ if (dataObject != null) {
+ return Optional.of(dataObject);
+ }
+ }
+ return Optional.absent();
+ }
+ }
}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.md.sal.binding.impl;
+
+import java.util.Map;
+import java.util.WeakHashMap;
+
+import javax.annotation.concurrent.GuardedBy;
+
+import org.opendaylight.controller.md.sal.binding.api.BindingTransactionChain;
+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.AsyncTransaction;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionChain;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionChainListener;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
+import org.opendaylight.yangtools.concepts.Delegator;
+
+import com.google.common.base.Preconditions;
+
+class BindingTranslatedTransactionChain implements BindingTransactionChain, Delegator<DOMTransactionChain> {
+
+ private final DOMTransactionChain delegate;
+
+ @GuardedBy("this")
+ private final Map<AsyncTransaction<?, ?>, AsyncTransaction<?, ?>> delegateTxToBindingTx = new WeakHashMap<>();
+ private final BindingToNormalizedNodeCodec codec;
+
+ public BindingTranslatedTransactionChain(final DOMDataBroker chainFactory,
+ final BindingToNormalizedNodeCodec codec, final TransactionChainListener listener) {
+ Preconditions.checkNotNull(chainFactory, "DOM Transaction chain factory must not be null");
+ this.delegate = chainFactory.createTransactionChain(new ListenerInvoker(listener));
+ this.codec = codec;
+ }
+
+ @Override
+ public DOMTransactionChain getDelegate() {
+ return delegate;
+ }
+
+ @Override
+ public ReadOnlyTransaction newReadOnlyTransaction() {
+ DOMDataReadOnlyTransaction delegateTx = delegate.newReadOnlyTransaction();
+ ReadOnlyTransaction bindingTx = new BindingDataReadTransactionImpl(delegateTx, codec);
+ putDelegateToBinding(delegateTx, bindingTx);
+ return bindingTx;
+ }
+
+ @Override
+ public ReadWriteTransaction newReadWriteTransaction() {
+ DOMDataReadWriteTransaction delegateTx = delegate.newReadWriteTransaction();
+ ReadWriteTransaction bindingTx = new BindingDataReadWriteTransactionImpl(delegateTx, codec);
+ putDelegateToBinding(delegateTx, bindingTx);
+ return bindingTx;
+ }
+
+ @Override
+ public WriteTransaction newWriteOnlyTransaction() {
+ DOMDataWriteTransaction delegateTx = delegate.newWriteOnlyTransaction();
+ WriteTransaction bindingTx = new BindingDataWriteTransactionImpl<>(delegateTx, codec);
+ putDelegateToBinding(delegateTx, bindingTx);
+ return bindingTx;
+ }
+
+ @Override
+ public void close() {
+ delegate.close();
+ }
+
+ private synchronized void putDelegateToBinding(final AsyncTransaction<?, ?> domTx,
+ final AsyncTransaction<?, ?> bindingTx) {
+ final Object previous = delegateTxToBindingTx.put(domTx, bindingTx);
+ Preconditions.checkState(previous == null, "DOM Transaction %s has already associated binding transation %s",domTx,previous);
+ }
+
+ private synchronized AsyncTransaction<?, ?> getBindingTransaction(final AsyncTransaction<?, ?> transaction) {
+ return delegateTxToBindingTx.get(transaction);
+ }
+
+ private final class ListenerInvoker implements TransactionChainListener {
+
+ private final TransactionChainListener listener;
+
+ public ListenerInvoker(final TransactionChainListener listener) {
+ this.listener = Preconditions.checkNotNull(listener, "Listener must not be null.");
+ }
+
+ @Override
+ public void onTransactionChainFailed(final TransactionChain<?, ?> chain,
+ final AsyncTransaction<?, ?> transaction, final Throwable cause) {
+ Preconditions.checkState(delegate.equals(chain),
+ "Illegal state - listener for %s was invoked for incorrect chain %s.", delegate, chain);
+ AsyncTransaction<?, ?> bindingTx = getBindingTransaction(transaction);
+ listener.onTransactionChainFailed(chain, bindingTx, cause);
+ }
+
+ @Override
+ public void onTransactionChainSuccessful(final TransactionChain<?, ?> chain) {
+ Preconditions.checkState(delegate.equals(chain),
+ "Illegal state - listener for %s was invoked for incorrect chain %s.", delegate, chain);
+ listener.onTransactionChainSuccessful(BindingTranslatedTransactionChain.this);
+ }
+ }
+
+}
}
private class ForwardedBackwardsCompatibleTransacion extends
- AbstractForwardedTransaction<DOMDataReadWriteTransaction> implements DataModificationTransaction {
+ AbstractReadWriteTransaction implements DataModificationTransaction {
private final ListenerRegistry<DataTransactionListener> listeners = ListenerRegistry.create();
private final Map<InstanceIdentifier<? extends DataObject>, DataObject> updated = new HashMap<>();
public void putOperationalData(final InstanceIdentifier<? extends DataObject> path, final DataObject data) {
boolean previouslyRemoved = posponedRemovedOperational.remove(path);
if(previouslyRemoved) {
- doPutWithEnsureParents(getDelegate(), LogicalDatastoreType.OPERATIONAL, path, data);
+ doPutWithEnsureParents(LogicalDatastoreType.OPERATIONAL, path, data);
} else {
- doMergeWithEnsureParents(getDelegate(), LogicalDatastoreType.OPERATIONAL, path, data);
+ doMergeWithEnsureParents(LogicalDatastoreType.OPERATIONAL, path, data);
}
}
}
updated.put(path, data);
if(previouslyRemoved) {
- doPutWithEnsureParents(getDelegate(), LogicalDatastoreType.CONFIGURATION, path, data);
+ doPutWithEnsureParents(LogicalDatastoreType.CONFIGURATION, path, data);
} else {
- doMergeWithEnsureParents(getDelegate(), LogicalDatastoreType.CONFIGURATION, path, data);
+ doMergeWithEnsureParents(LogicalDatastoreType.CONFIGURATION, path, data);
}
}
}
}
- @Override
- public Object getIdentifier() {
- return getDelegate().getIdentifier();
- }
-
private void changeStatus(final TransactionStatus status) {
LOG.trace("Transaction {} changed status to {}", getIdentifier(), status);
this.status = status;
public ListenableFuture<RpcResult<TransactionStatus>> commit() {
for(InstanceIdentifier<? extends DataObject> path : posponedRemovedConfiguration) {
- doDelete(getDelegate(), LogicalDatastoreType.CONFIGURATION, path);
+ doDelete(LogicalDatastoreType.CONFIGURATION, path);
}
for(InstanceIdentifier<? extends DataObject> path : posponedRemovedOperational) {
- doDelete(getDelegate(), LogicalDatastoreType.OPERATIONAL, path);
+ doDelete(LogicalDatastoreType.OPERATIONAL, path);
}
changeStatus(TransactionStatus.SUBMITED);
package org.opendaylight.controller.md.sal.binding.impl;
+import org.opendaylight.controller.md.sal.binding.api.BindingTransactionChain;
import org.opendaylight.controller.md.sal.binding.api.DataBroker;
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.TransactionStatus;
-import org.opendaylight.controller.md.sal.common.api.data.AsyncTransaction;
-import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionChainListener;
import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
-import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
-import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
-import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
import org.opendaylight.controller.sal.core.api.model.SchemaService;
-import org.opendaylight.yangtools.yang.binding.DataObject;
-import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
-import org.opendaylight.yangtools.yang.common.RpcResult;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.data.impl.codec.BindingIndependentMappingService;
-import com.google.common.base.Optional;
-import com.google.common.util.concurrent.ListenableFuture;
-
/**
* The DataBrokerImpl simply defers to the DOMDataBroker for all its operations.
* All transactions and listener registrations are wrapped by the DataBrokerImpl
* Besides this the DataBrokerImpl and it's collaborators also cache data that
* is already transformed from the binding independent to binding aware format
*
- * TODO : All references in this class to CompositeNode should be switched to
- * NormalizedNode once the MappingService is updated
- *
+
*/
public class ForwardedBindingDataBroker extends AbstractForwardedDataBroker implements DataBroker {
@Override
public WriteTransaction newWriteOnlyTransaction() {
- return new BindingDataWriteTransactionImpl<DOMDataWriteTransaction>(getDelegate().newWriteOnlyTransaction(),getCodec());
- }
-
- private abstract class AbstractBindingTransaction<T extends AsyncTransaction<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>>>
- extends AbstractForwardedTransaction<T> implements AsyncTransaction<InstanceIdentifier<?>, DataObject> {
-
- protected AbstractBindingTransaction(final T delegate, final BindingToNormalizedNodeCodec codec) {
- super(delegate, codec);
- }
-
- @Override
- public Object getIdentifier() {
- return getDelegate().getIdentifier();
- }
-
- }
-
-
- private class BindingDataReadTransactionImpl extends AbstractBindingTransaction<DOMDataReadOnlyTransaction> implements
- ReadOnlyTransaction {
-
- protected BindingDataReadTransactionImpl(final DOMDataReadOnlyTransaction delegate,
- final BindingToNormalizedNodeCodec codec) {
- super(delegate, codec);
- }
-
- @Override
- public ListenableFuture<Optional<DataObject>> read(final LogicalDatastoreType store,
- final InstanceIdentifier<?> path) {
- return doRead(getDelegate(), store, path);
- }
-
- @Override
- public void close() {
- getDelegate().close();
- }
+ return new BindingDataWriteTransactionImpl<>(getDelegate().newWriteOnlyTransaction(),getCodec());
}
- private class BindingDataWriteTransactionImpl<T extends DOMDataWriteTransaction> extends
- AbstractBindingTransaction<T> implements WriteTransaction {
-
- protected BindingDataWriteTransactionImpl(final T delegate, final BindingToNormalizedNodeCodec codec) {
- super(delegate, codec);
-
- }
-
- @Override
- public boolean cancel() {
- return doCancel(getDelegate());
- }
-
- @Override
- public void put(final LogicalDatastoreType store, final InstanceIdentifier<?> path, final DataObject data) {
- doPut(getDelegate(), store, path, data);
- }
-
- @Override
- public void merge(final LogicalDatastoreType store, final InstanceIdentifier<?> path, final DataObject data) {
- doMerge(getDelegate(), store, path, data);
- }
-
- @Override
- public void delete(final LogicalDatastoreType store, final InstanceIdentifier<?> path) {
- doDelete(getDelegate(), store, path);
- }
-
- @Override
- public ListenableFuture<RpcResult<TransactionStatus>> commit() {
- return doCommit(getDelegate());
- }
- }
-
- private class BindingDataReadWriteTransactionImpl extends
- BindingDataWriteTransactionImpl<DOMDataReadWriteTransaction> implements ReadWriteTransaction {
-
- protected BindingDataReadWriteTransactionImpl(final DOMDataReadWriteTransaction delegate,
- final BindingToNormalizedNodeCodec codec) {
- super(delegate, codec);
- }
-
- @Override
- public ListenableFuture<Optional<DataObject>> read(final LogicalDatastoreType store,
- final InstanceIdentifier<?> path) {
- return doRead(getDelegate(), store, path);
- }
+ @Override
+ public BindingTransactionChain createTransactionChain(final TransactionChainListener listener) {
+ return new BindingTranslatedTransactionChain(getDelegate(), getCodec(), listener);
}
}
import java.util.Map;
import java.util.Set;
-
+/**
+ * Event representing change in RPC routing table.
+ *
+ *
+ * @param <C> Type, which is used to represent Routing context.
+ * @param <P> Type of data tree path, which is used to identify route.
+ */
public interface RouteChange<C,P> {
+ /**
+ *
+ * Returns a map of removed routes in associated routing contexts.
+ * <p>
+ * This map represents routes, which were withdrawn from broker local
+ * routing table and broker may need to forward RPC to other broker
+ * in order to process RPC request.
+ *
+ * @return Map of contexts and removed routes
+ */
Map<C,Set<P>> getRemovals();
+ /**
+ *
+ * Returns a map of announced routes in associated routing contexts.
+ *
+ * This map represents routes, which were announced by broker
+ * and are present in broker's local routing table. This routes
+ * are processed by implementations which are registered
+ * to originating broker.
+ *
+ * @return Map of contexts and announced routes
+ */
Map<C,Set<P>> getAnnouncements();
}
package org.opendaylight.controller.md.sal.common.api.routing;
import java.util.EventListener;
-
+/**
+ *
+ * Listener which is interested in receiving RouteChangeEvents
+ * for its local broker.
+ * <p>
+ * Listener is registerd via {@link RouteChangePublisher#registerRouteChangeListener(RouteChangeListener)}
+ *
+ *
+ * @param <C> Type, which is used to represent Routing context.
+ * @param <P> Type of data tree path, which is used to identify route.
+ */
public interface RouteChangeListener<C,P> extends EventListener {
+ /**
+ * Callback which is invoked if there is an rpc routing table change.
+ *
+ * @param change Event representing change in local RPC routing table.
+ */
void onRouteChange(RouteChange<C, P> change);
}
import org.opendaylight.yangtools.concepts.ListenerRegistration;
+/**
+ * Publishes changes in local RPC routing table to registered listener.
+ *
+ * @param <C> Type, which is used to represent Routing context.
+ * @param <P> Type of data tree path, which is used to identify route.
+ */
public interface RouteChangePublisher<C,P> {
<L extends RouteChangeListener<C,P>> ListenerRegistration<L> registerRouteChangeListener(L listener);
import org.opendaylight.yangtools.concepts.Path;
import org.opendaylight.yangtools.concepts.Registration;
+/**
+ * Base interface for a routed RPC RPC implementation registration.
+ *
+ * @param <C> the context type used for routing
+ * @param <P> the path identifier type
+ * @param <S> the RPC implementation type
+ */
public interface RoutedRegistration<C, P extends Path<P>, S> extends Registration<S> {
+ /**
+ * Registers the RPC implementation associated with this registration for the given path
+ * identifier and context.
+ *
+ * @param context the context used for routing RPCs to this implementation.
+ * @param path the path identifier for which to register.
+ */
void registerPath(C context, P path);
+
+ /**
+ * Unregisters the RPC implementation associated with this registration for the given path
+ * identifier and context.
+ *
+ * @param context the context used for routing RPCs to this implementation.
+ * @param path the path identifier for which to unregister.
+ */
void unregisterPath(C context, P path);
@Override
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.util.AbstractMap;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
Collections.sort(unorderdChildData, new Comparator<LegacyNodeData>() {
@Override
public int compare(LegacyNodeData arg1, LegacyNodeData arg2) {
- String str1 = arg1.nodeKey.getLocalName();
- if (!(arg1.nodeData instanceof List))
- str1 += arg1.nodeData; // add simple node value
-
- String str2 = arg2.nodeKey.getLocalName();
- if (!(arg2.nodeData instanceof List))
- str2 += arg2.nodeData; // add simple node value
-
- return str1.compareTo(str2);
+ if (!(arg1.nodeData instanceof List) && !(arg2.nodeData instanceof List)) {
+ // if neither is a list, just compare them
+ String str1 = arg1.nodeKey.getLocalName() + arg1.nodeData;
+ String str2 = arg2.nodeKey.getLocalName() + arg2.nodeData;
+ return str1.compareTo(str2);
+ } else if (arg1.nodeData instanceof List && arg2.nodeData instanceof List) {
+ // if both are lists, first check their local name
+ String str1 = arg1.nodeKey.getLocalName();
+ String str2 = arg2.nodeKey.getLocalName();
+ if (!str1.equals(str2)) {
+ return str1.compareTo(str2);
+ } else {
+ // if local names are the same, then look at the list contents
+ List<LegacyNodeData> l1 = (List<LegacyNodeData>) arg1.nodeData;
+ List<LegacyNodeData> l2 = (List<LegacyNodeData>) arg2.nodeData;
+
+ if (l1.size() != l2.size()) {
+ // if the sizes are different, use that
+ return l2.size() - l1.size();
+ } else {
+ // lastly sort and recursively check the list contents
+ Collections.sort(l1, this);
+ Collections.sort(l2, this);
+
+ for (int i = 0 ; i < l1.size() ; i++) {
+ int diff = this.compare(l1.get(i), l2.get(i));
+ if (diff != 0) {
+ return diff;
+ }
+ }
+ return 0;
+ }
+ }
+ } else if( arg1.nodeData instanceof List ) {
+ return -1;
+ } else{
+ return 1;
+ }
}
});
Collections.sort(unorderedChildNodes, new Comparator<Node<?>>() {
@Override
public int compare(Node<?> n1, Node<?> n2) {
- String str1 = n1.getKey().getLocalName();
- if (n1 instanceof SimpleNode)
- str1 += ((SimpleNode<?>) n1).getValue();
-
- String str2 = n2.getKey().getLocalName();
- if (n2 instanceof SimpleNode)
- str2 += ((SimpleNode<?>) n2).getValue();
-
- return str1.compareTo(str2);
+ if (n1 instanceof SimpleNode && n2 instanceof SimpleNode) {
+ // if they're SimpleNodes just compare their strings
+ String str1 = n1.getKey().getLocalName() + ((SimpleNode<?>)n1).getValue();
+ String str2 = n2.getKey().getLocalName() + ((SimpleNode<?>)n2).getValue();
+ return str1.compareTo(str2);
+ } else if (n1 instanceof CompositeNode && n2 instanceof CompositeNode) {
+ // if they're CompositeNodes, things are more interesting
+ String str1 = n1.getKey().getLocalName();
+ String str2 = n2.getKey().getLocalName();
+ if (!str1.equals(str2)) {
+ // if their local names differ, return that difference
+ return str1.compareTo(str2);
+ } else {
+ // otherwise, we need to look at their contents
+ ArrayList<Node<?>> l1 = new ArrayList<Node<?>>( ((CompositeNode)n1).getValue() );
+ ArrayList<Node<?>> l2 = new ArrayList<Node<?>>( ((CompositeNode)n2).getValue() );
+
+ if (l1.size() != l2.size()) {
+ // if they have different numbers of things in them return that
+ return l2.size() - l1.size();
+ } else {
+ // otherwise, compare the individual elements, first sort them
+ Collections.sort(l1, this);
+ Collections.sort(l2, this);
+
+ // then compare them individually
+ for(int i = 0 ; i < l2.size() ; i++) {
+ int diff = this.compare(l1.get(i), l2.get(i));
+ if(diff != 0){
+ return diff;
+ }
+ }
+ return 0;
+ }
+ }
+ } else if (n1 instanceof CompositeNode && n2 instanceof SimpleNode) {
+ return -1;
+ } else if (n2 instanceof CompositeNode && n1 instanceof SimpleNode) {
+ return 1;
+ } else {
+ assertTrue("Expected either SimpleNodes CompositeNodes", false);
+ return 0;
+ }
}
});
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.cluster.datastore;
+
+import akka.actor.Props;
+import akka.actor.UntypedActor;
+import akka.japi.Creator;
+
+public class DataChangeListener extends UntypedActor {
+ @Override public void onReceive(Object message) throws Exception {
+ throw new UnsupportedOperationException("onReceive");
+ }
+
+ public static Props props() {
+ return Props.create(new Creator<DataChangeListener>() {
+ @Override
+ public DataChangeListener create() throws Exception {
+ return new DataChangeListener();
+ }
+
+ });
+
+ }
+}
package org.opendaylight.controller.cluster.datastore;
+import akka.actor.ActorRef;
+import akka.actor.ActorSystem;
+import org.opendaylight.controller.cluster.datastore.messages.RegisterChangeListener;
+import org.opendaylight.controller.cluster.datastore.messages.RegisterChangeListenerReply;
+import org.opendaylight.controller.cluster.datastore.messages.UpdateSchemaContext;
+import org.opendaylight.controller.cluster.datastore.utils.ActorContext;
import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker;
import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener;
import org.opendaylight.controller.sal.core.spi.data.DOMStore;
import org.opendaylight.yangtools.concepts.ListenerRegistration;
import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
*
*/
-public class DistributedDataStore implements DOMStore {
+public class DistributedDataStore implements DOMStore, SchemaContextListener {
+
+ private static final Logger
+ LOG = LoggerFactory.getLogger(DistributedDataStore.class);
+
+
+ private final String type;
+ private final ActorContext actorContext;
+
+ public DistributedDataStore(ActorSystem actorSystem, String type) {
+ this(new ActorContext(actorSystem, actorSystem.actorOf(ShardManager.props(type))), type);
+ }
+
+ public DistributedDataStore(ActorContext actorContext, String type) {
+ this.type = type;
+ this.actorContext = actorContext;
+ }
+
@Override
- public <L extends AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>>> ListenerRegistration<L> registerChangeListener(InstanceIdentifier path, L listener, AsyncDataBroker.DataChangeScope scope) {
- return new ListenerRegistrationProxy();
+ public <L extends AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>>> ListenerRegistration<L> registerChangeListener(
+ InstanceIdentifier path, L listener,
+ AsyncDataBroker.DataChangeScope scope) {
+
+ ActorRef dataChangeListenerActor = actorContext.getActorSystem().actorOf(DataChangeListener.props());
+
+ Object result = actorContext.executeShardOperation(Shard.DEFAULT_NAME,
+ new RegisterChangeListener(path, dataChangeListenerActor.path(),
+ AsyncDataBroker.DataChangeScope.BASE),
+ ActorContext.ASK_DURATION);
+
+ RegisterChangeListenerReply reply = (RegisterChangeListenerReply) result;
+ return new ListenerRegistrationProxy(reply.getListenerRegistrationPath());
}
+
+
@Override
public DOMStoreTransactionChain createTransactionChain() {
- return new TransactionChainProxy();
+ return new TransactionChainProxy(actorContext);
}
@Override
public DOMStoreReadTransaction newReadOnlyTransaction() {
- return new TransactionProxy();
+ return new TransactionProxy(actorContext, TransactionProxy.TransactionType.READ_ONLY);
}
@Override
public DOMStoreWriteTransaction newWriteOnlyTransaction() {
- return new TransactionProxy();
+ return new TransactionProxy(actorContext, TransactionProxy.TransactionType.WRITE_ONLY);
}
@Override
public DOMStoreReadWriteTransaction newReadWriteTransaction() {
- return new TransactionProxy();
+ return new TransactionProxy(actorContext, TransactionProxy.TransactionType.READ_WRITE);
+ }
+
+ @Override public void onGlobalContextUpdated(SchemaContext schemaContext) {
+ actorContext.getShardManager().tell(new UpdateSchemaContext(schemaContext), null);
}
}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.cluster.datastore;
+
+import akka.actor.ActorSelection;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+public class ListenerProxy implements AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>>{
+ private final ActorSelection listenerRegistrationActor;
+
+ public ListenerProxy(ActorSelection listenerRegistrationActor) {
+ this.listenerRegistrationActor = listenerRegistrationActor;
+ }
+
+ @Override public void onDataChanged(
+ AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change) {
+ throw new UnsupportedOperationException("onDataChanged");
+ }
+}
package org.opendaylight.controller.cluster.datastore;
+import akka.actor.ActorPath;
import org.opendaylight.yangtools.concepts.ListenerRegistration;
/**
* The ListenerRegistrationProxy talks to a remote ListenerRegistration actor.
*/
public class ListenerRegistrationProxy implements ListenerRegistration {
+ private final ActorPath listenerRegistrationPath;
+
+ public ListenerRegistrationProxy(ActorPath listenerRegistrationPath) {
+
+ this.listenerRegistrationPath = listenerRegistrationPath;
+ }
+
@Override
public Object getInstance() {
throw new UnsupportedOperationException("getInstance");
package org.opendaylight.controller.cluster.datastore;
import akka.actor.ActorRef;
+import akka.actor.ActorSelection;
+import akka.actor.Props;
import akka.event.Logging;
import akka.event.LoggingAdapter;
+import akka.japi.Creator;
+import akka.persistence.Persistent;
import akka.persistence.UntypedProcessor;
+import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
+import org.opendaylight.controller.cluster.datastore.messages.CommitTransactionReply;
import org.opendaylight.controller.cluster.datastore.messages.CreateTransactionChain;
import org.opendaylight.controller.cluster.datastore.messages.CreateTransactionChainReply;
+import org.opendaylight.controller.cluster.datastore.messages.ForwardedCommitTransaction;
import org.opendaylight.controller.cluster.datastore.messages.RegisterChangeListener;
import org.opendaylight.controller.cluster.datastore.messages.RegisterChangeListenerReply;
import org.opendaylight.controller.cluster.datastore.messages.UpdateSchemaContext;
+import org.opendaylight.controller.cluster.datastore.modification.Modification;
import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener;
import org.opendaylight.controller.md.sal.dom.store.impl.InMemoryDOMDataStore;
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreThreePhaseCommitCohort;
import org.opendaylight.controller.sal.core.spi.data.DOMStoreTransactionChain;
import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
/**
- * A Shard represents a portion of the logical data tree
- * <p/>
+ * A Shard represents a portion of the logical data tree <br/>
+ * <p>
* Our Shard uses InMemoryDataStore as it's internal representation and delegates all requests it
- *
+ * </p>
*/
public class Shard extends UntypedProcessor {
- ListeningExecutorService storeExecutor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(2));
+ public static final String DEFAULT_NAME = "default";
+
+ private final ListeningExecutorService storeExecutor =
+ MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(2));
+
+ private final InMemoryDOMDataStore store;
+
+ private final Map<Modification, DOMStoreThreePhaseCommitCohort>
+ modificationToCohort = new HashMap<>();
+
+ private final LoggingAdapter log =
+ Logging.getLogger(getContext().system(), this);
+
+ private Shard(String name) {
+ store = new InMemoryDOMDataStore(name, storeExecutor);
+ }
+
+ public static Props props(final String name) {
+ return Props.create(new Creator<Shard>() {
+
+ @Override
+ public Shard create() throws Exception {
+ return new Shard(name);
+ }
+
+ });
+ }
+
+ @Override
+ public void onReceive(Object message) throws Exception {
+ if (message instanceof CreateTransactionChain) {
+ createTransactionChain();
+ } else if (message instanceof RegisterChangeListener) {
+ registerChangeListener((RegisterChangeListener) message);
+ } else if (message instanceof UpdateSchemaContext) {
+ updateSchemaContext((UpdateSchemaContext) message);
+ } else if (message instanceof ForwardedCommitTransaction) {
+ handleForwardedCommit((ForwardedCommitTransaction) message);
+ } else if (message instanceof Persistent) {
+ commit((Persistent) message);
+ }
+ }
+
+ private void commit(Persistent message) {
+ Modification modification = (Modification) message.payload();
+ DOMStoreThreePhaseCommitCohort cohort =
+ modificationToCohort.remove(modification);
+ if (cohort == null) {
+ log.error(
+ "Could not find cohort for modification : " + modification);
+ return;
+ }
+ final ListenableFuture<Void> future = cohort.commit();
+ final ActorRef sender = getSender();
+ final ActorRef self = getSelf();
+ future.addListener(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ future.get();
+ sender.tell(new CommitTransactionReply(), self);
+ } catch (InterruptedException | ExecutionException e) {
+ log.error(e, "An exception happened when committing");
+ }
+ }
+ }, getContext().dispatcher());
+ }
- private final InMemoryDOMDataStore store = new InMemoryDOMDataStore("OPER", storeExecutor);
+ private void handleForwardedCommit(ForwardedCommitTransaction message) {
+ log.info("received forwarded transaction");
+ modificationToCohort
+ .put(message.getModification(), message.getCohort());
+ getSelf().forward(Persistent.create(message.getModification()),
+ getContext());
+ }
- LoggingAdapter log = Logging.getLogger(getContext().system(), this);
+ private void updateSchemaContext(UpdateSchemaContext message) {
+ store.onGlobalContextUpdated(message.getSchemaContext());
+ }
+
+ private void registerChangeListener(
+ RegisterChangeListener registerChangeListener) {
+
+ ActorSelection listenerRegistrationActor = getContext()
+ .system().actorSelection(registerChangeListener.getDataChangeListenerPath());
+
+ AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>>
+ listener = new ListenerProxy(listenerRegistrationActor);
+
+ org.opendaylight.yangtools.concepts.ListenerRegistration<AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>>>
+ registration =
+ store.registerChangeListener(registerChangeListener.getPath(),
+ listener, registerChangeListener.getScope());
+ ActorRef listenerRegistration =
+ getContext().actorOf(ListenerRegistration.props(registration));
+ getSender()
+ .tell(new RegisterChangeListenerReply(listenerRegistration.path()),
+ getSelf());
+ }
- @Override
- public void onReceive(Object message) throws Exception {
- if (message instanceof CreateTransactionChain) {
- createTransactionChain();
- } else if(message instanceof RegisterChangeListener){
- registerChangeListener((RegisterChangeListener) message);
- } else if(message instanceof UpdateSchemaContext){
- updateSchemaContext((UpdateSchemaContext) message);
+ private void createTransactionChain() {
+ DOMStoreTransactionChain chain = store.createTransactionChain();
+ ActorRef transactionChain =
+ getContext().actorOf(ShardTransactionChain.props(chain));
+ getSender()
+ .tell(new CreateTransactionChainReply(transactionChain.path()),
+ getSelf());
}
- }
-
- private void updateSchemaContext(UpdateSchemaContext message) {
- store.onGlobalContextUpdated(message.getSchemaContext());
- }
-
- private void registerChangeListener(RegisterChangeListener registerChangeListener) {
- org.opendaylight.yangtools.concepts.ListenerRegistration<AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>>> registration =
- store.registerChangeListener(registerChangeListener.getPath(), registerChangeListener.getListener(), registerChangeListener.getScope());
- ActorRef listenerRegistration = getContext().actorOf(ListenerRegistration.props(registration));
- getSender().tell(new RegisterChangeListenerReply(listenerRegistration.path()), getSelf());
- }
-
- private void createTransactionChain() {
- DOMStoreTransactionChain chain = store.createTransactionChain();
- ActorRef transactionChain = getContext().actorOf(ShardTransactionChain.props(chain));
- getSender().tell(new CreateTransactionChainReply(transactionChain.path()), getSelf());
- }
}
package org.opendaylight.controller.cluster.datastore;
+import akka.actor.ActorPath;
+import akka.actor.ActorRef;
import akka.actor.Address;
+import akka.actor.Props;
import akka.actor.UntypedActor;
import akka.event.Logging;
import akka.event.LoggingAdapter;
+import akka.japi.Creator;
import org.opendaylight.controller.cluster.datastore.messages.FindPrimary;
+import org.opendaylight.controller.cluster.datastore.messages.PrimaryFound;
import org.opendaylight.controller.cluster.datastore.messages.PrimaryNotFound;
+import org.opendaylight.controller.cluster.datastore.messages.UpdateSchemaContext;
import java.util.HashMap;
import java.util.List;
/**
* The ShardManager has the following jobs,
- *
- * - Create all the local shard replicas that belong on this cluster member
- * - Find the primary replica for any given shard
- * - Engage in shard replica elections which decide which replica should be the primary
- *
- * Creation of Shard replicas
- * ==========================
- * When the ShardManager is constructed it reads the cluster configuration to find out which shard replicas
- * belong on this member. It finds out the name of the current cluster member from the Akka Clustering Service.
- *
- * Replica Elections
- * =================
- * The Shard Manager uses multiple cues to initiate election.
- * - When a member of the cluster dies
- * - When a local shard replica dies
- * - When a local shard replica comes alive
+ * <p>
+ * <li> Create all the local shard replicas that belong on this cluster member
+ * <li> Find the primary replica for any given shard
+ * <li> Engage in shard replica elections which decide which replica should be the primary
+ * </p>
+ * <p/>
+ * <h3>>Creation of Shard replicas</h3
+ * <p>
+ * When the ShardManager is constructed it reads the cluster configuration to find out which shard replicas
+ * belong on this member. It finds out the name of the current cluster member from the Akka Clustering Service.
+ * </p>
+ * <p/>
+ * <h3> Replica Elections </h3>
+ * <p/>
+ * <p>
+ * The Shard Manager uses multiple cues to initiate election.
+ * <li> When a member of the cluster dies
+ * <li> When a local shard replica dies
+ * <li> When a local shard replica comes alive
+ * </p>
*/
public class ShardManager extends UntypedActor {
- // Stores a mapping between a shard name and the address of the current primary
- private final Map<String, Address> shardNameToPrimaryAddress = new HashMap<>();
+ // Stores a mapping between a shard name and the address of the current primary
+ private final Map<String, Address> shardNameToPrimaryAddress = new HashMap<>();
+
+ // Stores a mapping between a member name and the address of the member
+ private final Map<String, Address> memberNameToAddress = new HashMap<>();
+
+ // Stores a mapping between the shard name and all the members on which a replica of that shard are available
+ private final Map<String, List<String>> shardNameToMembers = new HashMap<>();
- // Stores a mapping between a member name and the address of the member
- private final Map<String, Address> memberNameToAddress = new HashMap<>();
+ private final LoggingAdapter log = Logging.getLogger(getContext().system(), this);
- // Stores a mapping between the shard name and all the members on which a replica of that shard are available
- private final Map<String, List<String>> shardNameToMembers = new HashMap<>();
+ private final ActorPath defaultShardPath;
- LoggingAdapter log = Logging.getLogger(getContext().system(), this);
+ /**
+ *
+ * @param type defines the kind of data that goes into shards created by this shard manager. Examples of type would be
+ * configuration or operational
+ */
+ private ShardManager(String type){
+ ActorRef actor = getContext().actorOf(Shard.props(Shard.DEFAULT_NAME + "-" + type));
+ defaultShardPath = actor.path();
+ }
- @Override
- public void onReceive(Object message) throws Exception {
- if(message instanceof FindPrimary ){
- FindPrimary msg = ((FindPrimary) message);
- getSender().tell(new PrimaryNotFound(msg.getShardName()), getSelf());
- }
+ public static Props props(final String type){
+ return Props.create(new Creator<ShardManager>(){
+
+ @Override
+ public ShardManager create() throws Exception {
+ return new ShardManager(type);
+ }
+ });
+ }
+
+ @Override
+ public void onReceive(Object message) throws Exception {
+ if (message instanceof FindPrimary) {
+ FindPrimary msg = ((FindPrimary) message);
+ String shardName = msg.getShardName();
+ if(Shard.DEFAULT_NAME.equals(shardName)){
+ getSender().tell(new PrimaryFound(defaultShardPath.toString()), getSelf());
+ } else {
+ getSender().tell(new PrimaryNotFound(shardName), getSelf());
+ }
+ } else if(message instanceof UpdateSchemaContext){
+ // FIXME : Notify all local shards of a context change
+ getContext().system().actorSelection(defaultShardPath).forward(message, getContext());
}
+ }
+
+
}
import org.opendaylight.controller.cluster.datastore.messages.ReadyTransactionReply;
import org.opendaylight.controller.cluster.datastore.messages.WriteData;
import org.opendaylight.controller.cluster.datastore.messages.WriteDataReply;
+import org.opendaylight.controller.cluster.datastore.modification.CompositeModification;
+import org.opendaylight.controller.cluster.datastore.modification.DeleteModification;
+import org.opendaylight.controller.cluster.datastore.modification.ImmutableCompositeModification;
+import org.opendaylight.controller.cluster.datastore.modification.MergeModification;
+import org.opendaylight.controller.cluster.datastore.modification.MutableCompositeModification;
+import org.opendaylight.controller.cluster.datastore.modification.WriteModification;
import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadWriteTransaction;
import org.opendaylight.controller.sal.core.spi.data.DOMStoreThreePhaseCommitCohort;
import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
/**
* The ShardTransaction Actor represents a remote transaction
- *
+ *<p>
* The ShardTransaction Actor delegates all actions to DOMDataReadWriteTransaction
- *
+ *</p>
+ *<p>
* Even though the DOMStore and the DOMStoreTransactionChain implement multiple types of transactions
* the ShardTransaction Actor only works with read-write transactions. This is just to keep the logic simple. At this
* time there are no known advantages for creating a read-only or write-only transaction which may change over time
* at which point we can optimize things in the distributed store as well.
- *
- * Handles Messages
- * ----------------
- * {@link org.opendaylight.controller.cluster.datastore.messages.ReadData}
- * {@link org.opendaylight.controller.cluster.datastore.messages.WriteData}
- * {@link org.opendaylight.controller.cluster.datastore.messages.MergeData}
- * {@link org.opendaylight.controller.cluster.datastore.messages.DeleteData}
- * {@link org.opendaylight.controller.cluster.datastore.messages.ReadyTransaction}
- * {@link org.opendaylight.controller.cluster.datastore.messages.CloseTransaction}
+ *</p>
+ *<p>
+ * Handles Messages <br/>
+ * ---------------- <br/>
+ * <li> {@link org.opendaylight.controller.cluster.datastore.messages.ReadData}
+ * <li> {@link org.opendaylight.controller.cluster.datastore.messages.WriteData}
+ * <li> {@link org.opendaylight.controller.cluster.datastore.messages.MergeData}
+ * <li> {@link org.opendaylight.controller.cluster.datastore.messages.DeleteData}
+ * <li> {@link org.opendaylight.controller.cluster.datastore.messages.ReadyTransaction}
+ * <li> {@link org.opendaylight.controller.cluster.datastore.messages.CloseTransaction}
+ * </p>
*/
public class ShardTransaction extends UntypedActor {
+ private final ActorRef shardActor;
+
private final DOMStoreReadWriteTransaction transaction;
+ private final MutableCompositeModification modification = new MutableCompositeModification();
+
private final LoggingAdapter log = Logging.getLogger(getContext().system(), this);
- public ShardTransaction(DOMStoreReadWriteTransaction transaction) {
+ public ShardTransaction(DOMStoreReadWriteTransaction transaction, ActorRef shardActor) {
this.transaction = transaction;
+ this.shardActor = shardActor;
}
- public static Props props(final DOMStoreReadWriteTransaction transaction){
+ public static Props props(final DOMStoreReadWriteTransaction transaction, final ActorRef shardActor){
return Props.create(new Creator<ShardTransaction>(){
@Override
public ShardTransaction create() throws Exception {
- return new ShardTransaction(transaction);
+ return new ShardTransaction(transaction, shardActor);
}
});
}
readyTransaction((ReadyTransaction) message);
} else if(message instanceof CloseTransaction){
closeTransaction((CloseTransaction) message);
+ } else if(message instanceof GetCompositedModification){
+ // This is here for testing only
+ getSender().tell(new GetCompositeModificationReply(new ImmutableCompositeModification(modification)), getSelf());
}
}
private void writeData(WriteData message){
+ modification.addModification(new WriteModification(message.getPath(), message.getData()));
transaction.write(message.getPath(), message.getData());
getSender().tell(new WriteDataReply(), getSelf());
}
private void mergeData(MergeData message){
+ modification.addModification(new MergeModification(message.getPath(), message.getData()));
transaction.merge(message.getPath(), message.getData());
getSender().tell(new MergeDataReply(), getSelf());
}
private void deleteData(DeleteData message){
+ modification.addModification(new DeleteModification(message.getPath()));
transaction.delete(message.getPath());
getSender().tell(new DeleteDataReply(), getSelf());
}
private void readyTransaction(ReadyTransaction message){
DOMStoreThreePhaseCommitCohort cohort = transaction.ready();
- ActorRef cohortActor = getContext().actorOf(ThreePhaseCommitCohort.props(cohort));
+ ActorRef cohortActor = getContext().actorOf(ThreePhaseCommitCohort.props(cohort, shardActor, modification));
getSender().tell(new ReadyTransactionReply(cohortActor.path()), getSelf());
}
transaction.close();
getSender().tell(new CloseTransactionReply(), getSelf());
}
+
+
+ // These classes are in here for test purposes only
+
+ static class GetCompositedModification {
+
+ }
+
+ static class GetCompositeModificationReply {
+ private final CompositeModification modification;
+
+
+ GetCompositeModificationReply(CompositeModification modification) {
+ this.modification = modification;
+ }
+
+
+ public CompositeModification getModification() {
+ return modification;
+ }
+ }
}
public void onReceive(Object message) throws Exception {
if(message instanceof CreateTransaction){
DOMStoreReadWriteTransaction transaction = chain.newReadWriteTransaction();
- ActorRef transactionActor = getContext().actorOf(ShardTransaction.props(transaction));
+ ActorRef transactionActor = getContext().actorOf(ShardTransaction.props(transaction, getContext().parent()));
getSender().tell(new CreateTransactionReply(transactionActor.path()), getSelf());
} else if (message instanceof CloseTransactionChain){
chain.close();
package org.opendaylight.controller.cluster.datastore;
+import akka.actor.ActorRef;
import akka.actor.Props;
import akka.actor.UntypedActor;
+import akka.event.Logging;
+import akka.event.LoggingAdapter;
import akka.japi.Creator;
+import com.google.common.util.concurrent.ListenableFuture;
+import org.opendaylight.controller.cluster.datastore.messages.AbortTransaction;
+import org.opendaylight.controller.cluster.datastore.messages.AbortTransactionReply;
+import org.opendaylight.controller.cluster.datastore.messages.CanCommitTransaction;
+import org.opendaylight.controller.cluster.datastore.messages.CanCommitTransactionReply;
+import org.opendaylight.controller.cluster.datastore.messages.CommitTransaction;
+import org.opendaylight.controller.cluster.datastore.messages.ForwardedCommitTransaction;
+import org.opendaylight.controller.cluster.datastore.messages.PreCommitTransaction;
+import org.opendaylight.controller.cluster.datastore.messages.PreCommitTransactionReply;
+import org.opendaylight.controller.cluster.datastore.modification.CompositeModification;
import org.opendaylight.controller.sal.core.spi.data.DOMStoreThreePhaseCommitCohort;
+import java.util.concurrent.ExecutionException;
+
public class ThreePhaseCommitCohort extends UntypedActor{
private final DOMStoreThreePhaseCommitCohort cohort;
+ private final ActorRef shardActor;
+ private final CompositeModification modification;
- public ThreePhaseCommitCohort(DOMStoreThreePhaseCommitCohort cohort) {
-
+ public ThreePhaseCommitCohort(DOMStoreThreePhaseCommitCohort cohort, ActorRef shardActor, CompositeModification modification) {
this.cohort = cohort;
+ this.shardActor = shardActor;
+ this.modification = modification;
}
- @Override
- public void onReceive(Object message) throws Exception {
- throw new UnsupportedOperationException("onReceive");
- }
+ private final LoggingAdapter log = Logging.getLogger(getContext().system(), this);
- public static Props props(final DOMStoreThreePhaseCommitCohort cohort) {
+ public static Props props(final DOMStoreThreePhaseCommitCohort cohort, final ActorRef shardActor, final CompositeModification modification) {
return Props.create(new Creator<ThreePhaseCommitCohort>(){
@Override
public ThreePhaseCommitCohort create() throws Exception {
- return new ThreePhaseCommitCohort(cohort);
+ return new ThreePhaseCommitCohort(cohort, shardActor, modification);
}
});
}
+
+ @Override
+ public void onReceive(Object message) throws Exception {
+ if(message instanceof CanCommitTransaction){
+ canCommit((CanCommitTransaction) message);
+ } else if(message instanceof PreCommitTransaction) {
+ preCommit((PreCommitTransaction) message);
+ } else if(message instanceof CommitTransaction){
+ commit((CommitTransaction) message);
+ } else if (message instanceof AbortTransaction){
+ abort((AbortTransaction) message);
+ }
+ }
+
+ private void abort(AbortTransaction message) {
+ final ListenableFuture<Void> future = cohort.abort();
+ final ActorRef sender = getSender();
+ final ActorRef self = getSelf();
+
+ future.addListener(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ future.get();
+ sender.tell(new AbortTransactionReply(), self);
+ } catch (InterruptedException | ExecutionException e) {
+ log.error(e, "An exception happened when aborting");
+ }
+ }
+ }, getContext().dispatcher());
+ }
+
+ private void commit(CommitTransaction message) {
+ // Forward the commit to the shard
+ log.info("Commit transaction now + " + shardActor);
+ shardActor.forward(new ForwardedCommitTransaction(cohort, modification), getContext());
+
+ }
+
+ private void preCommit(PreCommitTransaction message) {
+ final ListenableFuture<Void> future = cohort.preCommit();
+ final ActorRef sender = getSender();
+ final ActorRef self = getSelf();
+
+ future.addListener(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ future.get();
+ sender.tell(new PreCommitTransactionReply(), self);
+ } catch (InterruptedException | ExecutionException e) {
+ log.error(e, "An exception happened when preCommitting");
+ }
+ }
+ }, getContext().dispatcher());
+
+ }
+
+ private void canCommit(CanCommitTransaction message) {
+ final ListenableFuture<Boolean> future = cohort.canCommit();
+ final ActorRef sender = getSender();
+ final ActorRef self = getSelf();
+
+ future.addListener(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Boolean canCommit = future.get();
+ sender.tell(new CanCommitTransactionReply(canCommit), self);
+ } catch (InterruptedException | ExecutionException e) {
+ log.error(e, "An exception happened when aborting");
+ }
+ }
+ }, getContext().dispatcher());
+
+ }
}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.cluster.datastore;
+
+import akka.actor.ActorPath;
+import com.google.common.util.concurrent.ListenableFuture;
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreThreePhaseCommitCohort;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * ThreePhaseCommitCohortProxy represents a set of remote cohort proxies
+ */
+public class ThreePhaseCommitCohortProxy implements
+ DOMStoreThreePhaseCommitCohort{
+
+ private final List<ActorPath> cohortPaths;
+
+ public ThreePhaseCommitCohortProxy(List<ActorPath> cohortPaths) {
+
+ this.cohortPaths = cohortPaths;
+ }
+
+ @Override public ListenableFuture<Boolean> canCommit() {
+ throw new UnsupportedOperationException("canCommit");
+ }
+
+ @Override public ListenableFuture<Void> preCommit() {
+ throw new UnsupportedOperationException("preCommit");
+ }
+
+ @Override public ListenableFuture<Void> abort() {
+ throw new UnsupportedOperationException("abort");
+ }
+
+ @Override public ListenableFuture<Void> commit() {
+ throw new UnsupportedOperationException("commit");
+ }
+
+ public List<ActorPath> getCohortPaths() {
+ return Collections.unmodifiableList(this.cohortPaths);
+ }
+}
package org.opendaylight.controller.cluster.datastore;
+import org.opendaylight.controller.cluster.datastore.utils.ActorContext;
import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadTransaction;
import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadWriteTransaction;
import org.opendaylight.controller.sal.core.spi.data.DOMStoreTransactionChain;
* TransactionChainProxy acts as a proxy for a DOMStoreTransactionChain created on a remote shard
*/
public class TransactionChainProxy implements DOMStoreTransactionChain{
+ private final ActorContext actorContext;
+
+ public TransactionChainProxy(ActorContext actorContext) {
+ this.actorContext = actorContext;
+ }
+
@Override
public DOMStoreReadTransaction newReadOnlyTransaction() {
- throw new UnsupportedOperationException("newReadOnlyTransaction");
+ return new TransactionProxy(actorContext,
+ TransactionProxy.TransactionType.READ_ONLY);
}
@Override
public DOMStoreReadWriteTransaction newReadWriteTransaction() {
- throw new UnsupportedOperationException("newReadWriteTransaction");
+ return new TransactionProxy(actorContext,
+ TransactionProxy.TransactionType.WRITE_ONLY);
}
@Override
public DOMStoreWriteTransaction newWriteOnlyTransaction() {
- throw new UnsupportedOperationException("newWriteOnlyTransaction");
+ return new TransactionProxy(actorContext,
+ TransactionProxy.TransactionType.READ_WRITE);
}
@Override
public void close() {
- throw new UnsupportedOperationException("close");
+ throw new UnsupportedOperationException("close - not sure what to do here?");
}
}
package org.opendaylight.controller.cluster.datastore;
+import akka.actor.ActorPath;
+import akka.actor.ActorSelection;
import com.google.common.base.Optional;
import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListenableFutureTask;
+import org.opendaylight.controller.cluster.datastore.messages.CloseTransaction;
+import org.opendaylight.controller.cluster.datastore.messages.CreateTransaction;
+import org.opendaylight.controller.cluster.datastore.messages.CreateTransactionReply;
+import org.opendaylight.controller.cluster.datastore.messages.DeleteData;
+import org.opendaylight.controller.cluster.datastore.messages.MergeData;
+import org.opendaylight.controller.cluster.datastore.messages.ReadData;
+import org.opendaylight.controller.cluster.datastore.messages.ReadDataReply;
+import org.opendaylight.controller.cluster.datastore.messages.ReadyTransaction;
+import org.opendaylight.controller.cluster.datastore.messages.ReadyTransactionReply;
+import org.opendaylight.controller.cluster.datastore.messages.WriteData;
+import org.opendaylight.controller.cluster.datastore.utils.ActorContext;
import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadWriteTransaction;
import org.opendaylight.controller.sal.core.spi.data.DOMStoreThreePhaseCommitCohort;
import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicLong;
+
/**
* TransactionProxy acts as a proxy for one or more transactions that were created on a remote shard
- *
+ * <p>
* Creating a transaction on the consumer side will create one instance of a transaction proxy. If during
* the transaction reads and writes are done on data that belongs to different shards then a separate transaction will
* be created on each of those shards by the TransactionProxy
- *
+ *</p>
+ * <p>
* The TransactionProxy does not make any guarantees about atomicity or order in which the transactions on the various
* shards will be executed.
- *
+ * </p>
*/
public class TransactionProxy implements DOMStoreReadWriteTransaction {
+
+ public enum TransactionType {
+ READ_ONLY,
+ WRITE_ONLY,
+ READ_WRITE
+ }
+
+ private static final AtomicLong counter = new AtomicLong();
+
+ private final TransactionType readOnly;
+ private final ActorContext actorContext;
+ private final Map<String, ActorSelection> remoteTransactionPaths = new HashMap<>();
+ private final String identifier;
+
+ public TransactionProxy(
+ ActorContext actorContext,
+ TransactionType readOnly) {
+
+ this.identifier = "transaction-" + counter.getAndIncrement();
+ this.readOnly = readOnly;
+ this.actorContext = actorContext;
+
+ Object response = actorContext.executeShardOperation(Shard.DEFAULT_NAME, new CreateTransaction(), ActorContext.ASK_DURATION);
+ if(response instanceof CreateTransactionReply){
+ CreateTransactionReply reply = (CreateTransactionReply) response;
+ remoteTransactionPaths.put(Shard.DEFAULT_NAME, actorContext.actorSelection(reply.getTransactionPath()));
+ }
+ }
+
@Override
- public ListenableFuture<Optional<NormalizedNode<?, ?>>> read(InstanceIdentifier path) {
- throw new UnsupportedOperationException("read");
+ public ListenableFuture<Optional<NormalizedNode<?, ?>>> read(final InstanceIdentifier path) {
+ final ActorSelection remoteTransaction = remoteTransactionFromIdentifier(path);
+
+ Callable<Optional<NormalizedNode<?,?>>> call = new Callable() {
+
+ @Override public Optional<NormalizedNode<?,?>> call() throws Exception {
+ Object response = actorContext
+ .executeRemoteOperation(remoteTransaction, new ReadData(path),
+ ActorContext.ASK_DURATION);
+ if(response instanceof ReadDataReply){
+ ReadDataReply reply = (ReadDataReply) response;
+ //FIXME : A cast should not be required here ???
+ return (Optional<NormalizedNode<?, ?>>) Optional.of(reply.getNormalizedNode());
+ }
+
+ return Optional.absent();
+ }
+ };
+
+ ListenableFutureTask<Optional<NormalizedNode<?, ?>>>
+ future = ListenableFutureTask.create(call);
+
+ //FIXME : Use a thread pool here
+ Executors.newSingleThreadExecutor().submit(future);
+
+ return future;
}
@Override
public void write(InstanceIdentifier path, NormalizedNode<?, ?> data) {
- throw new UnsupportedOperationException("write");
+ final ActorSelection remoteTransaction = remoteTransactionFromIdentifier(path);
+ remoteTransaction.tell(new WriteData(path, data), null);
}
@Override
public void merge(InstanceIdentifier path, NormalizedNode<?, ?> data) {
- throw new UnsupportedOperationException("merge");
+ final ActorSelection remoteTransaction = remoteTransactionFromIdentifier(path);
+ remoteTransaction.tell(new MergeData(path, data), null);
}
@Override
public void delete(InstanceIdentifier path) {
- throw new UnsupportedOperationException("delete");
+ final ActorSelection remoteTransaction = remoteTransactionFromIdentifier(path);
+ remoteTransaction.tell(new DeleteData(path), null);
}
@Override
public DOMStoreThreePhaseCommitCohort ready() {
- throw new UnsupportedOperationException("ready");
+ List<ActorPath> cohortPaths = new ArrayList<>();
+
+ for(ActorSelection remoteTransaction : remoteTransactionPaths.values()) {
+ Object result = actorContext.executeRemoteOperation(remoteTransaction,
+ new ReadyTransaction(),
+ ActorContext.ASK_DURATION
+ );
+
+ if(result instanceof ReadyTransactionReply){
+ ReadyTransactionReply reply = (ReadyTransactionReply) result;
+ cohortPaths.add(reply.getCohortPath());
+ }
+ }
+
+ return new ThreePhaseCommitCohortProxy(cohortPaths);
}
@Override
public Object getIdentifier() {
- throw new UnsupportedOperationException("getIdentifier");
+ return this.identifier;
}
@Override
public void close() {
- throw new UnsupportedOperationException("close");
+ for(ActorSelection remoteTransaction : remoteTransactionPaths.values()) {
+ remoteTransaction.tell(new CloseTransaction(), null);
+ }
+ }
+
+ private ActorSelection remoteTransactionFromIdentifier(InstanceIdentifier path){
+ String shardName = shardNameFromIdentifier(path);
+ return remoteTransactionPaths.get(shardName);
+ }
+
+ private String shardNameFromIdentifier(InstanceIdentifier path){
+ return Shard.DEFAULT_NAME;
}
}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.cluster.datastore.messages;
+
+public class AbortTransaction {
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.cluster.datastore.messages;
+
+public class AbortTransactionReply {
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.cluster.datastore.messages;
+
+public class CanCommitTransaction {
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.cluster.datastore.messages;
+
+public class CanCommitTransactionReply {
+ private final Boolean canCommit;
+
+ public CanCommitTransactionReply(Boolean canCommit) {
+ this.canCommit = canCommit;
+ }
+
+ public Boolean getCanCommit() {
+ return canCommit;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.cluster.datastore.messages;
+
+public class CommitTransaction {
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.cluster.datastore.messages;
+
+public class CommitTransactionReply {
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.cluster.datastore.messages;
+
+import org.opendaylight.controller.cluster.datastore.modification.Modification;
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreThreePhaseCommitCohort;
+
+public class ForwardedCommitTransaction {
+ private final DOMStoreThreePhaseCommitCohort cohort;
+ private final Modification modification;
+
+ public ForwardedCommitTransaction(DOMStoreThreePhaseCommitCohort cohort, Modification modification){
+ this.cohort = cohort;
+ this.modification = modification;
+ }
+
+ public DOMStoreThreePhaseCommitCohort getCohort() {
+ return cohort;
+ }
+
+ public Modification getModification() {
+ return modification;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.cluster.datastore.messages;
+
+public class PreCommitTransaction {
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.cluster.datastore.messages;
+
+public class PreCommitTransactionReply {
+}
package org.opendaylight.controller.cluster.datastore.messages;
public class PrimaryFound {
+ private final String primaryPath;
+
+ public PrimaryFound(String primaryPath) {
+ this.primaryPath = primaryPath;
+ }
+
+ public String getPrimaryPath() {
+ return primaryPath;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ PrimaryFound that = (PrimaryFound) o;
+
+ if (!primaryPath.equals(that.primaryPath)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return primaryPath.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "PrimaryFound{" +
+ "primaryPath='" + primaryPath + '\'' +
+ '}';
+ }
+
+
}
import akka.actor.ActorPath;
public class ReadyTransactionReply {
- private final ActorPath path;
+ private final ActorPath cohortPath;
- public ReadyTransactionReply(ActorPath path) {
+ public ReadyTransactionReply(ActorPath cohortPath) {
- this.path = path;
+ this.cohortPath = cohortPath;
}
- public ActorPath getPath() {
- return path;
+ public ActorPath getCohortPath() {
+ return cohortPath;
}
}
package org.opendaylight.controller.cluster.datastore.messages;
+import akka.actor.ActorPath;
import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker;
-import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeListener;
import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
public class RegisterChangeListener {
- private final InstanceIdentifier path;
- private final AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>> listener;
- private final AsyncDataBroker.DataChangeScope scope;
+ private final InstanceIdentifier path;
+ private final ActorPath dataChangeListenerPath;
+ private final AsyncDataBroker.DataChangeScope scope;
- public RegisterChangeListener(InstanceIdentifier path, AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>> listener, AsyncDataBroker.DataChangeScope scope) {
- this.path = path;
- this.listener = listener;
- this.scope = scope;
- }
+ public RegisterChangeListener(InstanceIdentifier path,
+ ActorPath dataChangeListenerPath,
+ AsyncDataBroker.DataChangeScope scope) {
+ this.path = path;
+ this.dataChangeListenerPath = dataChangeListenerPath;
+ this.scope = scope;
+ }
- public InstanceIdentifier getPath() {
- return path;
- }
+ public InstanceIdentifier getPath() {
+ return path;
+ }
- public AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>> getListener() {
- return listener;
- }
- public AsyncDataBroker.DataChangeScope getScope() {
- return scope;
- }
+ public AsyncDataBroker.DataChangeScope getScope() {
+ return scope;
+ }
+
+ public ActorPath getDataChangeListenerPath() {
+ return dataChangeListenerPath;
+ }
}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.cluster.datastore.modification;
+
+
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+
+import java.io.Serializable;
+
+/**
+ * Base class to be used for all simple modifications that can be applied to a DOMStoreTransaction
+ */
+public abstract class AbstractModification implements Modification,
+ Serializable {
+
+ private static final long serialVersionUID = 1638042650152084457L;
+
+ protected final InstanceIdentifier path;
+
+ protected AbstractModification(InstanceIdentifier path) {
+ this.path = path;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.cluster.datastore.modification;
+
+import java.util.List;
+
+/**
+ * CompositeModification contains a list of modifications that need to be applied to the DOMStore
+ * <p>
+ * A CompositeModification gets stored in the transaction log for a Shard. During recovery when the transaction log
+ * is being replayed a DOMStoreWriteTransaction could be created and a CompositeModification could be applied to it.
+ * </p>
+ */
+public interface CompositeModification extends Modification {
+ /**
+ * Get a list of Modifications contained by this Composite
+ * @return
+ */
+ List<Modification> getModifications();
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.cluster.datastore.modification;
+
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreWriteTransaction;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+
+/**
+ * DeleteModification store all the parameters required to delete a path from the data tree
+ */
+public class DeleteModification extends AbstractModification {
+ public DeleteModification(InstanceIdentifier path) {
+ super(path);
+ }
+
+ @Override
+ public void apply(DOMStoreWriteTransaction transaction) {
+ transaction.delete(path);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.cluster.datastore.modification;
+
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreWriteTransaction;
+
+import java.util.List;
+
+public class ImmutableCompositeModification implements CompositeModification{
+
+ private final CompositeModification modification;
+
+ public ImmutableCompositeModification(CompositeModification modification){
+ this.modification = modification;
+ }
+
+ @Override
+ public List<Modification> getModifications() {
+ return modification.getModifications();
+ }
+
+ @Override
+ public void apply(DOMStoreWriteTransaction transaction) {
+ modification.apply(transaction);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.cluster.datastore.modification;
+
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreWriteTransaction;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+/**
+ * MergeModification stores all the parameters required to merge data into the specified path
+ */
+public class MergeModification extends AbstractModification{
+ private final NormalizedNode data;
+
+
+ public MergeModification(InstanceIdentifier path, NormalizedNode data) {
+ super(path);
+ this.data = data;
+ }
+
+ @Override
+ public void apply(DOMStoreWriteTransaction transaction) {
+ transaction.merge(path, data);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.cluster.datastore.modification;
+
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreWriteTransaction;
+
+/**
+ * Represents a modification to the data store.
+ * <p>
+ * Simple modifications can be of type,
+ * <li> {@link org.opendaylight.controller.cluster.datastore.modification.WriteModification}
+ * <li> {@link org.opendaylight.controller.cluster.datastore.modification.MergeModification}
+ * <li> {@link org.opendaylight.controller.cluster.datastore.modification.DeleteModification}
+ * </p>
+ *
+ * <p>
+ * Modifications can in turn be lumped into a single {@link org.opendaylight.controller.cluster.datastore.modification.CompositeModification}
+ * which can then be applied to a write transaction
+ * </p>
+ */
+public interface Modification {
+ /**
+ * Apply the modification to the specified transaction
+ * @param transaction
+ */
+ void apply(DOMStoreWriteTransaction transaction);
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.cluster.datastore.modification;
+
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreWriteTransaction;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * MutableCompositeModification is just a mutable version of a
+ * CompositeModification {@link org.opendaylight.controller.cluster.datastore.modification.MutableCompositeModification#addModification(Modification)}
+ */
+public class MutableCompositeModification
+ implements CompositeModification, Serializable {
+
+ private static final long serialVersionUID = 1163377899140186790L;
+
+ private final List<Modification> modifications = new ArrayList<>();
+
+ @Override
+ public void apply(DOMStoreWriteTransaction transaction) {
+ for (Modification modification : modifications) {
+ modification.apply(transaction);
+ }
+ }
+
+ /**
+ * Add a new Modification to the list of Modifications represented by this
+ * composite
+ *
+ * @param modification
+ */
+ public void addModification(Modification modification) {
+ modifications.add(modification);
+ }
+
+ public List<Modification> getModifications() {
+ return Collections.unmodifiableList(modifications);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.cluster.datastore.modification;
+
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreWriteTransaction;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+/**
+ * WriteModification stores all the parameters required to write data to the specified path
+ */
+public class WriteModification extends AbstractModification {
+
+ private final NormalizedNode data;
+
+ public WriteModification(InstanceIdentifier path, NormalizedNode data) {
+ super(path);
+ this.data = data;
+ }
+
+ @Override
+ public void apply(DOMStoreWriteTransaction transaction) {
+ transaction.write(path, data);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.cluster.datastore.utils;
+
+import akka.actor.ActorPath;
+import akka.actor.ActorRef;
+import akka.actor.ActorSelection;
+import akka.actor.ActorSystem;
+import akka.util.Timeout;
+import org.opendaylight.controller.cluster.datastore.messages.FindPrimary;
+import org.opendaylight.controller.cluster.datastore.messages.PrimaryFound;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import scala.concurrent.Await;
+import scala.concurrent.Future;
+import scala.concurrent.duration.Duration;
+import scala.concurrent.duration.FiniteDuration;
+
+import java.util.concurrent.TimeUnit;
+
+import static akka.pattern.Patterns.ask;
+
+/**
+ * The ActorContext class contains utility methods which could be used by
+ * non-actors (like DistributedDataStore) to work with actors a little more
+ * easily. An ActorContext can be freely passed around to local object instances
+ * but should not be passed to actors especially remote actors
+ */
+public class ActorContext {
+ private static final Logger
+ LOG = LoggerFactory.getLogger(ActorContext.class);
+
+ public static final FiniteDuration ASK_DURATION = Duration.create(5, TimeUnit.SECONDS);
+ public static final Duration AWAIT_DURATION = Duration.create(5, TimeUnit.SECONDS);
+
+ private final ActorSystem actorSystem;
+ private final ActorRef shardManager;
+
+ public ActorContext(ActorSystem actorSystem, ActorRef shardManager){
+ this.actorSystem = actorSystem;
+ this.shardManager = shardManager;
+ }
+
+ public ActorSystem getActorSystem() {
+ return actorSystem;
+ }
+
+ public ActorRef getShardManager() {
+ return shardManager;
+ }
+
+ public ActorSelection actorSelection(String actorPath){
+ return actorSystem.actorSelection(actorPath);
+ }
+
+ public ActorSelection actorSelection(ActorPath actorPath){
+ return actorSystem.actorSelection(actorPath);
+ }
+
+
+ /**
+ * Finds the primary for a given shard
+ *
+ * @param shardName
+ * @return
+ */
+ public ActorSelection findPrimary(String shardName) {
+ Object result = executeLocalOperation(shardManager,
+ new FindPrimary(shardName), ASK_DURATION);
+
+ if(result instanceof PrimaryFound){
+ PrimaryFound found = (PrimaryFound) result;
+
+ LOG.error("Primary found {}", found.getPrimaryPath());
+
+ return actorSystem.actorSelection(found.getPrimaryPath());
+ }
+ throw new RuntimeException("primary was not found");
+ }
+
+ /**
+ * Executes an operation on a local actor and wait for it's response
+ * @param actor
+ * @param message
+ * @param duration
+ * @return The response of the operation
+ */
+ public Object executeLocalOperation(ActorRef actor, Object message,
+ FiniteDuration duration){
+ Future<Object> future =
+ ask(actor, message, new Timeout(duration));
+
+ try {
+ return Await.result(future, AWAIT_DURATION);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Execute an operation on a remote actor and wait for it's response
+ * @param actor
+ * @param message
+ * @param duration
+ * @return
+ */
+ public Object executeRemoteOperation(ActorSelection actor, Object message,
+ FiniteDuration duration){
+ Future<Object> future =
+ ask(actor, message, new Timeout(duration));
+
+ try {
+ return Await.result(future, AWAIT_DURATION);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Execute an operation on the primary for a given shard
+ * <p>
+ * This method first finds the primary for a given shard ,then sends
+ * the message to the remote shard and waits for a response
+ * </p>
+ * @param shardName
+ * @param message
+ * @param duration
+ * @throws java.lang.RuntimeException when a primary is not found or if the message to the remote shard fails or times out
+ *
+ * @return
+ */
+ public Object executeShardOperation(String shardName, Object message, FiniteDuration duration){
+ ActorSelection primary = findPrimary(shardName);
+
+ return executeRemoteOperation(primary, message, duration);
+ }
+
+}
package org.opendaylight.controller.config.yang.config.distributed_datastore_provider;
+import akka.actor.ActorSystem;
import org.opendaylight.controller.cluster.datastore.DistributedDataStore;
public class DistributedDataStoreProviderModule extends org.opendaylight.controller.config.yang.config.distributed_datastore_provider.AbstractDistributedDataStoreProviderModule {
- public DistributedDataStoreProviderModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) {
- super(identifier, dependencyResolver);
+ public DistributedDataStoreProviderModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) {
+ super(identifier, dependencyResolver);
+ }
+
+ public DistributedDataStoreProviderModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver, org.opendaylight.controller.config.yang.config.distributed_datastore_provider.DistributedDataStoreProviderModule oldModule, java.lang.AutoCloseable oldInstance) {
+ super(identifier, dependencyResolver, oldModule, oldInstance);
+ }
+
+ @Override
+ public void customValidation() {
+ // add custom validation form module attributes here.
+ }
+
+ @Override
+ public java.lang.AutoCloseable createInstance() {
+ ActorSystem actorSystem = ActorSystem.create("opendaylight-cluster");
+ final DistributedDataStore configurationStore = new DistributedDataStore(actorSystem, "config");
+ final DistributedDataStore operationalStore = new DistributedDataStore(actorSystem, "operational");
+
+ final class AutoCloseableDistributedDataStore implements AutoCloseable {
+
+ @Override
+ public void close() throws Exception {
+ }
}
- public DistributedDataStoreProviderModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver, org.opendaylight.controller.config.yang.config.distributed_datastore_provider.DistributedDataStoreProviderModule oldModule, java.lang.AutoCloseable oldInstance) {
- super(identifier, dependencyResolver, oldModule, oldInstance);
- }
-
- @Override
- public void customValidation() {
- // add custom validation form module attributes here.
- }
-
- @Override
- public java.lang.AutoCloseable createInstance() {
- new DistributedDataStore();
-
- final class AutoCloseableDistributedDataStore implements AutoCloseable {
-
- @Override
- public void close() throws Exception {
-
- }
- }
-
- return new AutoCloseableDistributedDataStore();
- }
+ return new AutoCloseableDistributedDataStore();
+ }
}
private static ActorSystem system;
@BeforeClass
- public static void setUp(){
+ public static void setUpClass(){
system = ActorSystem.create("test");
}
@AfterClass
- public static void tearDown(){
+ public static void tearDownClass(){
JavaTestKit.shutdownActorSystem(system);
system = null;
}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.cluster.datastore;
+
+import akka.actor.ActorPath;
+import akka.actor.ActorRef;
+import akka.actor.ActorSelection;
+import akka.actor.Props;
+import akka.testkit.JavaTestKit;
+import junit.framework.Assert;
+import org.junit.Test;
+import org.opendaylight.controller.cluster.datastore.messages.CommitTransaction;
+import org.opendaylight.controller.cluster.datastore.messages.CommitTransactionReply;
+import org.opendaylight.controller.cluster.datastore.messages.CreateTransaction;
+import org.opendaylight.controller.cluster.datastore.messages.CreateTransactionChain;
+import org.opendaylight.controller.cluster.datastore.messages.CreateTransactionChainReply;
+import org.opendaylight.controller.cluster.datastore.messages.CreateTransactionReply;
+import org.opendaylight.controller.cluster.datastore.messages.PreCommitTransaction;
+import org.opendaylight.controller.cluster.datastore.messages.PreCommitTransactionReply;
+import org.opendaylight.controller.cluster.datastore.messages.ReadyTransaction;
+import org.opendaylight.controller.cluster.datastore.messages.ReadyTransactionReply;
+import org.opendaylight.controller.cluster.datastore.messages.UpdateSchemaContext;
+import org.opendaylight.controller.cluster.datastore.messages.WriteData;
+import org.opendaylight.controller.cluster.datastore.messages.WriteDataReply;
+import org.opendaylight.controller.md.cluster.datastore.model.TestModel;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+
+public class BasicIntegrationTest extends AbstractActorTest {
+
+ @Test
+ public void integrationTest() {
+ // This test will
+ // - create a Shard
+ // - initiate a transaction
+ // - write something
+ // - read the transaction for commit
+ // - commit the transaction
+
+
+ new JavaTestKit(getSystem()) {{
+ final Props props = Shard.props("config");
+ final ActorRef shard = getSystem().actorOf(props);
+
+ new Within(duration("5 seconds")) {
+ protected void run() {
+
+ shard.tell(
+ new UpdateSchemaContext(TestModel.createTestContext()),
+ getRef());
+
+ shard.tell(new CreateTransactionChain(), getRef());
+
+ final ActorSelection transactionChain =
+ new ExpectMsg<ActorSelection>("match hint") {
+ protected ActorSelection match(Object in) {
+ if (in instanceof CreateTransactionChainReply) {
+ ActorPath transactionChainPath =
+ ((CreateTransactionChainReply) in)
+ .getTransactionChainPath();
+ return getSystem()
+ .actorSelection(transactionChainPath);
+ } else {
+ throw noMatch();
+ }
+ }
+ }.get(); // this extracts the received message
+
+ Assert.assertNotNull(transactionChain);
+
+ transactionChain.tell(new CreateTransaction(), getRef());
+
+ final ActorSelection transaction =
+ new ExpectMsg<ActorSelection>("match hint") {
+ protected ActorSelection match(Object in) {
+ if (in instanceof CreateTransactionReply) {
+ ActorPath transactionPath =
+ ((CreateTransactionReply) in)
+ .getTransactionPath();
+ return getSystem()
+ .actorSelection(transactionPath);
+ } else {
+ throw noMatch();
+ }
+ }
+ }.get(); // this extracts the received message
+
+ Assert.assertNotNull(transaction);
+
+ transaction.tell(new WriteData(TestModel.TEST_PATH,
+ ImmutableNodes.containerNode(TestModel.TEST_QNAME)),
+ getRef());
+
+ Boolean writeDone = new ExpectMsg<Boolean>("match hint") {
+ protected Boolean match(Object in) {
+ if (in instanceof WriteDataReply) {
+ return true;
+ } else {
+ throw noMatch();
+ }
+ }
+ }.get(); // this extracts the received message
+
+ Assert.assertTrue(writeDone);
+
+ transaction.tell(new ReadyTransaction(), getRef());
+
+ final ActorSelection cohort =
+ new ExpectMsg<ActorSelection>("match hint") {
+ protected ActorSelection match(Object in) {
+ if (in instanceof ReadyTransactionReply) {
+ ActorPath cohortPath =
+ ((ReadyTransactionReply) in)
+ .getCohortPath();
+ return getSystem()
+ .actorSelection(cohortPath);
+ } else {
+ throw noMatch();
+ }
+ }
+ }.get(); // this extracts the received message
+
+ Assert.assertNotNull(cohort);
+
+ cohort.tell(new PreCommitTransaction(), getRef());
+
+ Boolean preCommitDone =
+ new ExpectMsg<Boolean>("match hint") {
+ protected Boolean match(Object in) {
+ if (in instanceof PreCommitTransactionReply) {
+ return true;
+ } else {
+ throw noMatch();
+ }
+ }
+ }.get(); // this extracts the received message
+
+ Assert.assertTrue(preCommitDone);
+
+ cohort.tell(new CommitTransaction(), getRef());
+
+ final Boolean commitDone =
+ new ExpectMsg<Boolean>("match hint") {
+ protected Boolean match(Object in) {
+ if (in instanceof CommitTransactionReply) {
+ return true;
+ } else {
+ throw noMatch();
+ }
+ }
+ }.get(); // this extracts the received message
+
+ Assert.assertTrue(commitDone);
+
+ }
+
+
+ };
+ }};
+
+
+ }
+}
package org.opendaylight.controller.cluster.datastore;
+import akka.actor.ActorRef;
+import akka.actor.Props;
import junit.framework.Assert;
+import org.opendaylight.controller.cluster.datastore.messages.CreateTransactionReply;
+import org.opendaylight.controller.cluster.datastore.messages.RegisterChangeListenerReply;
+import org.opendaylight.controller.cluster.datastore.utils.DoNothingActor;
+import org.opendaylight.controller.cluster.datastore.utils.MockActorContext;
+import org.opendaylight.controller.md.cluster.datastore.model.TestModel;
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.AsyncDataChangeListener;
import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-public class DistributedDataStoreTest {
+public class DistributedDataStoreTest extends AbstractActorTest{
private DistributedDataStore distributedDataStore;
+ private MockActorContext mockActorContext;
+ private ActorRef doNothingActorRef;
@org.junit.Before
public void setUp() throws Exception {
- distributedDataStore = new DistributedDataStore();
+ final Props props = Props.create(DoNothingActor.class);
+
+ doNothingActorRef = getSystem().actorOf(props);
+
+ mockActorContext = new MockActorContext(getSystem(), doNothingActorRef);
+ distributedDataStore = new DistributedDataStore(mockActorContext, "config");
+ distributedDataStore.onGlobalContextUpdated(
+ TestModel.createTestContext());
+
+ // Make CreateTransactionReply as the default response. Will need to be
+ // tuned if a specific test requires some other response
+ mockActorContext.setExecuteShardOperationResponse(
+ new CreateTransactionReply(doNothingActorRef.path()));
}
@org.junit.After
@org.junit.Test
public void testRegisterChangeListener() throws Exception {
+ mockActorContext.setExecuteShardOperationResponse(new RegisterChangeListenerReply(doNothingActorRef.path()));
ListenerRegistration registration =
- distributedDataStore.registerChangeListener(InstanceIdentifier.builder().build(), new AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>>() {
+ distributedDataStore.registerChangeListener(TestModel.TEST_PATH, new AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>>() {
@Override
public void onDataChanged(AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> change) {
throw new UnsupportedOperationException("onDataChanged");
import org.junit.BeforeClass;
import org.junit.Test;
import org.opendaylight.controller.cluster.datastore.messages.FindPrimary;
+import org.opendaylight.controller.cluster.datastore.messages.PrimaryFound;
import org.opendaylight.controller.cluster.datastore.messages.PrimaryNotFound;
import scala.concurrent.duration.Duration;
}
@Test
- public void testOnReceiveFindPrimary() throws Exception {
+ public void testOnReceiveFindPrimaryForNonExistentShard() throws Exception {
new JavaTestKit(system) {{
- final Props props = Props.create(ShardManager.class);
- final TestActorRef<ShardManager> subject = TestActorRef.create(system, props, "test");
+ final Props props = ShardManager.props("config");
+ final TestActorRef<ShardManager> subject = TestActorRef.create(system, props);
- // can also use JavaTestKit “from the outside”
- final JavaTestKit probe = new JavaTestKit(system);
-
- // the run() method needs to finish within 3 seconds
- new Within(duration("3 seconds")) {
+ new Within(duration("1 seconds")) {
protected void run() {
subject.tell(new FindPrimary("inventory"), getRef());
};
}};
}
+
+ @Test
+ public void testOnReceiveFindPrimaryForExistentShard() throws Exception {
+
+ new JavaTestKit(system) {{
+ final Props props = ShardManager.props("config");
+ final TestActorRef<ShardManager> subject = TestActorRef.create(system, props);
+
+ // the run() method needs to finish within 3 seconds
+ new Within(duration("1 seconds")) {
+ protected void run() {
+
+ subject.tell(new FindPrimary(Shard.DEFAULT_NAME), getRef());
+
+ expectMsgClass(PrimaryFound.class);
+
+ expectNoMsg();
+ }
+ };
+ }};
+ }
}
\ No newline at end of file
@Test
public void testOnReceiveCreateTransactionChain() throws Exception {
new JavaTestKit(getSystem()) {{
- final Props props = Props.create(Shard.class);
+ final Props props = Shard.props("config");
final ActorRef subject = getSystem().actorOf(props, "testCreateTransactionChain");
new Within(duration("1 seconds")) {
@Test
public void testOnReceiveRegisterListener() throws Exception {
new JavaTestKit(getSystem()) {{
- final Props props = Props.create(Shard.class);
+ final Props props = Shard.props("config");
final ActorRef subject = getSystem().actorOf(props, "testRegisterChangeListener");
new Within(duration("1 seconds")) {
subject.tell(new UpdateSchemaContext(TestModel.createTestContext()), getRef());
- subject.tell(new RegisterChangeListener(InstanceIdentifier.builder().build(), noOpDataChangeListener() , AsyncDataBroker.DataChangeScope.BASE), getRef());
+ subject.tell(new RegisterChangeListener(TestModel.TEST_PATH, getRef().path() , AsyncDataBroker.DataChangeScope.BASE), getRef());
final String out = new ExpectMsg<String>("match hint") {
// do not put code outside this method, will run afterwards
}};
}
+
+
private AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>> noOpDataChangeListener(){
return new AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>>() {
@Override
}
};
}
-}
\ No newline at end of file
+}
import org.opendaylight.controller.cluster.datastore.messages.ReadyTransactionReply;
import org.opendaylight.controller.cluster.datastore.messages.WriteData;
import org.opendaylight.controller.cluster.datastore.messages.WriteDataReply;
+import org.opendaylight.controller.cluster.datastore.modification.CompositeModification;
+import org.opendaylight.controller.cluster.datastore.modification.DeleteModification;
+import org.opendaylight.controller.cluster.datastore.modification.MergeModification;
+import org.opendaylight.controller.cluster.datastore.modification.Modification;
+import org.opendaylight.controller.cluster.datastore.modification.WriteModification;
import org.opendaylight.controller.md.cluster.datastore.model.TestModel;
import org.opendaylight.controller.md.sal.dom.store.impl.InMemoryDOMDataStore;
import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
public class ShardTransactionTest extends AbstractActorTest {
private static ListeningExecutorService storeExecutor = MoreExecutors.listeningDecorator(MoreExecutors.sameThreadExecutor());
@Test
public void testOnReceiveReadData() throws Exception {
new JavaTestKit(getSystem()) {{
- final Props props = ShardTransaction.props(store.newReadWriteTransaction());
+ final ActorRef shard = getSystem().actorOf(Shard.props("config"));
+ final Props props = ShardTransaction.props(store.newReadWriteTransaction(), shard);
final ActorRef subject = getSystem().actorOf(props, "testReadData");
new Within(duration("1 seconds")) {
}};
}
+ private void assertModification(final ActorRef subject, final Class<? extends Modification> modificationType){
+ new JavaTestKit(getSystem()) {{
+ new Within(duration("1 seconds")) {
+ protected void run() {
+ subject.tell(new ShardTransaction.GetCompositedModification(), getRef());
+
+ final CompositeModification compositeModification = new ExpectMsg<CompositeModification>("match hint") {
+ // do not put code outside this method, will run afterwards
+ protected CompositeModification match(Object in) {
+ if (in instanceof ShardTransaction.GetCompositeModificationReply) {
+ return ((ShardTransaction.GetCompositeModificationReply) in).getModification();
+ } else {
+ throw noMatch();
+ }
+ }
+ }.get(); // this extracts the received message
+
+ assertTrue(compositeModification.getModifications().size() == 1);
+ assertEquals(modificationType, compositeModification.getModifications().get(0).getClass());
+
+ }
+ };
+ }};
+ }
+
@Test
public void testOnReceiveWriteData() throws Exception {
new JavaTestKit(getSystem()) {{
- final Props props = ShardTransaction.props(store.newReadWriteTransaction());
+ final ActorRef shard = getSystem().actorOf(Shard.props("config"));
+ final Props props = ShardTransaction.props(store.newReadWriteTransaction(), shard);
final ActorRef subject = getSystem().actorOf(props, "testWriteData");
new Within(duration("1 seconds")) {
assertEquals("match", out);
+ assertModification(subject, WriteModification.class);
expectNoMsg();
}
@Test
public void testOnReceiveMergeData() throws Exception {
new JavaTestKit(getSystem()) {{
- final Props props = ShardTransaction.props(store.newReadWriteTransaction());
+ final ActorRef shard = getSystem().actorOf(Shard.props("config"));
+ final Props props = ShardTransaction.props(store.newReadWriteTransaction(), shard);
final ActorRef subject = getSystem().actorOf(props, "testMergeData");
new Within(duration("1 seconds")) {
assertEquals("match", out);
+ assertModification(subject, MergeModification.class);
+
expectNoMsg();
}
@Test
public void testOnReceiveDeleteData() throws Exception {
new JavaTestKit(getSystem()) {{
- final Props props = ShardTransaction.props(store.newReadWriteTransaction());
+ final ActorRef shard = getSystem().actorOf(Shard.props("config"));
+ final Props props = ShardTransaction.props(store.newReadWriteTransaction(), shard);
final ActorRef subject = getSystem().actorOf(props, "testDeleteData");
new Within(duration("1 seconds")) {
assertEquals("match", out);
+ assertModification(subject, DeleteModification.class);
expectNoMsg();
}
@Test
public void testOnReceiveReadyTransaction() throws Exception {
new JavaTestKit(getSystem()) {{
- final Props props = ShardTransaction.props(store.newReadWriteTransaction());
+ final ActorRef shard = getSystem().actorOf(Shard.props("config"));
+ final Props props = ShardTransaction.props(store.newReadWriteTransaction(), shard);
final ActorRef subject = getSystem().actorOf(props, "testReadyTransaction");
new Within(duration("1 seconds")) {
@Test
public void testOnReceiveCloseTransaction() throws Exception {
new JavaTestKit(getSystem()) {{
- final Props props = ShardTransaction.props(store.newReadWriteTransaction());
+ final ActorRef shard = getSystem().actorOf(Shard.props("config"));
+ final Props props = ShardTransaction.props(store.newReadWriteTransaction(), shard);
final ActorRef subject = getSystem().actorOf(props, "testCloseTransaction");
new Within(duration("1 seconds")) {
--- /dev/null
+package org.opendaylight.controller.cluster.datastore;
+
+import akka.actor.ActorRef;
+import akka.actor.Props;
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.ListenableFuture;
+import junit.framework.Assert;
+import org.junit.Test;
+import org.opendaylight.controller.cluster.datastore.messages.CloseTransaction;
+import org.opendaylight.controller.cluster.datastore.messages.CreateTransactionReply;
+import org.opendaylight.controller.cluster.datastore.messages.DeleteData;
+import org.opendaylight.controller.cluster.datastore.messages.MergeData;
+import org.opendaylight.controller.cluster.datastore.messages.ReadDataReply;
+import org.opendaylight.controller.cluster.datastore.messages.ReadyTransactionReply;
+import org.opendaylight.controller.cluster.datastore.messages.WriteData;
+import org.opendaylight.controller.cluster.datastore.utils.ActorContext;
+import org.opendaylight.controller.cluster.datastore.utils.DoNothingActor;
+import org.opendaylight.controller.cluster.datastore.utils.MessageCollectorActor;
+import org.opendaylight.controller.cluster.datastore.utils.MockActorContext;
+import org.opendaylight.controller.md.cluster.datastore.model.TestModel;
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreThreePhaseCommitCohort;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+
+import java.util.List;
+
+public class TransactionProxyTest extends AbstractActorTest {
+
+ @Test
+ public void testRead() throws Exception {
+ final Props props = Props.create(DoNothingActor.class);
+ final ActorRef actorRef = getSystem().actorOf(props);
+
+ final MockActorContext actorContext = new MockActorContext(this.getSystem());
+ actorContext.setExecuteShardOperationResponse(new CreateTransactionReply(actorRef.path()));
+ actorContext.setExecuteRemoteOperationResponse("message");
+
+ TransactionProxy transactionProxy =
+ new TransactionProxy(actorContext,
+ TransactionProxy.TransactionType.READ_ONLY);
+
+
+ ListenableFuture<Optional<NormalizedNode<?, ?>>> read =
+ transactionProxy.read(TestModel.TEST_PATH);
+
+ Optional<NormalizedNode<?, ?>> normalizedNodeOptional = read.get();
+
+ Assert.assertFalse(normalizedNodeOptional.isPresent());
+
+ actorContext.setExecuteRemoteOperationResponse(new ReadDataReply(
+ ImmutableNodes.containerNode(TestModel.TEST_QNAME)));
+
+ read = transactionProxy.read(TestModel.TEST_PATH);
+
+ normalizedNodeOptional = read.get();
+
+ Assert.assertTrue(normalizedNodeOptional.isPresent());
+ }
+
+ @Test
+ public void testWrite() throws Exception {
+ final Props props = Props.create(MessageCollectorActor.class);
+ final ActorRef actorRef = getSystem().actorOf(props);
+
+ final MockActorContext actorContext = new MockActorContext(this.getSystem());
+ actorContext.setExecuteShardOperationResponse(new CreateTransactionReply(actorRef.path()));
+ actorContext.setExecuteRemoteOperationResponse("message");
+
+ TransactionProxy transactionProxy =
+ new TransactionProxy(actorContext,
+ TransactionProxy.TransactionType.READ_ONLY);
+
+ transactionProxy.write(TestModel.TEST_PATH,
+ ImmutableNodes.containerNode(TestModel.NAME_QNAME));
+
+ ActorContext testContext = new ActorContext(getSystem(), getSystem().actorOf(Props.create(DoNothingActor.class)));
+ Object messages = testContext
+ .executeLocalOperation(actorRef, "messages",
+ ActorContext.ASK_DURATION);
+
+ Assert.assertNotNull(messages);
+
+ Assert.assertTrue(messages instanceof List);
+
+ List<Object> listMessages = (List<Object>) messages;
+
+ Assert.assertEquals(1, listMessages.size());
+
+ Assert.assertTrue(listMessages.get(0) instanceof WriteData);
+ }
+
+ @Test
+ public void testMerge() throws Exception {
+ final Props props = Props.create(MessageCollectorActor.class);
+ final ActorRef actorRef = getSystem().actorOf(props);
+
+ final MockActorContext actorContext = new MockActorContext(this.getSystem());
+ actorContext.setExecuteShardOperationResponse(new CreateTransactionReply(actorRef.path()));
+ actorContext.setExecuteRemoteOperationResponse("message");
+
+ TransactionProxy transactionProxy =
+ new TransactionProxy(actorContext,
+ TransactionProxy.TransactionType.READ_ONLY);
+
+ transactionProxy.merge(TestModel.TEST_PATH,
+ ImmutableNodes.containerNode(TestModel.NAME_QNAME));
+
+ ActorContext testContext = new ActorContext(getSystem(), getSystem().actorOf(Props.create(DoNothingActor.class)));
+ Object messages = testContext
+ .executeLocalOperation(actorRef, "messages",
+ ActorContext.ASK_DURATION);
+
+ Assert.assertNotNull(messages);
+
+ Assert.assertTrue(messages instanceof List);
+
+ List<Object> listMessages = (List<Object>) messages;
+
+ Assert.assertEquals(1, listMessages.size());
+
+ Assert.assertTrue(listMessages.get(0) instanceof MergeData);
+ }
+
+ @Test
+ public void testDelete() throws Exception {
+ final Props props = Props.create(MessageCollectorActor.class);
+ final ActorRef actorRef = getSystem().actorOf(props);
+
+ final MockActorContext actorContext = new MockActorContext(this.getSystem());
+ actorContext.setExecuteShardOperationResponse(new CreateTransactionReply(actorRef.path()));
+ actorContext.setExecuteRemoteOperationResponse("message");
+
+ TransactionProxy transactionProxy =
+ new TransactionProxy(actorContext,
+ TransactionProxy.TransactionType.READ_ONLY);
+
+ transactionProxy.delete(TestModel.TEST_PATH);
+
+ ActorContext testContext = new ActorContext(getSystem(), getSystem().actorOf(Props.create(DoNothingActor.class)));
+ Object messages = testContext
+ .executeLocalOperation(actorRef, "messages",
+ ActorContext.ASK_DURATION);
+
+ Assert.assertNotNull(messages);
+
+ Assert.assertTrue(messages instanceof List);
+
+ List<Object> listMessages = (List<Object>) messages;
+
+ Assert.assertEquals(1, listMessages.size());
+
+ Assert.assertTrue(listMessages.get(0) instanceof DeleteData);
+ }
+
+ @Test
+ public void testReady() throws Exception {
+ final Props props = Props.create(DoNothingActor.class);
+ final ActorRef doNothingActorRef = getSystem().actorOf(props);
+
+ final MockActorContext actorContext = new MockActorContext(this.getSystem());
+ actorContext.setExecuteShardOperationResponse(new CreateTransactionReply(doNothingActorRef.path()));
+ actorContext.setExecuteRemoteOperationResponse(new ReadyTransactionReply(doNothingActorRef.path()));
+
+ TransactionProxy transactionProxy =
+ new TransactionProxy(actorContext,
+ TransactionProxy.TransactionType.READ_ONLY);
+
+
+ DOMStoreThreePhaseCommitCohort ready = transactionProxy.ready();
+
+ Assert.assertTrue(ready instanceof ThreePhaseCommitCohortProxy);
+
+ ThreePhaseCommitCohortProxy proxy = (ThreePhaseCommitCohortProxy) ready;
+
+ Assert.assertTrue("No cohort paths returned", proxy.getCohortPaths().size() > 0);
+
+ }
+
+ @Test
+ public void testGetIdentifier(){
+ final Props props = Props.create(DoNothingActor.class);
+ final ActorRef doNothingActorRef = getSystem().actorOf(props);
+
+ final MockActorContext actorContext = new MockActorContext(this.getSystem());
+ actorContext.setExecuteShardOperationResponse(
+ new CreateTransactionReply(doNothingActorRef.path()));
+
+ TransactionProxy transactionProxy =
+ new TransactionProxy(actorContext,
+ TransactionProxy.TransactionType.READ_ONLY);
+
+ Assert.assertNotNull(transactionProxy.getIdentifier());
+ }
+
+ @Test
+ public void testClose(){
+ final Props props = Props.create(MessageCollectorActor.class);
+ final ActorRef actorRef = getSystem().actorOf(props);
+
+ final MockActorContext actorContext = new MockActorContext(this.getSystem());
+ actorContext.setExecuteShardOperationResponse(new CreateTransactionReply(actorRef.path()));
+ actorContext.setExecuteRemoteOperationResponse("message");
+
+ TransactionProxy transactionProxy =
+ new TransactionProxy(actorContext,
+ TransactionProxy.TransactionType.READ_ONLY);
+
+ transactionProxy.close();
+
+ ActorContext testContext = new ActorContext(getSystem(), getSystem().actorOf(Props.create(DoNothingActor.class)));
+ Object messages = testContext
+ .executeLocalOperation(actorRef, "messages",
+ ActorContext.ASK_DURATION);
+
+ Assert.assertNotNull(messages);
+
+ Assert.assertTrue(messages instanceof List);
+
+ List<Object> listMessages = (List<Object>) messages;
+
+ Assert.assertEquals(1, listMessages.size());
+
+ Assert.assertTrue(listMessages.get(0) instanceof CloseTransaction);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.cluster.datastore.modification;
+
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+import org.junit.Before;
+import org.opendaylight.controller.md.cluster.datastore.model.TestModel;
+import org.opendaylight.controller.md.sal.dom.store.impl.InMemoryDOMDataStore;
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadTransaction;
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreThreePhaseCommitCohort;
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreWriteTransaction;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+public abstract class AbstractModificationTest {
+
+ protected InMemoryDOMDataStore store;
+
+ @Before
+ public void setUp(){
+ store = new InMemoryDOMDataStore("test", MoreExecutors.sameThreadExecutor());
+ store.onGlobalContextUpdated(TestModel.createTestContext());
+ }
+
+ protected void commitTransaction(DOMStoreWriteTransaction transaction){
+ DOMStoreThreePhaseCommitCohort cohort = transaction.ready();
+ cohort.preCommit();
+ cohort.commit();
+ }
+
+ protected Optional<NormalizedNode<?,?>> readData(InstanceIdentifier path) throws Exception{
+ DOMStoreReadTransaction transaction = store.newReadOnlyTransaction();
+ ListenableFuture<Optional<NormalizedNode<?, ?>>> future = transaction.read(path);
+ return future.get();
+ }
+}
--- /dev/null
+package org.opendaylight.controller.cluster.datastore.modification;
+
+import com.google.common.base.Optional;
+import org.junit.Assert;
+import org.junit.Test;
+import org.opendaylight.controller.md.cluster.datastore.model.TestModel;
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadWriteTransaction;
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreWriteTransaction;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+
+public class DeleteModificationTest extends AbstractModificationTest{
+
+ @Test
+ public void testApply() throws Exception {
+ //Write something into the datastore
+ DOMStoreReadWriteTransaction writeTransaction = store.newReadWriteTransaction();
+ WriteModification writeModification = new WriteModification(TestModel.TEST_PATH, ImmutableNodes.containerNode(TestModel.TEST_QNAME));
+ writeModification.apply(writeTransaction);
+ commitTransaction(writeTransaction);
+
+ //Check if it's in the datastore
+ Optional<NormalizedNode<?,?>> data = readData(TestModel.TEST_PATH);
+ Assert.assertTrue(data.isPresent());
+
+ //Delete stuff from the datastore
+ DOMStoreWriteTransaction deleteTransaction = store.newWriteOnlyTransaction();
+ DeleteModification deleteModification = new DeleteModification(TestModel.TEST_PATH);
+ deleteModification.apply(deleteTransaction);
+ commitTransaction(deleteTransaction);
+
+ data = readData(TestModel.TEST_PATH);
+ Assert.assertFalse(data.isPresent());
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.opendaylight.controller.cluster.datastore.modification;
+
+import com.google.common.base.Optional;
+import org.junit.Assert;
+import org.junit.Test;
+import org.opendaylight.controller.md.cluster.datastore.model.TestModel;
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadWriteTransaction;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+
+public class MergeModificationTest extends AbstractModificationTest{
+
+ @Test
+ public void testApply() throws Exception {
+ //TODO : Need to write a better test for this
+
+ //Write something into the datastore
+ DOMStoreReadWriteTransaction writeTransaction = store.newReadWriteTransaction();
+ MergeModification writeModification = new MergeModification(TestModel.TEST_PATH, ImmutableNodes.containerNode(TestModel.TEST_QNAME));
+ writeModification.apply(writeTransaction);
+ commitTransaction(writeTransaction);
+
+ //Check if it's in the datastore
+ Optional<NormalizedNode<?,?>> data = readData(TestModel.TEST_PATH);
+ Assert.assertTrue(data.isPresent());
+
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.opendaylight.controller.cluster.datastore.modification;
+
+import com.google.common.base.Optional;
+import junit.framework.Assert;
+import org.junit.Test;
+import org.opendaylight.controller.md.cluster.datastore.model.TestModel;
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadWriteTransaction;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+
+public class MutableCompositeModificationTest extends AbstractModificationTest {
+
+ @Test
+ public void testApply() throws Exception {
+
+ MutableCompositeModification compositeModification = new MutableCompositeModification();
+ compositeModification.addModification(new WriteModification(TestModel.TEST_PATH, ImmutableNodes.containerNode(TestModel.TEST_QNAME)));
+
+ DOMStoreReadWriteTransaction transaction = store.newReadWriteTransaction();
+ compositeModification.apply(transaction);
+ commitTransaction(transaction);
+
+ Optional<NormalizedNode<?,?>> data = readData(TestModel.TEST_PATH);
+
+ Assert.assertNotNull(data.get());
+ Assert.assertEquals(TestModel.TEST_QNAME, data.get().getNodeType());
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.opendaylight.controller.cluster.datastore.modification;
+
+import com.google.common.base.Optional;
+import org.junit.Assert;
+import org.junit.Test;
+import org.opendaylight.controller.md.cluster.datastore.model.TestModel;
+import org.opendaylight.controller.sal.core.spi.data.DOMStoreReadWriteTransaction;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+
+public class WriteModificationTest extends AbstractModificationTest{
+
+ @Test
+ public void testApply() throws Exception {
+ //Write something into the datastore
+ DOMStoreReadWriteTransaction writeTransaction = store.newReadWriteTransaction();
+ WriteModification writeModification = new WriteModification(TestModel.TEST_PATH, ImmutableNodes.containerNode(TestModel.TEST_QNAME));
+ writeModification.apply(writeTransaction);
+ commitTransaction(writeTransaction);
+
+ //Check if it's in the datastore
+ Optional<NormalizedNode<?,?>> data = readData(TestModel.TEST_PATH);
+ Assert.assertTrue(data.isPresent());
+
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.cluster.datastore.utils;
+
+import akka.actor.UntypedActor;
+
+public class DoNothingActor extends UntypedActor {
+
+ @Override public void onReceive(Object message) throws Exception {
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.cluster.datastore.utils;
+
+import akka.actor.UntypedActor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * MessageCollectorActor collects messages as it receives them. It can send
+ * those collected messages to any sender which sends it the "messages" message
+ * <p>
+ * This class would be useful as a mock to test whether messages were sent
+ * to a remote actor or not.
+ * </p>
+ */
+public class MessageCollectorActor extends UntypedActor {
+ private List<Object> messages = new ArrayList<>();
+
+ @Override public void onReceive(Object message) throws Exception {
+ if(message instanceof String){
+ if("messages".equals(message)){
+ getSender().tell(messages, getSelf());
+ }
+ } else {
+ messages.add(message);
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, 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.controller.cluster.datastore.utils;
+
+
+import akka.actor.ActorRef;
+import akka.actor.ActorSelection;
+import akka.actor.ActorSystem;
+import scala.concurrent.duration.FiniteDuration;
+
+public class MockActorContext extends ActorContext {
+
+ private Object executeShardOperationResponse;
+ private Object executeRemoteOperationResponse;
+ private Object executeLocalOperationResponse;
+
+ public MockActorContext(ActorSystem actorSystem) {
+ super(actorSystem, null);
+ }
+
+ public MockActorContext(ActorSystem actorSystem, ActorRef shardManager) {
+ super(actorSystem, shardManager);
+ }
+
+
+ @Override public Object executeShardOperation(String shardName,
+ Object message, FiniteDuration duration) {
+ return executeShardOperationResponse;
+ }
+
+ @Override public Object executeRemoteOperation(ActorSelection actor,
+ Object message, FiniteDuration duration) {
+ return executeRemoteOperationResponse;
+ }
+
+ @Override public ActorSelection findPrimary(String shardName) {
+ return null;
+ }
+
+ public void setExecuteShardOperationResponse(Object response){
+ executeShardOperationResponse = response;
+ }
+
+ public void setExecuteRemoteOperationResponse(Object response){
+ executeRemoteOperationResponse = response;
+ }
+
+ public void setExecuteLocalOperationResponse(
+ Object executeLocalOperationResponse) {
+ this.executeLocalOperationResponse = executeLocalOperationResponse;
+ }
+
+
+}
--- /dev/null
+akka {
+ actor {
+ serializers {
+ java = "akka.serialization.JavaSerializer"
+ }
+
+ serialization-bindings {
+ "org.opendaylight.controller.cluster.datastore.modification.MutableCompositeModification" = java
+ }
+ }
+}
\ No newline at end of file
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-common-util</artifactId>
+ </dependency>
<dependency>
<groupId>org.opendaylight.controller</groupId>
<artifactId>sal-remote</artifactId>
<artifactId>mockito-all</artifactId>
<scope>test</scope>
</dependency>
- <dependency>
- <groupId>org.opendaylight.controller</groupId>
- <artifactId>sal-common-util</artifactId>
- <scope>test</scope>
- </dependency>
</dependencies>
<build>
*/
package org.opendaylight.controller.sal.restconf.impl;
+import com.google.common.util.concurrent.Futures;
+import java.util.Collections;
import java.util.concurrent.Future;
-
import javax.ws.rs.core.Response.Status;
-
import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
import org.opendaylight.controller.md.sal.common.api.data.DataReader;
+import org.opendaylight.controller.sal.common.util.Rpcs;
import org.opendaylight.controller.sal.core.api.Broker.ConsumerSession;
import org.opendaylight.controller.sal.core.api.data.DataBrokerService;
import org.opendaylight.controller.sal.core.api.data.DataChangeListener;
import org.opendaylight.controller.sal.streams.listeners.ListenerAdapter;
import org.opendaylight.yangtools.concepts.ListenerRegistration;
import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.RpcError;
import org.opendaylight.yangtools.yang.common.RpcResult;
import org.opendaylight.yangtools.yang.data.api.CompositeNode;
import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
public Future<RpcResult<TransactionStatus>> commitConfigurationDataDelete( final InstanceIdentifier path ) {
this.checkPreconditions();
-
- final DataModificationTransaction transaction = dataService.beginTransaction();
- LOG.info( "Delete Configuration via Restconf: {}", path );
- transaction.removeConfigurationData( path );
- return transaction.commit();
+ return deleteDataAtTarget(path,dataService.beginTransaction());
}
public Future<RpcResult<TransactionStatus>> commitConfigurationDataDeleteBehindMountPoint(
final MountInstance mountPoint, final InstanceIdentifier path ) {
this.checkPreconditions();
+ return deleteDataAtTarget(path,mountPoint.beginTransaction());
+ }
- final DataModificationTransaction transaction = mountPoint.beginTransaction();
- LOG.info( "Delete Configuration via Restconf: {}", path );
- transaction.removeConfigurationData( path );
+ private Future<RpcResult<TransactionStatus>> deleteDataAtTarget(final InstanceIdentifier path,
+ final DataModificationTransaction transaction) {
+ LOG.info("Delete Configuration via Restconf: {}", path);
+ CompositeNode redDataAtPath = transaction.readConfigurationData(path);
+ if (redDataAtPath == null) {
+ return Futures.immediateFuture(Rpcs.<TransactionStatus> getRpcResult(true, TransactionStatus.COMMITED,
+ Collections.<RpcError> emptyList()));
+ }
+ transaction.removeConfigurationData(path);
return transaction.commit();
}
*/
package org.opendaylight.controller.sal.restconf.impl.test;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
+import static org.mockito.Matchers.any;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.util.concurrent.Futures;
import java.util.Map;
import java.util.concurrent.Future;
-
import org.junit.Before;
import org.junit.Test;
import org.mockito.InOrder;
import org.opendaylight.yangtools.yang.common.RpcResult;
import org.opendaylight.yangtools.yang.data.api.CompositeNode;
import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.util.concurrent.Futures;
+import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode;
/**
* Unit tests for BrokerFacade.
Future<RpcResult<TransactionStatus>> expFuture = Futures.immediateFuture( null );
when( dataBroker.beginTransaction() ).thenReturn( mockTransaction );
+ when(mockTransaction.readConfigurationData(any(InstanceIdentifier.class))).thenReturn(
+ ImmutableCompositeNode.builder().toInstance());
mockTransaction.removeConfigurationData( instanceID );
when( mockTransaction.commit() ).thenReturn( expFuture );
Future<RpcResult<TransactionStatus>> expFuture = Futures.immediateFuture( null );
when( mockMountInstance.beginTransaction() ).thenReturn( mockTransaction );
+ when(mockTransaction.readConfigurationData(any(InstanceIdentifier.class))).thenReturn(
+ ImmutableCompositeNode.builder().toInstance());
mockTransaction.removeConfigurationData( instanceID );
when( mockTransaction.commit() ).thenReturn( expFuture );
</rpc-registry>
<data-broker>
- <type xmlns:binding="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding">binding:binding-data-broker</type>
+ <type xmlns:binding="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding">binding:binding-async-data-broker</type>
<name>binding-data-broker</name>
</data-broker>
*/
package org.opendaylight.controller.config.yang.config.toaster_provider.impl;
+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.DataChangeScope;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
import org.opendaylight.controller.sal.binding.api.BindingAwareBroker;
-import org.opendaylight.controller.sal.binding.api.data.DataChangeListener;
-import org.opendaylight.controller.sal.binding.api.data.DataProviderService;
import org.opendaylight.controller.sample.toaster.provider.OpendaylightToaster;
import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterService;
import org.opendaylight.yangtools.concepts.ListenerRegistration;
/**
*
*/
-public final class ToasterProviderModule extends org.opendaylight.controller.config.yang.config.toaster_provider.impl.AbstractToasterProviderModule
- {
+public final class ToasterProviderModule extends
+ org.opendaylight.controller.config.yang.config.toaster_provider.impl.AbstractToasterProviderModule {
private static final Logger log = LoggerFactory.getLogger(ToasterProviderModule.class);
- public ToasterProviderModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) {
+ public ToasterProviderModule(final org.opendaylight.controller.config.api.ModuleIdentifier identifier,
+ final org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) {
super(identifier, dependencyResolver);
}
- public ToasterProviderModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver,
- ToasterProviderModule oldModule, java.lang.AutoCloseable oldInstance) {
+ public ToasterProviderModule(final org.opendaylight.controller.config.api.ModuleIdentifier identifier,
+ final org.opendaylight.controller.config.api.DependencyResolver dependencyResolver,
+ final ToasterProviderModule oldModule, final java.lang.AutoCloseable oldInstance) {
super(identifier, dependencyResolver, oldModule, oldInstance);
}
@Override
protected void customValidation() {
- // No need to validate dependencies, since all dependencies have mandatory true flag in yang
+ // No need to validate dependencies, since all dependencies have
+ // mandatory true flag in yang
// config-subsystem will perform the validation for dependencies
}
// Register to md-sal
opendaylightToaster.setNotificationProvider(getNotificationServiceDependency());
- DataProviderService dataBrokerService = getDataBrokerDependency();
+ DataBroker dataBrokerService = getDataBrokerDependency();
opendaylightToaster.setDataProvider(dataBrokerService);
- final ListenerRegistration<DataChangeListener> dataChangeListenerRegistration =
- dataBrokerService.registerDataChangeListener( OpendaylightToaster.TOASTER_IID, opendaylightToaster );
+ final ListenerRegistration<DataChangeListener> dataChangeListenerRegistration = dataBrokerService
+ .registerDataChangeListener(LogicalDatastoreType.CONFIGURATION, OpendaylightToaster.TOASTER_IID,
+ opendaylightToaster, DataChangeScope.SUBTREE);
final BindingAwareBroker.RpcRegistration<ToasterService> rpcRegistration = getRpcRegistryDependency()
.addRpcImplementation(ToasterService.class, opendaylightToaster);
import java.util.concurrent.atomic.AtomicLong;
import org.opendaylight.controller.config.yang.config.toaster_provider.impl.ToasterProviderRuntimeMXBean;
-import org.opendaylight.controller.md.sal.common.api.data.DataChangeEvent;
+import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.controller.md.sal.binding.api.DataChangeListener;
+import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
import org.opendaylight.controller.sal.binding.api.NotificationProviderService;
-import org.opendaylight.controller.sal.binding.api.data.DataBrokerService;
-import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction;
import org.opendaylight.controller.sal.common.util.RpcErrors;
import org.opendaylight.controller.sal.common.util.Rpcs;
import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.DisplayString;
import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.MakeToastInput;
+import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.RestockToasterInput;
import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.Toaster;
import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.Toaster.ToasterStatus;
-import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.RestockToasterInput;
import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterBuilder;
import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterOutOfBreadBuilder;
import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterRestocked;
import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterRestockedBuilder;
import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterService;
-import org.opendaylight.controller.sal.binding.api.data.DataChangeListener;
import org.opendaylight.yangtools.yang.binding.DataObject;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
import org.opendaylight.yangtools.yang.common.RpcError;
+import org.opendaylight.yangtools.yang.common.RpcError.ErrorSeverity;
import org.opendaylight.yangtools.yang.common.RpcError.ErrorType;
import org.opendaylight.yangtools.yang.common.RpcResult;
-import org.opendaylight.yangtools.yang.common.RpcError.ErrorSeverity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final DisplayString TOASTER_MODEL_NUMBER = new DisplayString("Model 1 - Binding Aware");
private NotificationProviderService notificationProvider;
- private DataBrokerService dataProvider;
+ private DataBroker dataProvider;
private final ExecutorService executor;
executor = Executors.newFixedThreadPool(1);
}
- public void setNotificationProvider(NotificationProviderService salService) {
+ public void setNotificationProvider(final NotificationProviderService salService) {
this.notificationProvider = salService;
}
- public void setDataProvider(DataBrokerService salDataProvider) {
+ public void setDataProvider(final DataBroker salDataProvider) {
this.dataProvider = salDataProvider;
updateStatus();
}
executor.shutdown();
if (dataProvider != null) {
- final DataModificationTransaction t = dataProvider.beginTransaction();
- t.removeOperationalData(TOASTER_IID);
- t.commit().get();
+ WriteTransaction t = dataProvider.newWriteOnlyTransaction();
+ t.delete(LogicalDatastoreType.OPERATIONAL,TOASTER_IID);
+ t.commit().get(); // FIXME: This call should not be blocking.
}
}
* Implemented from the DataChangeListener interface.
*/
@Override
- public void onDataChanged( DataChangeEvent<InstanceIdentifier<?>, DataObject> change ) {
- DataObject dataObject = change.getUpdatedConfigurationData().get( TOASTER_IID );
+ public void onDataChanged( final AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> change ) {
+ DataObject dataObject = change.getUpdatedSubtree();
if( dataObject instanceof Toaster )
{
Toaster toaster = (Toaster) dataObject;
* RestConf RPC call implemented from the ToasterService interface.
*/
@Override
- public Future<RpcResult<Void>> makeToast(MakeToastInput input) {
+ public Future<RpcResult<Void>> makeToast(final MakeToastInput input) {
LOG.info("makeToast: " + input);
synchronized (taskLock) {
* ToasterRestocked notification.
*/
@Override
- public Future<RpcResult<java.lang.Void>> restockToaster(RestockToasterInput input) {
+ public Future<RpcResult<java.lang.Void>> restockToaster(final RestockToasterInput input) {
LOG.info( "restockToaster: " + input );
synchronized( taskLock ) {
private void updateStatus() {
if (dataProvider != null) {
- final DataModificationTransaction t = dataProvider.beginTransaction();
- t.removeOperationalData(TOASTER_IID);
- t.putOperationalData(TOASTER_IID, buildToaster());
+ WriteTransaction tx = dataProvider.newWriteOnlyTransaction();
+ tx.put(LogicalDatastoreType.OPERATIONAL,TOASTER_IID, buildToaster());
try {
- t.commit().get();
+ tx.commit().get();
} catch (InterruptedException | ExecutionException e) {
LOG.warn("Failed to update toaster status, operational otherwise", e);
}
final MakeToastInput toastRequest;
- public MakeToastTask(MakeToastInput toast) {
+ public MakeToastTask(final MakeToastInput toast) {
toastRequest = toast;
}
uses config:service-ref {
refine type {
mandatory false;
- config:required-identity mdsal:binding-data-broker;
+ config:required-identity mdsal:binding-async-data-broker;
}
}
}