oracle.kv.impl.admin.TopologyStore Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of oracle-nosql-server Show documentation
Show all versions of oracle-nosql-server Show documentation
NoSQL Database Server - supplies build and runtime support for the server (store) side of the Oracle NoSQL Database.
/*-
* 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