diff options
Diffstat (limited to 'v3po/translate-utils')
13 files changed, 1304 insertions, 0 deletions
diff --git a/v3po/translate-utils/pom.xml b/v3po/translate-utils/pom.xml new file mode 100644 index 000000000..d547cb483 --- /dev/null +++ b/v3po/translate-utils/pom.xml @@ -0,0 +1,90 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (c) 2015 Cisco and/or its affiliates. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <groupId>io.fd.honeycomb.common</groupId> + <artifactId>api-parent</artifactId> + <version>1.0.0-SNAPSHOT</version> + <relativePath>../../common/api-parent</relativePath> + </parent> + + <modelVersion>4.0.0</modelVersion> + <groupId>io.fd.honeycomb.v3po</groupId> + <artifactId>translate-utils</artifactId> + <version>1.0.0-SNAPSHOT</version> + <packaging>bundle</packaging> + + <dependencyManagement> + <dependencies> + <dependency> + <groupId>org.opendaylight.mdsal</groupId> + <artifactId>mdsal-artifacts</artifactId> + <version>2.0.0-Beryllium</version> + <type>pom</type> + <scope>import</scope> + </dependency> + <dependency> + <groupId>org.opendaylight.controller</groupId> + <artifactId>mdsal-artifacts</artifactId> + <version>1.3.0-Beryllium</version> + <type>pom</type> + <scope>import</scope> + </dependency> + </dependencies> + </dependencyManagement> + + <dependencies> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>translate-api</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>translate-spi</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.opendaylight.controller</groupId> + <artifactId>sal-core-api</artifactId> + </dependency> + <dependency> + <groupId>org.opendaylight.mdsal</groupId> + <artifactId>mdsal-binding-dom-codec</artifactId> + </dependency> + + <!-- Testing Dependencies --> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>v3po-api</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-all</artifactId> + <scope>test</scope> + </dependency> + + </dependencies> + +</project> diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/RWUtils.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/RWUtils.java new file mode 100644 index 000000000..027d9bbb7 --- /dev/null +++ b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/RWUtils.java @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.v3po.translate.util; + +import com.google.common.base.Function; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; +import io.fd.honeycomb.v3po.translate.read.ChildReader; +import io.fd.honeycomb.v3po.translate.write.ChildWriter; +import io.fd.honeycomb.v3po.translate.SubtreeManager; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import org.opendaylight.yangtools.yang.binding.Augmentation; +import org.opendaylight.yangtools.yang.binding.ChildOf; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.Identifiable; +import org.opendaylight.yangtools.yang.binding.Identifier; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +public final class RWUtils { + + private RWUtils() {} + + /** + * Find next item in ID after provided type + */ + @Nonnull + public static InstanceIdentifier.PathArgument getNextId(@Nonnull final InstanceIdentifier<? extends DataObject> id, + @Nonnull final InstanceIdentifier<? extends DataObject> type) { + // TODO this is inefficient(maybe, depending on actual Iterable type) + final Iterable<InstanceIdentifier.PathArgument> pathArguments = id.getPathArguments(); + final int i = Iterables.indexOf(pathArguments, new Predicate<InstanceIdentifier.PathArgument>() { + @Override + public boolean apply(final InstanceIdentifier.PathArgument input) { + return input.getType().isAssignableFrom(type.getTargetType()); + } + }); + Preconditions.checkArgument(i >= 0, "Unable to find %s type in %s", type.getTargetType(), id); + return Iterables.get(pathArguments, i + 1); + } + + public static <T> List<ChildReader<? extends ChildOf<T>>> emptyChildReaderList() { + return Collections.emptyList(); + } + + public static <T> List<ChildWriter<? extends ChildOf<T>>> emptyChildWriterList() { + return Collections.emptyList(); + } + + public static <T> List<ChildReader<? extends Augmentation<T>>> emptyAugReaderList() { + return Collections.emptyList(); + } + + public static <T> List<ChildWriter<? extends Augmentation<T>>> emptyAugWriterList() { + return Collections.emptyList(); + } + + public static <T> List<ChildReader<? extends Augmentation<T>>> singletonAugReaderList( + ChildReader<? extends Augmentation<T>> item) { + return Collections.<ChildReader<? extends Augmentation<T>>>singletonList(item); + } + + public static <T> List<ChildReader<? extends ChildOf<T>>> singletonChildReaderList( + ChildReader<? extends ChildOf<T>> item) { + return Collections.<ChildReader<? extends ChildOf<T>>>singletonList(item); + } + + public static <T> List<ChildWriter<? extends ChildOf<T>>> singletonChildWriterList( + ChildWriter<? extends ChildOf<T>> item) { + return Collections.<ChildWriter<? extends ChildOf<T>>>singletonList(item); + } + + /** + * Replace last item in ID with a provided IdentifiableItem of the same type + */ + @SuppressWarnings("unchecked") + @Nonnull + public static <D extends DataObject & Identifiable<K>, K extends Identifier<D>> InstanceIdentifier<D> replaceLastInId( + @Nonnull final InstanceIdentifier<D> id, final InstanceIdentifier.IdentifiableItem<D, K> currentBdItem) { + + final Iterable<InstanceIdentifier.PathArgument> pathArguments = id.getPathArguments(); + final Iterable<InstanceIdentifier.PathArgument> withoutCurrent = + Iterables.limit(pathArguments, Iterables.size(pathArguments) - 1); + final Iterable<InstanceIdentifier.PathArgument> concat = + Iterables.concat(withoutCurrent, Collections.singleton(currentBdItem)); + return (InstanceIdentifier<D>) InstanceIdentifier.create(concat); + } + + /** + * Create IdentifiableItem from target type of provided ID with provided key + */ + @Nonnull + public static <D extends DataObject & Identifiable<K>, K extends Identifier<D>> InstanceIdentifier.IdentifiableItem<D, K> getCurrentIdItem( + @Nonnull final InstanceIdentifier<D> id, final K key) { + return new InstanceIdentifier.IdentifiableItem<>(id.getTargetType(), key); + } + + /** + * Trim InstanceIdentifier at indexOf(type) + */ + @SuppressWarnings("unchecked") + @Nonnull + public static <D extends DataObject> InstanceIdentifier<D> cutId(@Nonnull final InstanceIdentifier<? extends DataObject> id, + @Nonnull final InstanceIdentifier<D> type) { + final Iterable<InstanceIdentifier.PathArgument> pathArguments = id.getPathArguments(); + final int i = Iterables.indexOf(pathArguments, new Predicate<InstanceIdentifier.PathArgument>() { + @Override + public boolean apply(final InstanceIdentifier.PathArgument input) { + return input.getType().equals(type.getTargetType()); + } + }); + Preconditions.checkArgument(i >= 0, "ID %s does not contain %s", id, type); + return (InstanceIdentifier<D>) InstanceIdentifier.create(Iterables.limit(pathArguments, i + 1)); + } + + /** + * Create a map from a collection, checking for duplicity in the process + */ + @Nonnull + public static <K, V> Map<K, V> uniqueLinkedIndex(@Nonnull final Collection<V> values, @Nonnull final Function<? super V, K> keyFunction) { + final Map<K, V> objectObjectLinkedHashMap = Maps.newLinkedHashMap(); + for (V value : values) { + final K key = keyFunction.apply(value); + Preconditions.checkArgument(objectObjectLinkedHashMap.put(key, value) == null, + "Duplicate key detected : %s", key); + } + return objectObjectLinkedHashMap; + } + + public static final Function<SubtreeManager<? extends DataObject>, Class<? extends DataObject>> + MANAGER_CLASS_FUNCTION = new Function<SubtreeManager<? extends DataObject>, Class<? extends DataObject>>() { + @Override + public Class<? extends DataObject> apply(final SubtreeManager<? extends DataObject> input) { + return input.getManagedDataObjectType().getTargetType(); + } + }; + + public static final Function<SubtreeManager<? extends Augmentation<?>>, Class<? extends DataObject>> + MANAGER_CLASS_AUG_FUNCTION = new Function<SubtreeManager<? extends Augmentation<?>>, Class<? extends DataObject>>() { + + @Override + @SuppressWarnings("unchecked") + public Class<? extends DataObject> apply(final SubtreeManager<? extends Augmentation<?>> input) { + final Class<? extends Augmentation<?>> targetType = input.getManagedDataObjectType().getTargetType(); + Preconditions.checkArgument(DataObject.class.isAssignableFrom(targetType)); + return (Class<? extends DataObject>) targetType; + } + }; + + @SuppressWarnings("unchecked") + public static <D extends DataObject> InstanceIdentifier<D> appendTypeToId( + final InstanceIdentifier<? extends DataObject> parentId, final InstanceIdentifier<D> type) { + Preconditions.checkArgument(!parentId.contains(type), + "Unexpected InstanceIdentifier %s, already contains %s", parentId, type); + final InstanceIdentifier.PathArgument t = Iterables.getOnlyElement(type.getPathArguments()); + return (InstanceIdentifier<D>) InstanceIdentifier.create(Iterables.concat( + parentId.getPathArguments(), Collections.singleton(t))); + } +} diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/ReflectionUtils.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/ReflectionUtils.java new file mode 100644 index 000000000..ea0b3b2c4 --- /dev/null +++ b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/ReflectionUtils.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.v3po.translate.util; + +import com.google.common.base.Optional; +import java.lang.reflect.Method; +import java.util.List; +import javax.annotation.Nonnull; + +/** + * Reflection based utilities + */ +public final class ReflectionUtils { + + private ReflectionUtils() {} + + /** + * Find a specific method using reflection + * + * @param managedType Class object to find method in + * @param prefix Method name prefix used when finding the method. Case does not matter. + * @param paramTypes List of input argument types + * @param retType Return type + * + * @return Found method or Optional.absent() if there's no such method + */ + @Nonnull + public static Optional<Method> findMethodReflex(@Nonnull final Class<?> managedType, + @Nonnull final String prefix, + @Nonnull final List<Class<?>> paramTypes, + @Nonnull final Class<?> retType) { + for (Method method : managedType.getMethods()) { + if(isMethodMatch(prefix, paramTypes, retType, method)) { + return Optional.of(method); + } + } + + return Optional.absent(); + } + + private static boolean isMethodMatch(final @Nonnull String prefix, + final @Nonnull List<Class<?>> paramTypes, + final @Nonnull Class<?> retType, final Method method) { + if (!method.getName().toLowerCase().startsWith(prefix.toLowerCase())) { + return false; + } + + final Class<?>[] parameterTypes = method.getParameterTypes(); + if (parameterTypes.length != paramTypes.size()) { + return false; + } + + for (int i = 0; i < parameterTypes.length; i++) { + if (!parameterTypes[i].isAssignableFrom(paramTypes.get(i))) { + return false; + } + } + + if (!method.getReturnType().equals(retType)) { + return false; + } + + return true; + } +} diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/read/DelegatingReaderRegistry.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/read/DelegatingReaderRegistry.java new file mode 100644 index 000000000..387f3cc7c --- /dev/null +++ b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/read/DelegatingReaderRegistry.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.v3po.translate.util.read; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Optional; +import com.google.common.collect.Iterables; +import com.google.common.collect.LinkedListMultimap; +import com.google.common.collect.Multimap; +import io.fd.honeycomb.v3po.translate.read.ListReader; +import io.fd.honeycomb.v3po.translate.read.ReadContext; +import io.fd.honeycomb.v3po.translate.read.ReadFailedException; +import io.fd.honeycomb.v3po.translate.read.ReaderRegistry; +import io.fd.honeycomb.v3po.translate.util.RWUtils; +import io.fd.honeycomb.v3po.translate.read.Reader; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Simple reader registry able to perform and aggregated read (ROOT read) on top of all provided readers. Also able to + * delegate a specific read to one of the delegate readers. + * + * This could serve as a utility to hold & hide all available readers in upper layers. + */ +public final class DelegatingReaderRegistry implements ReaderRegistry { + + private static final Logger LOG = LoggerFactory.getLogger(DelegatingReaderRegistry.class); + + private final Map<Class<? extends DataObject>, Reader<? extends DataObject>> rootReaders; + + /** + * Create new {@link DelegatingReaderRegistry} + * + * @param rootReaders List of delegate readers + */ + public DelegatingReaderRegistry(@Nonnull final List<Reader<? extends DataObject>> rootReaders) { + this.rootReaders = RWUtils.uniqueLinkedIndex(checkNotNull(rootReaders), RWUtils.MANAGER_CLASS_FUNCTION); + } + + @Override + @Nonnull + public Multimap<InstanceIdentifier<? extends DataObject>, ? extends DataObject> readAll( + @Nonnull final ReadContext ctx) throws ReadFailedException { + + LOG.debug("Reading from all delegates: {}", this); + LOG.trace("Reading from all delegates: {}", rootReaders.values()); + + final Multimap<InstanceIdentifier<? extends DataObject>, DataObject> objects = LinkedListMultimap.create(); + for (Reader<? extends DataObject> rootReader : rootReaders.values()) { + LOG.debug("Reading from delegate: {}", rootReader); + + if (rootReader instanceof ListReader) { + final List<? extends DataObject> listEntries = + ((ListReader) rootReader).readList(rootReader.getManagedDataObjectType(), ctx); + if (!listEntries.isEmpty()) { + objects.putAll(rootReader.getManagedDataObjectType(), listEntries); + } + } else { + final Optional<? extends DataObject> read = rootReader.read(rootReader.getManagedDataObjectType(), ctx); + if (read.isPresent()) { + objects.putAll(rootReader.getManagedDataObjectType(), Collections.singletonList(read.get())); + } + } + } + + return objects; + } + + @Nonnull + @Override + public Optional<? extends DataObject> read(@Nonnull final InstanceIdentifier<? extends DataObject> id, + @Nonnull final ReadContext ctx) + throws ReadFailedException { + final InstanceIdentifier.PathArgument first = checkNotNull( + Iterables.getFirst(id.getPathArguments(), null), "Empty id"); + final Reader<? extends DataObject> reader = rootReaders.get(first.getType()); + checkNotNull(reader, + "Unable to read %s. Missing reader. Current readers for: %s", id, rootReaders.keySet()); + LOG.debug("Reading from delegate: {}", reader); + return reader.read(id, ctx); + } + + /** + * @throws UnsupportedOperationException This getter is not supported for reader registry since it does not manage a + * specific node type + */ + @Nonnull + @Override + public InstanceIdentifier<DataObject> getManagedDataObjectType() { + throw new UnsupportedOperationException("Root registry has no type"); + } +} diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/read/NoopReaderCustomizer.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/read/NoopReaderCustomizer.java new file mode 100644 index 000000000..5ed033755 --- /dev/null +++ b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/read/NoopReaderCustomizer.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.v3po.translate.util.read; + +import io.fd.honeycomb.v3po.translate.Context; +import io.fd.honeycomb.v3po.translate.spi.read.RootReaderCustomizer; +import org.opendaylight.yangtools.concepts.Builder; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +public abstract class NoopReaderCustomizer<C extends DataObject, B extends Builder<C>> implements + RootReaderCustomizer<C, B> { + + @Override + public void readCurrentAttributes(InstanceIdentifier<C> id, final B builder, final Context context) { + // Noop + } +} diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/read/ReflexiveChildReaderCustomizer.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/read/ReflexiveChildReaderCustomizer.java new file mode 100644 index 000000000..3d5f9f4e8 --- /dev/null +++ b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/read/ReflexiveChildReaderCustomizer.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.v3po.translate.util.read; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import io.fd.honeycomb.v3po.translate.util.ReflectionUtils; +import io.fd.honeycomb.v3po.translate.spi.read.ChildReaderCustomizer; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collections; +import org.opendaylight.yangtools.concepts.Builder; +import org.opendaylight.yangtools.yang.binding.DataObject; + +/** + * Might be slow ! + */ +public class ReflexiveChildReaderCustomizer<C extends DataObject, B extends Builder<C>> + extends ReflexiveRootReaderCustomizer<C, B> + implements ChildReaderCustomizer<C,B> { + + public ReflexiveChildReaderCustomizer(final Class<B> builderClass) { + super(builderClass); + } + + // TODO Could be just a default implementation in interface (making this a mixin) + + @Override + public void merge(final Builder<? extends DataObject> parentBuilder, final C readValue) { + final Optional<Method> method = + ReflectionUtils.findMethodReflex(parentBuilder.getClass(), "set", + Collections.<Class<?>>singletonList(readValue.getClass()), parentBuilder.getClass()); + + Preconditions.checkArgument(method.isPresent(), "Unable to set %s to %s", readValue, parentBuilder); + + try { + method.get().invoke(parentBuilder, readValue); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new IllegalArgumentException("Unable to set " + readValue + " to " + parentBuilder, e); + } + } + +} diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/read/ReflexiveRootReaderCustomizer.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/read/ReflexiveRootReaderCustomizer.java new file mode 100644 index 000000000..029f359bb --- /dev/null +++ b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/read/ReflexiveRootReaderCustomizer.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.v3po.translate.util.read; + +import org.opendaylight.yangtools.concepts.Builder; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +/** + * Might be slow ! + */ +public class ReflexiveRootReaderCustomizer<C extends DataObject, B extends Builder<C>> extends NoopReaderCustomizer<C, B> { + + private final Class<B> builderClass; + + public ReflexiveRootReaderCustomizer(final Class<B> builderClass) { + this.builderClass = builderClass; + } + + @Override + public B getBuilder(InstanceIdentifier<C> id) { + try { + return builderClass.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new IllegalStateException("Unable to instantiate " + builderClass, e); + } + } +} diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/DelegatingWriterRegistry.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/DelegatingWriterRegistry.java new file mode 100644 index 000000000..fda289e2b --- /dev/null +++ b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/DelegatingWriterRegistry.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.v3po.translate.util.write; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Function; +import com.google.common.collect.Collections2; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import io.fd.honeycomb.v3po.translate.TranslationException; +import io.fd.honeycomb.v3po.translate.util.RWUtils; +import io.fd.honeycomb.v3po.translate.write.WriteContext; +import io.fd.honeycomb.v3po.translate.write.Writer; +import io.fd.honeycomb.v3po.translate.write.WriterRegistry; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Simple writer registry able to perform and aggregated read (ROOT write) on top of all provided writers. Also able to + * delegate a specific read to one of the delegate writers. + * + * This could serve as a utility to hold & hide all available writers in upper layers. + */ +public final class DelegatingWriterRegistry implements WriterRegistry { + + private static final Logger LOG = LoggerFactory.getLogger(DelegatingWriterRegistry.class); + + private static final Function<InstanceIdentifier<?>, Class<? extends DataObject>> ID_TO_CLASS = + new Function<InstanceIdentifier<?>, Class<? extends DataObject>>() { + @Override + public Class<? extends DataObject> apply(final InstanceIdentifier<?> input) { + return input.getTargetType(); + } + }; + + private final Map<Class<? extends DataObject>, Writer<? extends DataObject>> rootWriters; + + /** + * Create new {@link DelegatingWriterRegistry} + * + * @param rootWriters List of delegate writers + */ + public DelegatingWriterRegistry(@Nonnull final List<Writer<? extends DataObject>> rootWriters) { + this.rootWriters = RWUtils.uniqueLinkedIndex(checkNotNull(rootWriters), RWUtils.MANAGER_CLASS_FUNCTION); + } + + /** + * @throws UnsupportedOperationException This getter is not supported for writer registry since it does not manage a + * specific node type + */ + @Nonnull + @Override + public InstanceIdentifier<DataObject> getManagedDataObjectType() { + throw new UnsupportedOperationException("Root registry has no type"); + } + + @Override + public void update(@Nonnull final InstanceIdentifier<? extends DataObject> id, + @Nullable final DataObject dataBefore, + @Nullable final DataObject dataAfter, + @Nonnull final WriteContext ctx) throws TranslationException { + final InstanceIdentifier.PathArgument first = checkNotNull( + Iterables.getFirst(id.getPathArguments(), null), "Empty id"); + final Writer<? extends DataObject> writer = rootWriters.get(first.getType()); + checkNotNull(writer, + "Unable to write %s. Missing writer. Current writers for: %s", id, rootWriters.keySet()); + writer.update(id, dataBefore, dataAfter, ctx); + } + + @Override + public void update(@Nonnull final Map<InstanceIdentifier<?>, DataObject> nodesBefore, + @Nonnull final Map<InstanceIdentifier<?>, DataObject> nodesAfter, + @Nonnull final WriteContext ctx) throws TranslationException { + checkAllWritersPresent(nodesBefore); + checkAllWritersPresent(nodesAfter); + + final List<InstanceIdentifier<?>> processedNodes = Lists.newArrayList(); + + for (Map.Entry<Class<? extends DataObject>, Writer<? extends DataObject>> rootWriterEntry : rootWriters + .entrySet()) { + + final InstanceIdentifier<? extends DataObject> id = rootWriterEntry.getValue().getManagedDataObjectType(); + + final DataObject dataBefore = nodesBefore.get(id); + final DataObject dataAfter = nodesAfter.get(id); + + // No change to current writer + if (dataBefore == null && dataAfter == null) { + continue; + } + + LOG.debug("ChangesProcessor.applyChanges() processing dataBefore={}, dataAfter={}", dataBefore, dataAfter); + + try { + update(id, dataBefore, dataAfter, ctx); + processedNodes.add(id); + } catch (Exception e) { + LOG.error("Error while processing data change of: {} (before={}, after={})", + id, dataBefore, dataAfter, e); + throw new BulkUpdateException( + id, new ReverterImpl(this, processedNodes, nodesBefore, nodesAfter, ctx), e); + } + } + } + + private void checkAllWritersPresent(final @Nonnull Map<InstanceIdentifier<?>, DataObject> nodesBefore) { + checkArgument(rootWriters.keySet().containsAll(Collections2.transform(nodesBefore.keySet(), ID_TO_CLASS)), + "Unable to handle all changes. Missing dedicated writers for: %s", + Sets.difference(nodesBefore.keySet(), rootWriters.keySet())); + } + + private static final class ReverterImpl implements Reverter { + private final WriterRegistry delegatingWriterRegistry; + private final List<InstanceIdentifier<?>> processedNodes; + private final Map<InstanceIdentifier<?>, DataObject> nodesBefore; + private final Map<InstanceIdentifier<?>, DataObject> nodesAfter; + private final WriteContext ctx; + + ReverterImpl(final WriterRegistry delegatingWriterRegistry, + final List<InstanceIdentifier<?>> processedNodes, + final Map<InstanceIdentifier<?>, DataObject> nodesBefore, + final Map<InstanceIdentifier<?>, DataObject> nodesAfter, final WriteContext ctx) { + this.delegatingWriterRegistry = delegatingWriterRegistry; + this.processedNodes = processedNodes; + this.nodesBefore = nodesBefore; + this.nodesAfter = nodesAfter; + this.ctx = ctx; + } + + @Override + public void revert() throws RevertFailedException { + final LinkedList<InstanceIdentifier<?>> notReverted = new LinkedList<>(processedNodes); + + while (notReverted.size() > 0) { + final InstanceIdentifier<?> node = notReverted.peekLast(); + LOG.debug("ChangesProcessor.revertChanges() processing node={}", node); + + final DataObject dataBefore = nodesBefore.get(node); + final DataObject dataAfter = nodesAfter.get(node); + + // revert a change by invoking writer with reordered arguments + try { + delegatingWriterRegistry.update(node, dataAfter, dataBefore, ctx); + notReverted.removeLast(); // change was successfully reverted + } catch (Exception e) { + throw new RevertFailedException(notReverted, e); + } + + } + } + } +} diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/NoopWriterCustomizer.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/NoopWriterCustomizer.java new file mode 100644 index 000000000..266325815 --- /dev/null +++ b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/NoopWriterCustomizer.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.v3po.translate.util.write; + +import io.fd.honeycomb.v3po.translate.spi.write.RootWriterCustomizer; +import io.fd.honeycomb.v3po.translate.Context; +import javax.annotation.Nonnull; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +/** + * Customizer not performing any changes on current level. Suitable for nodes that don't have any leaves and all of + * its child nodes are managed by dedicated writers + */ +public class NoopWriterCustomizer<D extends DataObject> implements RootWriterCustomizer<D> { + + @Override + public void writeCurrentAttributes(@Nonnull final InstanceIdentifier<D> id, @Nonnull final D dataAfter, + @Nonnull final Context ctx) { + + } + + @Override + public void updateCurrentAttributes(@Nonnull final InstanceIdentifier<D> id, @Nonnull final D dataBefore, + @Nonnull final D dataAfter, + @Nonnull final Context ctx) { + + } + + @Override + public void deleteCurrentAttributes(@Nonnull final InstanceIdentifier<D> id, @Nonnull final D dataBefore, + @Nonnull final Context ctx) { + + } +} diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/ReflexiveChildWriterCustomizer.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/ReflexiveChildWriterCustomizer.java new file mode 100644 index 000000000..ba67e560c --- /dev/null +++ b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/ReflexiveChildWriterCustomizer.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.v3po.translate.util.write; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.Iterables; +import io.fd.honeycomb.v3po.translate.util.ReflectionUtils; +import io.fd.honeycomb.v3po.translate.spi.write.ChildWriterCustomizer; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collections; +import javax.annotation.Nonnull; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +/** + * Might be slow ! + */ +public class ReflexiveChildWriterCustomizer<C extends DataObject> extends NoopWriterCustomizer<C> implements + ChildWriterCustomizer<C> { + + @Nonnull + @Override + @SuppressWarnings("unchecked") + public Optional<C> extract(@Nonnull final InstanceIdentifier<C> currentId, @Nonnull final DataObject parentData) { + final Class<C> currentType = currentId.getTargetType(); + final Optional<Method> method = ReflectionUtils.findMethodReflex(getParentType(currentId), + "get" + currentType.getSimpleName(), Collections.<Class<?>>emptyList(), currentType); + + Preconditions.checkArgument(method.isPresent(), "Unable to get %s from %s", currentType, parentData); + + try { + return method.isPresent() + ? Optional.of((C) method.get().invoke(parentData)) + : Optional.<C>absent(); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new IllegalArgumentException("Unable to get " + currentType + " from " + parentData, e); + } + } + + private Class<? extends DataObject> getParentType(final @Nonnull InstanceIdentifier<C> currentId) { + return Iterables.get(currentId.getPathArguments(), Iterables.size(currentId.getPathArguments()) - 2).getType(); + } +} diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/TransactionWriteContext.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/TransactionWriteContext.java new file mode 100644 index 000000000..0bb68e3b2 --- /dev/null +++ b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/TransactionWriteContext.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.v3po.translate.util.write; + +import com.google.common.base.Optional; +import com.google.common.util.concurrent.CheckedFuture; +import io.fd.honeycomb.v3po.translate.write.WriteContext; +import io.fd.honeycomb.v3po.translate.Context; +import java.util.Map; +import javax.annotation.Nonnull; +import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; +import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; +import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction; +import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; + +/** + * Transaction based WriteContext + */ +public final class TransactionWriteContext implements WriteContext { + + private final DOMDataReadOnlyTransaction beforeTx; + private final DOMDataReadOnlyTransaction afterTx; + private final Context ctx; + private final BindingNormalizedNodeSerializer serializer; + + public TransactionWriteContext(final BindingNormalizedNodeSerializer serializer, + final DOMDataReadOnlyTransaction beforeTx, + final DOMDataReadOnlyTransaction afterTx) { + this.serializer = serializer; + this.beforeTx = beforeTx; + this.afterTx = afterTx; + this.ctx = new Context(); + } + + // TODO make this asynchronous + + @Override + public Optional<DataObject> readBefore(@Nonnull final InstanceIdentifier<? extends DataObject> currentId) { + return read(currentId, beforeTx); + } + + private Optional<DataObject> read(final InstanceIdentifier<? extends DataObject> currentId, + final DOMDataReadOnlyTransaction tx) { + final YangInstanceIdentifier path = serializer.toYangInstanceIdentifier(currentId); + + final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> read = + tx.read(LogicalDatastoreType.CONFIGURATION, path); + + try { + // TODO once the APIs are asynchronous use just Futures.transform + final Optional<NormalizedNode<?, ?>> optional = read.checkedGet(); + + if (!optional.isPresent()) { + return Optional.absent(); + } + + final NormalizedNode<?, ?> data = optional.get(); + final Map.Entry<InstanceIdentifier<?>, DataObject> entry = serializer.fromNormalizedNode(path, data); + + return Optional.of(entry.getValue()); + } catch (ReadFailedException e) { + throw new IllegalStateException("Unable to perform read", e); + } + } + + @Override + public Optional<DataObject> readAfter(@Nonnull final InstanceIdentifier<? extends DataObject> currentId) { + return read(currentId, afterTx); + } + + @Override + public Context getContext() { + return ctx; + } + + /** + * Does not close the transactions + */ + @Override + public void close() { + ctx.close(); + } +} diff --git a/v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/impl/write/util/DelegatingWriterRegistryTest.java b/v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/impl/write/util/DelegatingWriterRegistryTest.java new file mode 100644 index 000000000..e201890b5 --- /dev/null +++ b/v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/impl/write/util/DelegatingWriterRegistryTest.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.v3po.translate.impl.write.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import io.fd.honeycomb.v3po.translate.TranslationException; +import io.fd.honeycomb.v3po.translate.util.write.DelegatingWriterRegistry; +import io.fd.honeycomb.v3po.translate.write.WriteContext; +import io.fd.honeycomb.v3po.translate.write.Writer; +import io.fd.honeycomb.v3po.translate.write.WriterRegistry; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.Interfaces; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.Vpp; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.VppState; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +public class DelegatingWriterRegistryTest { + + private final InstanceIdentifier<Vpp> vppId; + private final InstanceIdentifier<VppState> vppStateId; + private final InstanceIdentifier<Interfaces> interfaceId; + + private WriteContext ctx; + private Writer<Vpp> writer; + private Writer<VppState> vppStateWriter; + private Writer<Interfaces> interfacesWriter; + + private DelegatingWriterRegistry registry; + + public DelegatingWriterRegistryTest() { + vppId = InstanceIdentifier.create(Vpp.class); + vppStateId = InstanceIdentifier.create(VppState.class); + interfaceId = InstanceIdentifier.create(Interfaces.class); + } + + @SuppressWarnings("unchecked") + private <D extends DataObject> Writer<D> mockWriter(Class<D> clazz) { + final Writer<D> mock = (Writer<D>) Mockito.mock(Writer.class); + doReturn(InstanceIdentifier.create(clazz)).when(mock).getManagedDataObjectType(); + return mock; + } + + private DataObject mockDataObject(final String name, final Class<? extends DataObject> classToMock) { + final DataObject dataBefore = mock(classToMock, name); + doReturn(classToMock).when(dataBefore).getImplementedInterface(); + return dataBefore; + } + + @SuppressWarnings("unchecked") + private static Map<InstanceIdentifier<?>, DataObject> asMap(DataObject... objects) { + final Map<InstanceIdentifier<?>, DataObject> map = new HashMap<>(); + for (DataObject object : objects) { + final Class<? extends DataObject> implementedInterface = + (Class<? extends DataObject>) object.getImplementedInterface(); + final InstanceIdentifier<?> id = InstanceIdentifier.create(implementedInterface); + map.put(id, object); + } + return map; + } + + @Before + public void setUp() { + ctx = mock(WriteContext.class); + writer = mockWriter(Vpp.class); + vppStateWriter = mockWriter(VppState.class); + interfacesWriter = mockWriter(Interfaces.class); + + final List<Writer<? extends DataObject>> writers = new ArrayList<>(); + writers.add(writer); + writers.add(vppStateWriter); + writers.add(interfacesWriter); + + registry = new DelegatingWriterRegistry(writers); + } + + @Test(expected = UnsupportedOperationException.class) + public void testGetManagedDataObjectType() { + registry.getManagedDataObjectType(); + } + + @Test + public void testBulkUpdateRevert() throws Exception { + // Prepare data changes: + final DataObject dataBefore1 = mockDataObject("Vpp before", Vpp.class); + final DataObject dataAfter1 = mockDataObject("Vpp after", Vpp.class); + + final DataObject dataBefore2 = mockDataObject("VppState before", VppState.class); + final DataObject dataAfter2 = mockDataObject("VppState after", VppState.class); + + // Fail on update + Mockito.doThrow(new TranslationException("vpp failed")).when(vppStateWriter) + .update(vppStateId, dataBefore2, dataAfter2, ctx); + + // Run the test + try { + registry.update(asMap(dataBefore1, dataBefore2), asMap(dataAfter1, dataAfter2), ctx); + } catch (WriterRegistry.BulkUpdateException e) { + // Check second update failed + assertEquals(vppStateId, e.getFailedId()); + verify(writer).update(vppId, dataBefore1, dataAfter1, ctx); + verify(vppStateWriter).update(vppStateId, dataBefore2, dataAfter2, ctx); + + // Try to revert changes + e.revertChanges(); + + // Check revert was successful + verify(writer).update(vppId, dataAfter1, dataBefore1, ctx); + verify(vppStateWriter, never()).update(vppStateId, dataAfter2, dataBefore2, ctx); + + return; + } + fail("BulkUpdateException expected"); + } + + @Test + public void testBulkUpdateRevertFail() throws Exception { + // Prepare data changes: + final DataObject dataBefore1 = mockDataObject("Vpp before", Vpp.class); + final DataObject dataAfter1 = mockDataObject("Vpp after", Vpp.class); + + final DataObject dataBefore2 = mockDataObject("VppState before", VppState.class); + final DataObject dataAfter2 = mockDataObject("VppState after", VppState.class); + + final DataObject dataBefore3 = mockDataObject("Interfaces before", Interfaces.class); + final DataObject dataAfter3 = mockDataObject("Interfaces after", Interfaces.class); + + // Fail on the third update + doThrow(new TranslationException("vpp failed")).when(interfacesWriter) + .update(interfaceId, dataBefore3, dataAfter3, ctx); + + // Fail on the second revert + doThrow(new TranslationException("vpp failed again")).when(writer) + .update(vppId, dataAfter1, dataBefore1, ctx); + + // Run the test + try { + registry.update(asMap(dataBefore1, dataBefore2, dataBefore3), asMap(dataAfter1, dataAfter2, dataAfter3), ctx); + } catch (WriterRegistry.BulkUpdateException e) { + // Check third update failed + assertEquals(interfaceId, e.getFailedId()); + verify(writer).update(vppId, dataBefore1, dataAfter1, ctx); + verify(vppStateWriter).update(vppStateId, dataBefore2, dataAfter2, ctx); + verify(interfacesWriter).update(interfaceId, dataBefore3, dataAfter3, ctx); + + // Try to revert changes + try { + e.revertChanges(); + } catch (WriterRegistry.Reverter.RevertFailedException e2) { + // Check second revert failed + assertEquals(Collections.singletonList(vppId), e2.getNotRevertedChanges()); + verify(writer).update(vppId, dataAfter1, dataBefore1, ctx); + verify(vppStateWriter).update(vppStateId, dataAfter2, dataBefore2, ctx); + verify(interfacesWriter, never()).update(interfaceId, dataAfter3, dataBefore3, ctx); + return; + } + fail("WriterRegistry.Revert.RevertFailedException expected"); + } + fail("BulkUpdateException expected"); + } +}
\ No newline at end of file diff --git a/v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/impl/write/util/TransactionWriteContextTest.java b/v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/impl/write/util/TransactionWriteContextTest.java new file mode 100644 index 000000000..5a8740b2d --- /dev/null +++ b/v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/impl/write/util/TransactionWriteContextTest.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.v3po.translate.impl.write.util; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; + +import com.google.common.base.Optional; +import com.google.common.util.concurrent.CheckedFuture; +import io.fd.honeycomb.v3po.translate.Context; +import io.fd.honeycomb.v3po.translate.util.write.TransactionWriteContext; +import java.util.Map; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; +import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; +import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction; +import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.Vpp; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.VppState; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.BridgeDomains; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.bridge.domains.BridgeDomain; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; + +public class TransactionWriteContextTest { + + @Mock + private BindingNormalizedNodeSerializer serializer; + @Mock + private DOMDataReadOnlyTransaction beforeTx; + @Mock + private DOMDataReadOnlyTransaction afterTx; + @Mock + private CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> future; + @Mock + private Optional<org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode<?, ?>> optional; + @Mock + private Map.Entry entry; + + private TransactionWriteContext transactionWriteContext; + + @Before + public void setUp() { + initMocks(this); + transactionWriteContext = new TransactionWriteContext(serializer, beforeTx, afterTx); + } + + @Test + public void testReadBeforeNoData() throws Exception { + when(beforeTx.read(eq(LogicalDatastoreType.CONFIGURATION), any(YangInstanceIdentifier.class))).thenReturn(future); + when(future.checkedGet()).thenReturn(optional); + when(optional.isPresent()).thenReturn(false); + + final InstanceIdentifier<BridgeDomain> instanceId = + InstanceIdentifier.create(Vpp.class).child(BridgeDomains.class).child(BridgeDomain.class); + + final Optional<DataObject> dataObjects = transactionWriteContext.readBefore(instanceId); + assertNotNull(dataObjects); + assertFalse(dataObjects.isPresent()); + + verify(serializer).toYangInstanceIdentifier(instanceId); + verify(serializer, never()).fromNormalizedNode(any(YangInstanceIdentifier.class), any(NormalizedNode.class)); + } + + + @Test + public void testReadBefore() throws Exception { + when(beforeTx.read(eq(LogicalDatastoreType.CONFIGURATION), any(YangInstanceIdentifier.class))).thenReturn(future); + when(future.checkedGet()).thenReturn(optional); + when(optional.isPresent()).thenReturn(true); + + final InstanceIdentifier<BridgeDomain> instanceId = + InstanceIdentifier.create(Vpp.class).child(BridgeDomains.class).child(BridgeDomain.class); + final YangInstanceIdentifier yangId = YangInstanceIdentifier.builder().node(VppState.QNAME).node( + BridgeDomains.QNAME).node(BridgeDomain.QNAME).build(); + when(serializer.toYangInstanceIdentifier(any(InstanceIdentifier.class))).thenReturn(yangId); + when(serializer.fromNormalizedNode(eq(yangId), any(NormalizedNode.class))).thenReturn(entry); + when(entry.getValue()).thenReturn(mock(DataObject.class)); + + final Optional<DataObject> dataObjects = transactionWriteContext.readBefore(instanceId); + assertNotNull(dataObjects); + assertTrue(dataObjects.isPresent()); + + verify(serializer).toYangInstanceIdentifier(instanceId); + verify(serializer).fromNormalizedNode(eq(yangId), any(NormalizedNode.class)); + } + + @Test(expected = IllegalStateException.class) + public void testReadBeforeFailed() throws Exception { + when(beforeTx.read(eq(LogicalDatastoreType.CONFIGURATION), any(YangInstanceIdentifier.class))).thenReturn(future); + when(future.checkedGet()).thenThrow(ReadFailedException.class); + transactionWriteContext.readBefore(mock(InstanceIdentifier.class)); + } + + @Test(expected = IllegalStateException.class) + public void testReadAfterFailed() throws Exception { + when(afterTx.read(eq(LogicalDatastoreType.CONFIGURATION), any(YangInstanceIdentifier.class))).thenReturn(future); + when(future.checkedGet()).thenThrow(ReadFailedException.class); + transactionWriteContext.readAfter(mock(InstanceIdentifier.class)); + } + + @Test + public void testGetContext() throws Exception { + assertNotNull(transactionWriteContext.getContext()); + } + + @Test + public void testClose() throws Exception { + final Context context = transactionWriteContext.getContext(); + transactionWriteContext.close(); + // TODO verify context was closed + } +}
\ No newline at end of file |