From 87a35a44acf380c29e9b0a0d12e25819505efca3 Mon Sep 17 00:00:00 2001 From: Marek Gradzki Date: Fri, 4 Mar 2016 12:32:10 +0100 Subject: Dedicated data-tree for Honeycomb agent. Initial API Implementation. Change-Id: I96c682e2d0d544a4f937bc992a7d0919cb358fac Signed-off-by: Marek Gradzki --- .../fd/honeycomb/v3po/impl/data/DataTreeUtils.java | 76 ++++++++ .../v3po/impl/data/VppConfigDataTree.java | 206 +++++++++++++++++++++ .../fd/honeycomb/v3po/impl/data/VppDataTree.java | 5 +- .../v3po/impl/data/VppOperationalDataTree.java | 80 ++++++++ 4 files changed, 366 insertions(+), 1 deletion(-) create mode 100644 v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/DataTreeUtils.java create mode 100644 v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppConfigDataTree.java create mode 100644 v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppOperationalDataTree.java (limited to 'v3po/impl/src/main/java/io/fd') diff --git a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/DataTreeUtils.java b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/DataTreeUtils.java new file mode 100644 index 000000000..bb9da5695 --- /dev/null +++ b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/DataTreeUtils.java @@ -0,0 +1,76 @@ +/* + * 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.impl.data; + +import com.google.common.base.Preconditions; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; +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.DataContainerChild; +import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Utility class for various operations on DataTree. + */ +final class DataTreeUtils { + private static final Logger LOG = LoggerFactory.getLogger(DataTreeUtils.class); + + private DataTreeUtils() { + throw new UnsupportedOperationException("Can't instantiate util class"); + } + + /** + * Translates children of supplied YANG ContainerNode into Binding data. + * + * @param parent ContainerNode representing data + * @param serializer service for serialization between Java Binding Data representation and NormalizedNode + * representation. + * @return NormalizedNode representation of parent's node children + */ + static Map, DataObject> childrenFromNormalized(@Nonnull final DataContainerNode parent, + @Nonnull final BindingNormalizedNodeSerializer serializer) { + + Preconditions.checkNotNull(parent, "parent node should not be null"); + Preconditions.checkNotNull(serializer, "serializer should not be null"); + + final Map, DataObject> map = new HashMap<>(); + + final Collection> children = + parent.getValue(); + + for (final DataContainerChild child : children) { + final YangInstanceIdentifier.PathArgument pathArgument = child.getIdentifier(); + final YangInstanceIdentifier identifier = YangInstanceIdentifier.create(pathArgument); + LOG.debug("VppConfigDataProxy.extractDataObject() child={}, pathArgument={}, identifier={}", child, + pathArgument, identifier); + + final Map.Entry, DataObject> entry = serializer.fromNormalizedNode(identifier, child); + if (entry != null) { + map.put(entry.getKey(), entry.getValue()); + } + } + + return map; + } +} diff --git a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppConfigDataTree.java b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppConfigDataTree.java new file mode 100644 index 000000000..e1f356592 --- /dev/null +++ b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppConfigDataTree.java @@ -0,0 +1,206 @@ +/* + * 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.impl.data; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.util.concurrent.CheckedFuture; +import com.google.common.util.concurrent.Futures; +import io.fd.honeycomb.v3po.impl.trans.VppApiInvocationException; +import io.fd.honeycomb.v3po.impl.trans.VppWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nonnull; +import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; +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.DataContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * VppDataTree implementation for configuration data. + */ +public final class VppConfigDataTree implements VppDataTree { + + private static final Logger LOG = LoggerFactory.getLogger(VppConfigDataTree.class); + + private final BindingNormalizedNodeSerializer serializer; + private final DataTree dataTree; + private final VppWriter writer; + + /** + * Creates configuration data tree instance. + * + * @param serializer service for serialization between Java Binding Data representation and NormalizedNode + * representation. + * @param dataTree data tree for configuration data representation + * @param vppWriter service for translation between Java Binding Data and Vpp. + */ + public VppConfigDataTree(@Nonnull final BindingNormalizedNodeSerializer serializer, + @Nonnull final DataTree dataTree, @Nonnull final VppWriter vppWriter) { + this.serializer = Preconditions.checkNotNull(serializer, "serializer should not be null"); + this.dataTree = Preconditions.checkNotNull(dataTree, "dataTree should not be null"); + this.writer = Preconditions.checkNotNull(vppWriter, "vppWriter should not be null"); + } + + @Override + public VppDataTreeSnapshot takeSnapshot() { + return new ConfigSnapshot(dataTree.takeSnapshot()); + } + + @Override + public void commit(final DataTreeModification modification) throws DataValidationFailedException, VppApiInvocationException { + dataTree.validate(modification); + + final DataTreeCandidate candidate = dataTree.prepare(modification); + + final DataTreeCandidateNode rootNode = candidate.getRootNode(); + final YangInstanceIdentifier rootPath = candidate.getRootPath(); + final Optional> normalizedDataBefore = rootNode.getDataBefore(); + final Optional> normalizedDataAfter = rootNode.getDataAfter(); + LOG.debug("VppConfigDataProxy.commit() rootPath={}, rootNode={}, dataBefore={}, dataAfter={}", + rootPath, rootNode, normalizedDataBefore, normalizedDataAfter); + + final Map, DataObject> nodesBefore = extractNetconfData(normalizedDataBefore); + LOG.debug("VppConfigDataProxy.commit() extracted nodesBefore={}", nodesBefore.keySet()); + + final Map, DataObject> nodesAfter = extractNetconfData(normalizedDataAfter); + LOG.debug("VppConfigDataProxy.commit() extracted nodesAfter={}", nodesAfter.keySet()); + + final ChangesProcessor processor = new ChangesProcessor(writer, nodesBefore, nodesAfter); + try { + processor.applyChanges(); + } catch (VppApiInvocationException e) { + LOG.warn("Failed to apply changes", e); + LOG.info("Trying to revert successful changes for current transaction"); + + try { + processor.revertChanges(); + LOG.info("Changes successfully reverted"); + } catch (VppApiInvocationException e2) { + LOG.error("Failed to revert successful changes", e2); + } + + // rethrow as we can't do anything more about it + throw e; + } + + + dataTree.commit(candidate); + } + + private Map, DataObject> extractNetconfData(final Optional> parentOptional) { + if (parentOptional.isPresent()) { + final DataContainerNode parent = (DataContainerNode)parentOptional.get(); + return DataTreeUtils.childrenFromNormalized(parent, serializer); + } + return Collections.emptyMap(); + } + + private final static class ConfigSnapshot implements VppDataTreeSnapshot { + private final DataTreeSnapshot snapshot; + + ConfigSnapshot(@Nonnull final DataTreeSnapshot snapshot) { + this.snapshot = snapshot; + } + + @Override + public CheckedFuture>, ReadFailedException> read( + final YangInstanceIdentifier path) { + return Futures.immediateCheckedFuture(snapshot.readNode(path)); + } + + @Override + public DataTreeModification newModification() { + return snapshot.newModification(); + } + } + + private static final class ChangesProcessor { + private final VppWriter writer; + private final List> processedNodes; + private final Map, DataObject> nodesBefore; + private final Map, DataObject> nodesAfter; + + ChangesProcessor(@Nonnull final VppWriter writer, + final Map, DataObject> nodesBefore, + final Map, DataObject> nodesAfter) { + this.writer = Preconditions.checkNotNull(writer, "VppWriter is null!"); + this.nodesBefore = Preconditions.checkNotNull(nodesBefore, "nodesBefore is null!"); + this.nodesAfter = Preconditions.checkNotNull(nodesAfter, "nodesAfter is null!"); + processedNodes = new ArrayList<>(); + } + + void applyChanges() throws VppApiInvocationException { + // TODO we should care about the order of modified subtrees + final Set> allNodes = new HashSet<>(); + allNodes.addAll(nodesBefore.keySet()); + allNodes.addAll(nodesAfter.keySet()); + LOG.debug("VppConfigDataProxy.applyChanges() all extracted nodes: {}", allNodes); + + for (InstanceIdentifier node : allNodes) { + LOG.debug("VppConfigDataProxy.applyChanges() processing node={}", node); + final DataObject dataBefore = nodesBefore.get(node); + final DataObject dataAfter = nodesAfter.get(node); + + try { + writer.process(dataBefore, dataAfter); + processedNodes.add(node); + } catch (VppApiInvocationException e) { + LOG.error("Error while processing data change (before={}, after={})", dataBefore, dataAfter, e); + throw e; + } + } + } + + void revertChanges() throws VppApiInvocationException { + Preconditions.checkNotNull(writer, "VppWriter is nuserializerll!"); + + // revert changes in reverse order they were applied + final ListIterator> iterator = processedNodes.listIterator(processedNodes.size()); + + while (iterator.hasPrevious()) { + final InstanceIdentifier node = iterator.previous(); + LOG.debug("VppConfigDataProxy.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 + writer.process(dataAfter, dataBefore); + } + } + } +} + + + diff --git a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppDataTree.java b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppDataTree.java index 9f64c3966..a3a4fae65 100644 --- a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppDataTree.java +++ b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppDataTree.java @@ -17,6 +17,7 @@ package io.fd.honeycomb.v3po.impl.data; import com.google.common.annotations.Beta; +import io.fd.honeycomb.v3po.impl.trans.VppApiInvocationException; import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification; import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException; @@ -30,8 +31,10 @@ public interface VppDataTree { * * @param modification VPP data tree modification * @throws DataValidationFailedException if modification data is not valid + * @throws VppApiInvocationException if commit failed while updating VPP state */ - void commit(final DataTreeModification modification) throws DataValidationFailedException; + void commit(final DataTreeModification modification) throws DataValidationFailedException, + VppApiInvocationException; /** * Creates read-only snapshot of a VppDataTree. diff --git a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppOperationalDataTree.java b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppOperationalDataTree.java new file mode 100644 index 000000000..0e6bc7d34 --- /dev/null +++ b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppOperationalDataTree.java @@ -0,0 +1,80 @@ +/* + * 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.impl.data; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.util.concurrent.CheckedFuture; +import com.google.common.util.concurrent.Futures; +import io.fd.honeycomb.v3po.impl.trans.VppReader; +import java.util.Map; +import javax.annotation.Nonnull; +import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; +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; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * ReadableVppDataTree implementation for operational data. + */ +public final class VppOperationalDataTree implements ReadableVppDataTree { + private static final Logger LOG = LoggerFactory.getLogger(VppOperationalDataTree.class); + private final BindingNormalizedNodeSerializer serializer; + private final VppReader reader; + + /** + * Creates operational data tree instance. + * + * @param serializer service for serialization between Java Binding Data representation and NormalizedNode + * representation. + * @param reader service for translation between Vpp and Java Binding Data. + */ + public VppOperationalDataTree(@Nonnull BindingNormalizedNodeSerializer serializer, + @Nonnull VppReader reader) { + this.serializer = Preconditions.checkNotNull(serializer, "serializer should not be null"); + this.reader = Preconditions.checkNotNull(reader, "reader should not be null"); + } + + @Override + public CheckedFuture>, ReadFailedException> read( + final YangInstanceIdentifier yangInstanceIdentifier) { + // TODO What if the path is ROOT/empty? + final InstanceIdentifier path = serializer.fromYangInstanceIdentifier(yangInstanceIdentifier); + LOG.debug("VppOperationalDataProxy.read(), path={}", path); + + final DataObject dataObject = reader.read(path); // FIXME we need to expect a list of dataObjects here + return Futures.immediateCheckedFuture(toNormalizedNode(path, dataObject)); + } + + private Optional> toNormalizedNode(final InstanceIdentifier path, + final DataObject dataObject) { + LOG.trace("VppOperationalDataProxy.toNormalizedNode(), path={}, path={}", path, dataObject); + final Map.Entry> entry = + serializer.toNormalizedNode(path, dataObject); + + final NormalizedNode value = entry.getValue(); + LOG.trace("VppOperationalDataProxy.toNormalizedNode(), value={}", value); + + final Optional> optional = Optional.>fromNullable(value); + LOG.trace("VppOperationalDataProxy.toNormalizedNode(), optional={}", optional); + return optional; + } +} -- cgit 1.2.3-korg