All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opendaylight.controller.cluster.sharding.DistributedShardChangePublisher Maven / Gradle / Ivy

/*
 * Copyright (c) 2016 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.sharding;

import static com.google.common.base.Preconditions.checkState;
import static java.util.Objects.requireNonNull;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import org.checkerframework.checker.lock.qual.GuardedBy;
import org.opendaylight.controller.cluster.databroker.actors.dds.DataStoreClient;
import org.opendaylight.controller.cluster.datastore.DistributedDataStoreInterface;
import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeListener;
import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
import org.opendaylight.mdsal.dom.spi.AbstractDOMDataTreeChangeListenerRegistration;
import org.opendaylight.mdsal.dom.spi.AbstractRegistrationTree;
import org.opendaylight.mdsal.dom.spi.RegistrationTreeNode;
import org.opendaylight.mdsal.dom.spi.shard.ChildShardContext;
import org.opendaylight.mdsal.dom.spi.store.DOMStoreTreeChangePublisher;
import org.opendaylight.yangtools.concepts.ListenerRegistration;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
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.DataTreeCandidateNodes;
import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidates;
import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeConfiguration;
import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
import org.opendaylight.yangtools.yang.data.impl.schema.tree.InMemoryDataTreeFactory;
import org.opendaylight.yangtools.yang.data.impl.schema.tree.SchemaValidationFailedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DistributedShardChangePublisher
        extends AbstractRegistrationTree>
        implements DOMStoreTreeChangePublisher {

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

    private final DistributedDataStoreInterface distributedDataStore;
    private final YangInstanceIdentifier shardPath;

    private final Map childShards;

    @GuardedBy("this")
    private final DataTree dataTree;

    public DistributedShardChangePublisher(final DataStoreClient client,
                                           final DistributedDataStoreInterface distributedDataStore,
                                           final DOMDataTreeIdentifier prefix,
                                           final Map childShards) {
        this.distributedDataStore = distributedDataStore;
        // TODO keeping the whole dataTree thats contained in subshards doesn't seem like a good idea
        // maybe the whole listener logic would be better in the backend shards where we have direct access to the
        // dataTree and wont have to cache it redundantly.

        final DataTreeConfiguration baseConfig;
        switch (prefix.getDatastoreType()) {
            case CONFIGURATION:
                baseConfig = DataTreeConfiguration.DEFAULT_CONFIGURATION;
                break;
            case OPERATIONAL:
                baseConfig = DataTreeConfiguration.DEFAULT_OPERATIONAL;
                break;
            default:
                throw new UnsupportedOperationException("Unknown prefix type " + prefix.getDatastoreType());
        }

        this.dataTree = new InMemoryDataTreeFactory().create(new DataTreeConfiguration.Builder(baseConfig.getTreeType())
                .setMandatoryNodesValidation(baseConfig.isMandatoryNodesValidationEnabled())
                .setUniqueIndexes(baseConfig.isUniqueIndexEnabled())
                .setRootPath(prefix.getRootIdentifier())
                .build());

        // XXX: can we guarantee that the root is present in the schemacontext?
        this.dataTree.setEffectiveModelContext(distributedDataStore.getActorUtils().getSchemaContext());
        this.shardPath = prefix.getRootIdentifier();
        this.childShards = childShards;
    }

    protected void registrationRemoved(final AbstractDOMDataTreeChangeListenerRegistration registration) {
        LOG.debug("Closing registration {}", registration);
    }

    @Override
    public  AbstractDOMDataTreeChangeListenerRegistration
            registerTreeChangeListener(final YangInstanceIdentifier path, final L listener) {
        takeLock();
        try {
            return setupListenerContext(path, listener);
        } finally {
            releaseLock();
        }
    }

    private  AbstractDOMDataTreeChangeListenerRegistration
            setupListenerContext(final YangInstanceIdentifier listenerPath, final L listener) {
        // we need to register the listener registration path based on the shards root
        // we have to strip the shard path from the listener path and then register
        YangInstanceIdentifier strippedIdentifier = listenerPath;
        if (!shardPath.isEmpty()) {
            strippedIdentifier = YangInstanceIdentifier.create(stripShardPath(shardPath, listenerPath));
        }

        final DOMDataTreeListenerWithSubshards subshardListener =
                new DOMDataTreeListenerWithSubshards(strippedIdentifier, listener);
        final AbstractDOMDataTreeChangeListenerRegistration reg =
                setupContextWithoutSubshards(listenerPath, strippedIdentifier, subshardListener);

        for (final ChildShardContext maybeAffected : childShards.values()) {
            if (listenerPath.contains(maybeAffected.getPrefix().getRootIdentifier())) {
                // consumer has initialDataChangeEvent subshard somewhere on lower level
                // register to the notification manager with snapshot and forward child notifications to parent
                LOG.debug("Adding new subshard{{}} to listener at {}", maybeAffected.getPrefix(), listenerPath);
                subshardListener.addSubshard(maybeAffected);
            } else if (maybeAffected.getPrefix().getRootIdentifier().contains(listenerPath)) {
                // bind path is inside subshard
                // TODO can this happen? seems like in ShardedDOMDataTree we are
                // already registering to the lowest shard possible
                throw new UnsupportedOperationException("Listener should be registered directly "
                        + "into initialDataChangeEvent subshard");
            }
        }

        return reg;
    }

    private  AbstractDOMDataTreeChangeListenerRegistration
            setupContextWithoutSubshards(final YangInstanceIdentifier shardLookup,
                                         final YangInstanceIdentifier listenerPath,
                                         final DOMDataTreeListenerWithSubshards listener) {

        LOG.debug("Registering root listener full path: {}, path inside shard: {}", shardLookup, listenerPath);

        // register in the shard tree
        final RegistrationTreeNode> node =
                findNodeFor(listenerPath.getPathArguments());

        // register listener in CDS
        ListenerRegistration listenerReg = distributedDataStore
                .registerProxyListener(shardLookup, listenerPath, listener);

        @SuppressWarnings("unchecked")
        final AbstractDOMDataTreeChangeListenerRegistration registration =
            new AbstractDOMDataTreeChangeListenerRegistration<>((L) listener) {
                @Override
                protected void removeRegistration() {
                    listener.close();
                    DistributedShardChangePublisher.this.removeRegistration(node, this);
                    registrationRemoved(this);
                    listenerReg.close();
                }
            };
        addRegistration(node, registration);

        return registration;
    }

    private static Iterable stripShardPath(final YangInstanceIdentifier shardPath,
                                                         final YangInstanceIdentifier listenerPath) {
        if (shardPath.isEmpty()) {
            return listenerPath.getPathArguments();
        }

        final List listenerPathArgs = new ArrayList<>(listenerPath.getPathArguments());
        final Iterator shardIter = shardPath.getPathArguments().iterator();
        final Iterator listenerIter = listenerPathArgs.iterator();

        while (shardIter.hasNext()) {
            if (shardIter.next().equals(listenerIter.next())) {
                listenerIter.remove();
            } else {
                break;
            }
        }

        return listenerPathArgs;
    }

    synchronized DataTreeCandidate applyChanges(final YangInstanceIdentifier listenerPath,
            final Collection changes) throws DataValidationFailedException {
        final DataTreeModification modification = dataTree.takeSnapshot().newModification();
        for (final DataTreeCandidate change : changes) {
            try {
                DataTreeCandidates.applyToModification(modification, change);
            } catch (SchemaValidationFailedException e) {
                LOG.error("Validation failed", e);
            }
        }

        modification.ready();

        final DataTreeCandidate candidate;

        dataTree.validate(modification);

        // strip nodes we dont need since this listener doesn't have to be registered at the root of the DataTree
        candidate = dataTree.prepare(modification);
        dataTree.commit(candidate);


        DataTreeCandidateNode modifiedChild = candidate.getRootNode();

        for (final PathArgument pathArgument : listenerPath.getPathArguments()) {
            modifiedChild = modifiedChild.getModifiedChild(pathArgument).orElse(null);
        }


        if (modifiedChild == null) {
            modifiedChild = DataTreeCandidateNodes.empty(dataTree.getRootPath().getLastPathArgument());
        }

        return DataTreeCandidates.newDataTreeCandidate(dataTree.getRootPath(), modifiedChild);
    }


    private final class DOMDataTreeListenerWithSubshards implements DOMDataTreeChangeListener {

        private final YangInstanceIdentifier listenerPath;
        private final DOMDataTreeChangeListener delegate;
        private final Map> registrations =
                new ConcurrentHashMap<>();

        @GuardedBy("this")
        private final Collection stashedDataTreeCandidates = new LinkedList<>();

        DOMDataTreeListenerWithSubshards(final YangInstanceIdentifier listenerPath,
                                         final DOMDataTreeChangeListener delegate) {
            this.listenerPath = requireNonNull(listenerPath);
            this.delegate = requireNonNull(delegate);
        }

        @Override
        public synchronized void onDataTreeChanged(final Collection changes) {
            LOG.debug("Received data changed {}", changes);

            if (!stashedDataTreeCandidates.isEmpty()) {
                LOG.debug("Adding stashed subshards' changes {}", stashedDataTreeCandidates);
                changes.addAll(stashedDataTreeCandidates);
                stashedDataTreeCandidates.clear();
            }

            try {
                applyChanges(listenerPath, changes);
            } catch (final DataValidationFailedException e) {
                // TODO should we fail here? What if stashed changes
                // (changes from subshards) got ahead more than one generation
                // from current shard. Than we can fail to apply this changes
                // upon current data tree, but once we get respective changes
                // from current shard, we can apply also changes from
                // subshards.
                //
                // However, we can loose ability to notice and report some
                // errors then. For example, we cannot detect potential lost
                // changes from current shard.
                LOG.error("Validation failed for modification built from changes {}, current data tree: {}",
                        changes, dataTree, e);
                throw new RuntimeException("Notification validation failed", e);
            }

            delegate.onDataTreeChanged(changes);
        }

        synchronized void onDataTreeChanged(final YangInstanceIdentifier pathFromRoot,
                                            final Collection changes) {
            final YangInstanceIdentifier changeId =
                    YangInstanceIdentifier.create(stripShardPath(dataTree.getRootPath(), pathFromRoot));

            final List newCandidates = changes.stream()
                    .map(candidate -> DataTreeCandidates.newDataTreeCandidate(changeId, candidate.getRootNode()))
                    .collect(Collectors.toList());

            try {
                delegate.onDataTreeChanged(Collections.singleton(applyChanges(listenerPath, newCandidates)));
            } catch (final DataValidationFailedException e) {
                // We cannot apply changes from subshard to current data tree.
                // Maybe changes from current shard haven't been applied to
                // data tree yet. Postpone processing of these changes till we
                // receive changes from current shard.
                LOG.debug("Validation for modification built from subshard {} changes {} failed, current data tree {}.",
                        pathFromRoot, changes, dataTree, e);
                stashedDataTreeCandidates.addAll(newCandidates);
            }
        }

        void addSubshard(final ChildShardContext context) {
            checkState(context.getShard() instanceof DOMStoreTreeChangePublisher,
                    "All subshards that are initialDataChangeEvent part of ListenerContext need to be listenable");

            final DOMStoreTreeChangePublisher listenableShard = (DOMStoreTreeChangePublisher) context.getShard();
            // since this is going into subshard we want to listen for ALL changes in the subshard
            registrations.put(context.getPrefix().getRootIdentifier(),
                    listenableShard.registerTreeChangeListener(
                            context.getPrefix().getRootIdentifier(), changes -> onDataTreeChanged(
                                    context.getPrefix().getRootIdentifier(), changes)));
        }

        void close() {
            for (final ListenerRegistration registration : registrations.values()) {
                registration.close();
            }
            registrations.clear();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy