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

oracle.kv.impl.admin.TopologyStore Maven / Gradle / Ivy

Go to download

NoSQL Database Server - supplies build and runtime support for the server (store) side of the Oracle NoSQL Database.

There is a newer version: 18.3.10
Show newest version
/*-
 * Copyright (C) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This file was distributed by Oracle as part of a version of Oracle NoSQL
 * Database made available at:
 *
 * http://www.oracle.com/technetwork/database/database-technologies/nosqldb/downloads/index.html
 *
 * Please see the LICENSE file included in the top-level directory of the
 * appropriate version of Oracle NoSQL Database for a copy of the license and
 * additional information.
 */

package oracle.kv.impl.admin;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.sleepycat.bind.tuple.StringBinding;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.CursorConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.Environment;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.Transaction;
import com.sleepycat.persist.EntityCursor;
import com.sleepycat.persist.EntityStore;
import com.sleepycat.persist.PrimaryIndex;

import oracle.kv.impl.admin.AdminDatabase.DB_TYPE;
import oracle.kv.impl.admin.AdminDatabase.LongKeyDatabase;
import oracle.kv.impl.admin.AdminStores.AdminStore;
import oracle.kv.impl.admin.plan.DeploymentInfo;
import oracle.kv.impl.admin.topo.RealizedTopology;
import oracle.kv.impl.admin.topo.TopologyCandidate;
import oracle.kv.impl.topo.Topology;
import oracle.kv.impl.topo.TopologyHolder;
import oracle.kv.impl.util.SerializationUtil;

/**
 * Topologies are stored in in two forms:
 *
 * 1)a collection of TopologyCandidates, which are named topologies created by
 *   the user.These are in a PrimaryIndex<String, TopologyCandidate>,
 *   where the key is the candidate name.
 *
 * 2)a bounded collection of historical RealizedTopology(s), which keeps track
 *   of all topologies that have deployed by administrative commands. This is
 *   in a PrimaryIndex<Long, RealizedTopology>, where the key is a
 *   timestamp.  Realized topologies optionally refer to their originating
 *   candidate.
 *
 * The indices used are:
 *
 * PrimaryIndex<String, TopologyCandidate>: list of candidates:
 * PrimaryIndex<Long, RealizedTopology>: Realized topologies, ordered by
 *                            timestamp. The current topology is the latest one.
 *
 * The life cycle of a topology is this:
 *
 * - The user creates a named topology candidate using the "topology create
 * name ...". The resulting topology may be deployed, or further
 * manipulated with one of the topology commands that modify layouts, such as
 * topology redistribute, topology rebalance, etc.
 *
 * - This candidate is stored in the candidates collection, and can be viewed,
 * listed, or deleted.
 *
 * - When the user has a topology she likes, she can choose to deploy it with
 * the plan deploy-topology name command. Deployments are composed of
 * multiple tasks, and the realized topology is saved periodically as the
 * deployment executes. Since plans can be interrupted, canceled, or incur
 * errors, the realized topology may never reach the precise state of the named
 * topology candidate. The current topology of the store is saved
 * as the last record of the historical topology collection.
 *
 * The historical collection is an audit trail of deployed topologies. It is
 * meant as an audit trail/debugging aid. There is one record per invocation of
 * the deploy-topology command, which preserves the final version of the
 * topology as generated by that command. The historical collection is bounded
 * by time, and versions of the topology that are older than the expiry date
 * are pruned. In a fairly stable store, there may be few deploy-topology
 * commands, and a minimum number of records are kept.
 */
public abstract class TopologyStore extends AdminStore {

    public static TopologyStore getReadOnlyInstance(Logger logger,
                                                    Environment env) {
        return new TopologyDatabaseStore(logger, env, Integer.MAX_VALUE, true);
    }

    static TopologyStore getStoreByVersion(int schemaVersion,
                                           Admin admin, EntityStore eStore) {
        if (schemaVersion < AdminSchemaVersion.SCHEMA_VERSION_5) {
            assert eStore != null;
            return new TopologyDPLStore(admin.getLogger(), eStore);
        }
        return new TopologyDatabaseStore(admin.getLogger(), admin.getEnv(),
                         admin.getParams().getAdminParams().getMaxTopoChanges(),
                                         false /* read only */);
    }

    /**
     * The maximum number of topo changes that are to be retained in realized
     * topologies that are being saved.
     */
    private final int maxTopoChanges;

    /**
     * Start time of the last realized topology from the historical collection.
     * Since the start time is the primary key for the RealizedTopology
     * instance, it's important that the timestamp for new realizedTopologies
     * always ascend. If there is some clock skew between nodes in the Admin
     * replication group, there is the possibility that the start millis is <=
     * to the last saved RealizedTopology. Cache the last start time to use wen
     * generating a unique timestamp.
     */
    private long lastStartMillis = 0;

    private TopologyStore(Logger logger, int maxTopoChanges) {
        super(logger);
        this.maxTopoChanges = maxTopoChanges;
    }

    /**
     * Returns the list of topologies in the history.
     *
     * @param concise if true generate a concise string for each topology
     * @return the list of topologies in the history
     */
    public List displayHistory(boolean concise) {
        return displayHistory(null, concise);
    }

    /**
     * Gets the current realized topology from the historical store using the
     * specified transaction and return a copy as a new topology instance.
     *
     * @param txn a transaction
     * @return the current realized topology
     */
    Topology getTopology(Transaction txn) {
        final RealizedTopology rt = getCurrentRealizedTopology(txn);
        return (rt == null) ? null : rt.getTopology();
    }

    /**
     * Puts the specified realized topology into the store using the specified
     * transaction.
     *
     * @param txn a transaction
     * @param rt the realized topology to store
     */
    abstract void putTopology(Transaction txn, RealizedTopology rt);

    /**
     * Returns true if the specified candidate exists in the store.
     */
    boolean candidateExists(Transaction txn, String candidateName) {
        return getCandidate(txn, candidateName) != null;
    }

    /**
     * Returns a list of names of all candidates. If there are no named
     * topologies, an empty list is returned.
     *
     * @param txn a transaction
     * @return the list of candidate names
     */
    public abstract List getCandidateNames(Transaction txn);

    /**
     * Gets the specified topology candidate from the store using the specified
     * transaction.
     *
     * @param txn a transaction
     * @param candidateName a candidate name
     * @return the candidate or null
     */
    abstract TopologyCandidate getCandidate(Transaction txn,
                                            String candidateName);

    /**
     * Puts the specified topology candidate into the store using the specified
     * transaction.
     *
     * @param txn a transaction
     * @param candidate the candidate to store
     */
    abstract void putCandidate(Transaction txn, TopologyCandidate candidate);

    /**
     * Deletes the specified topology candidate from the store using the
     * specified transaction.
     *
     * @param txn a transaction
     * @param candidateName a candidate name
     */
    abstract void deleteCandidate(Transaction txn, String candidateName);

    /**
     * Validates the specified proposed start time.
     * Since the start time is the primary key for the historical collection,
     * ensure that the start time for any new realized topology is greater than
     * the start time recorded for the current topology. Due to clock skew in
     * the HA rep group, conceivably the start time could fail to advance if
     * there is admin rep node failover.
     *
     * @param proposedStartTime
     * @return the start time to use
     */
    synchronized long validateStartTime(long proposedStartTime) {
        /* The proposed start time is greater, as expected. */
        if (proposedStartTime > lastStartMillis) {
            lastStartMillis = proposedStartTime;
            return proposedStartTime;
        }

        /*
         * For some reason, the proposed start time has receded. Manufacture
         * a new start time. This should be fairly infrequent.
         */
        lastStartMillis++;
        logger.log(Level.INFO,
                   "TopologyStore: proposedStartTime of {0} is less than " +
                   "cached time, so use topoStore lastStartTime of {1}",
                   new Object[]{proposedStartTime, lastStartMillis});
        return lastStartMillis;
    }

    /**
     * Refreshes the cached last start time stored in store for validating
     * new realized topologies.
     */
    void initCachedStartTime(Transaction txn) {
        final RealizedTopology rt = getCurrentRealizedTopology(txn);
        lastStartMillis = (rt == null) ? 0 : rt.getStartTime();
    }

    abstract List displayHistory(Transaction txn, boolean concise);

    protected void pruneChanges(Topology topology) {
        topology.pruneChanges(Integer.MAX_VALUE, maxTopoChanges);
    }

    protected abstract
            RealizedTopology getCurrentRealizedTopology(Transaction txn);

    private static class TopologyDatabaseStore extends TopologyStore {
        private final LongKeyDatabase historyDb;
        private final AdminDatabase candidateDb;

        TopologyDatabaseStore(Logger logger, Environment env,
                              int maxTopoChanges,  boolean readOnly) {
            super(logger, maxTopoChanges);
            historyDb = new LongKeyDatabase<>(DB_TYPE.TOPOLOGY_HISTORY,
                                              logger, env, readOnly);
            candidateDb = new AdminDatabase(
                                            DB_TYPE.TOPOLOGY_CANDIDATE, logger,
                                            env, readOnly) {
                @Override
                protected DatabaseEntry keyToEntry(String key) {
                    final DatabaseEntry keyEntry = new DatabaseEntry();
                    StringBinding.stringToEntry(key, keyEntry);
                    return keyEntry;
                }};
        }

        @Override
        public void close() {
            historyDb.close();
            candidateDb.close();
        }

        @Override
        void putTopology(Transaction txn, RealizedTopology rt) {
            pruneChanges(rt.getTopology());
            historyDb.put(txn, rt.getStartTime(), rt, false);
        }

        @Override
        public List getCandidateNames(Transaction txn) {
            final List names = new ArrayList<>();
            final DatabaseEntry keyEntry = new DatabaseEntry();
            final DatabaseEntry valueEntry = new DatabaseEntry();
            valueEntry.setPartial(0, 0, true);
            try (final Cursor cursor = candidateDb.openCursor(txn)) {
                while (true) {
                    final OperationStatus status =
                                            cursor.getNext(keyEntry,
                                                           valueEntry,
                                                           LockMode.DEFAULT);
                    if (status != OperationStatus.SUCCESS) {
                        return names;
                    }
                    names.add(StringBinding.entryToString(keyEntry));
                }
            }
        }

        @Override
        TopologyCandidate getCandidate(Transaction txn, String candidateName) {
            return candidateDb.get(txn, candidateName, LockMode.READ_COMMITTED,
                                   TopologyCandidate.class);
        }

        @Override
        void putCandidate(Transaction txn, TopologyCandidate candidate) {
            pruneChanges(candidate.getTopology());
            candidateDb.put(txn, candidate.getName(), candidate, false);
        }

        @Override
        void deleteCandidate(Transaction txn, String candidateName) {
            candidateDb.delete(txn, candidateName);
        }

        @Override
        List displayHistory(Transaction txn, boolean concise) {
            final List display = new ArrayList<>();
            final DatabaseEntry keyEntry = new DatabaseEntry();
            final DatabaseEntry valueEntry = new DatabaseEntry();
            try (final Cursor cursor = historyDb.openCursor(txn)) {
                while (true) {
                    final OperationStatus status =
                                            cursor.getNext(keyEntry,
                                                           valueEntry,
                                                           LockMode.DEFAULT);
                    if (status != OperationStatus.SUCCESS) {
                        return display;
                    }
                    final RealizedTopology rt =
                            SerializationUtil.getObject(valueEntry.getData(),
                                                        RealizedTopology.class);
                    display.add(rt.display(concise));
                }
            }
        }

        @Override
        protected RealizedTopology getCurrentRealizedTopology(Transaction txn) {
            try (final Cursor cursor = historyDb.openCursor(txn)) {
                final DatabaseEntry keyEntry = new DatabaseEntry();
                final DatabaseEntry valueEntry = new DatabaseEntry();
                final OperationStatus status = cursor.getLast(keyEntry,
                                                              valueEntry,
                                                              LockMode.DEFAULT);
                return (status == OperationStatus.SUCCESS) ?
                           SerializationUtil.getObject(valueEntry.getData(),
                                                       RealizedTopology.class) :
                           null;
            }
        }
    }

    private static class TopologyDPLStore extends TopologyStore {
        private final EntityStore eStore;

        private TopologyDPLStore(Logger logger, EntityStore eStore) {
            super(logger, Integer.MAX_VALUE);
            this.eStore = eStore;
        }

        @Override
        void putTopology(Transaction txn, RealizedTopology rt) {
            readOnly();
        }

        @Override
        public List getCandidateNames(Transaction txn) {
            final PrimaryIndex candidates =
                                                            openCandidates();
            try (EntityCursor nameCursor =
                            candidates.keys(txn, CursorConfig.READ_COMMITTED)) {
                final List names = new ArrayList<>();
                final Iterator iter = nameCursor.iterator();
                while (iter.hasNext()) {
                    names.add(iter.next());
                }
                return names;
            }
        }

        @Override
        TopologyCandidate getCandidate(Transaction txn, String candidateName) {
            final PrimaryIndex candidates =
                                                            openCandidates();
            return candidates.get(txn, candidateName, LockMode.DEFAULT);
        }

        @Override
        void putCandidate(Transaction txn, TopologyCandidate candidate) {
            readOnly();
        }

        @Override
        void deleteCandidate(Transaction txn, String candidateName) {
            readOnly();
        }

        @Override
        List displayHistory(Transaction txn, boolean concise) {
            final List display = new ArrayList<>();
            final PrimaryIndex history = openHistory();
            try (EntityCursor cursor =
                           history.entities(txn, CursorConfig.READ_COMMITTED)) {
                for (RealizedTopology dt: cursor) {
                    display.add(dt.display(concise));
                }
            }
            return display;
        }

        @Override
        protected RealizedTopology getCurrentRealizedTopology(Transaction txn) {
            final PrimaryIndex history = openHistory();

            try (final EntityCursor cursor =
                           history.entities(txn, CursorConfig.READ_COMMITTED)) {
                final RealizedTopology lastRT = cursor.last();
                return (lastRT == null) ? null : lastRT;
            }
        }

        /**
         * Open the primary index for the historical collection of realized
         * topologies.
         */
        private PrimaryIndex openHistory() {
            return eStore.getPrimaryIndex(Long.class, RealizedTopology.class);
        }

        /**
         * Get a handle onto the candidate primary index. We do not cache a
         * reference to the index in the class so that we can transparently
         * handle the case where there is an admin failover. In those cases,
         * the Admin will close the previous entity store handle and will
         * re-open it. Since this is a low frequency access, it's okay to
         * re-open the index each time, and avoid having to update cached
         * index handles when the entity store reference is updated.
         */
        private PrimaryIndex openCandidates() {
            return eStore.getPrimaryIndex(String.class,
                                          TopologyCandidate.class);
        }

        @Override
        protected void convertTo(int existingVersion, AdminStore newStore,
                                 Transaction txn) {
            final TopologyStore newTopoStore = (TopologyStore)newStore;

            if (existingVersion < AdminSchemaVersion.SCHEMA_VERSION_3) {
                upgradePreV3Topology(newTopoStore, txn);
                return;
            }

            final PrimaryIndex candidates =
                                                            openCandidates();
            try (EntityCursor cursor =
                        candidates.entities(txn, CursorConfig.READ_COMMITTED)) {
                for (TopologyCandidate tc : cursor) {
                    newTopoStore.putCandidate(txn, tc);
                }
            }
            final PrimaryIndex history = openHistory();
            try (EntityCursor cursor =
                        history.entities(txn, CursorConfig.READ_COMMITTED)) {
                for (RealizedTopology rt : cursor) {
                    newTopoStore.putTopology(txn, rt);
                }
            }
        }

        private void upgradePreV3Topology(TopologyStore newTopoStore,
                                          Transaction txn) {
            final PrimaryIndex pi =
                    eStore.getPrimaryIndex(String.class, TopologyHolder.class);
            final TopologyHolder holder =
                  pi.get(txn, TopologyHolder.getKey(), LockMode.READ_COMMITTED);

            if (holder == null) {
                return;
            }
            final Topology oldTopo = holder.getTopology();
            if (oldTopo.getVersion() != 0) {

                /*
                 * Something is wrong.  We are trying to upgrade the Admin
                 * database to V3 from an earier version.  We know earlier
                 * versions of the database contain only Topologies with
                 * Topology version 0.
                 */
                 throw new IllegalStateException
                     ("Unexpected Topology version " + oldTopo.getVersion() +
                      " found in legacy Admin database.");
            }

            if (!oldTopo.upgrade()) {

                /*
                 * If Topology.upgrade returns false, the required upgrade
                 * failed. In all likelihood, the problem is that the replicas
                 * are not evenly distributed among multiple data centers, which
                 * prevents Topology.upgrade from choosing a repfactor for the
                 * datacenters.
                 *
                 * When this happens, the topology version number is not set to
                 * the current version, and future attempts to rebalance or to
                 * perform elasticity operations will fail because the
                 * datacenters will have a repFactor of zero.  The user has no
                 * choice but to dump and restore.
                 */
                logger.severe
                  ("Unable to upgrade the Topology from its earlier version. " +
                   "The store will continue to run with this Topology, " +
                   "But certain features that are new in R2, such " +
                   "as dynamic master rebalancing, and elasticity " +
                   "operations, will not be available. " +
                   "To enable these features will require dumping the " +
                   "database contents and restoring them to an R2 topology, " +
                   "using the snapshot and load commands.  Please see the " +
                   "NoSQL Database Administrator's Guide, chap. 7, " +
                   "for more information");
            }

            final DeploymentInfo info =
                    DeploymentInfo.makeStartupDeploymentInfo();
            final RealizedTopology rt = new RealizedTopology(oldTopo, info);
            rt.setStartTime();
            newTopoStore.putTopology(txn, rt);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy