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

oracle.kv.impl.topo.Topology 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.topo;

import static oracle.kv.impl.util.SerializationUtil.readPackedInt;
import static oracle.kv.impl.util.SerializationUtil.readString;
import static oracle.kv.impl.util.SerializationUtil.writePackedInt;
import static oracle.kv.impl.util.SerializationUtil.writeString;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import oracle.kv.impl.api.RequestHandler;
import oracle.kv.impl.api.TopologyInfo;
import oracle.kv.impl.fault.UnknownVersionException;
import oracle.kv.impl.metadata.Metadata;
import oracle.kv.impl.topo.ResourceId.ResourceType;
import oracle.kv.impl.topo.change.TopologyChange;
import oracle.kv.impl.topo.change.TopologyChangeTracker;
import oracle.kv.impl.util.FastExternalizable;
import oracle.kv.impl.util.SerializationUtil;

import com.sleepycat.persist.model.Persistent;

import org.codehaus.jackson.node.ObjectNode;

/**
 * Topology describes the general logical layout of the KVS and its
 * relationship to the physical elements (SNs) on which it has been mapped.
 * 

* The logical layout is defined in terms of the partitions, the assignment of * the partitions to replication groups, the RNs that comprise each replication * group and the assignment of RNs to SNs. *

* Topology is created and maintained by the Admin; the authoritative instance * of the Topology is stored and maintained by the Admin in the AdminDB. Each * RN has a copy of the Topology that is stored in its local database and * updated lazily in response to changes originating at the Admin. Changes made * by the Admin are propagated around the KV Store via two mechanisms: *

    *
  1. They are piggy-backed on top of responses to KVS requests.
  2. *
  3. Some commands explicitly broadcast a new topology
  4. *
*

* KV clients also maintain a copy of the Topology. They acquire an initial * copy of the Topology from the RN they contact when the KVS handle is first * created. Since they do not participate in the broadcast protocol, they * acquire updates primarily via the response mechanism. The updates can then * be applied to update the local copy of the Topology. *

* The Topology can be modified via its add/update/remove methods. Each such * operation is noted in the list of changes returned by {@link * #getChanges(int)} * * @see #writeFastExternal FastExternalizable format */ @Persistent(version=1) public class Topology implements FastExternalizable, Metadata, Serializable { private static final long serialVersionUID = 1L; /* The version associated with this topology instance. */ private int version; /** * The compact id that uniquely identifies the Topology as belonging to * this store. It's simply a millisecond time stamp that's assigned when a * Topology is first created. It's used to guard against clients that * incorrectly communicate with the wrong store. * * For compatibility with earlier releases, a value of 0 is treated as a * wildcard value from pre 2.0 releases. * * @since 2.0 */ private long id = 0; private String kvStoreName; /* The mapping components comprising the Topology. */ private PartitionMap partitionMap; private RepGroupMap repGroupMap; private StorageNodeMap storageNodeMap; private DatacenterMap datacenterMap; /* Used to dispatch map-specific operations. */ private Map>> typeToComponentMaps; private TopologyChangeTracker changeTracker; /* * Signature of this topology instance. It is made transient to be * compatible with DPL store, but will be serialized/de-serialized. */ private transient byte[] signature; /* The Topology id associated with an empty Topology. */ public static final int EMPTY_TOPOLOGY_ID = -1; /* * The Topology id that is used to bypass topology checks for r1 * compatibility. */ public static final int NOCHECK_TOPOLOGY_ID = 0; /* * The current topology version number. New Topology instances are created * with this version number. */ public static final int CURRENT_VERSION = 1; /** * Creates a new named empty KV Store topology. * * @param kvStoreName the name associated with the KV Store */ public Topology(String kvStoreName) { this(kvStoreName, System.currentTimeMillis()); } /** * Creates a named empty KV Store topology, but with a specific topology id. */ public Topology(String kvStoreName, long topoId) { version = CURRENT_VERSION; id = topoId; this.kvStoreName = kvStoreName; partitionMap = new PartitionMap(this); repGroupMap = new RepGroupMap(this); storageNodeMap = new StorageNodeMap(this); datacenterMap = new DatacenterMap(this); changeTracker = new TopologyChangeTracker(this); createTypeToComponentMaps(); } private void createTypeToComponentMaps() { typeToComponentMaps = new HashMap>>(); for (ComponentMap m : getAllComponentMaps()) { typeToComponentMaps.put(m.getResourceType(), m); } } @SuppressWarnings("unused") private Topology() { } public Topology(DataInput in, short serialVersion) throws IOException { version = readPackedInt(in); id = in.readLong(); kvStoreName = readString(in, serialVersion); partitionMap = new PartitionMap(this, in, serialVersion); repGroupMap = new RepGroupMap(this, in, serialVersion); storageNodeMap = new StorageNodeMap(this, in, serialVersion); datacenterMap = new DatacenterMap(this, in, serialVersion); changeTracker = new TopologyChangeTracker(this); createTypeToComponentMaps(); } /** * Writes this object to the output stream. Format: *

    *
  1. ({@link SerializationUtil#writePackedInt packedInt}) {@link * #getVersion version} *
  2. ({@link DataOutput#writeLong long}) {@link #getId id} *
  3. ({@link SerializationUtil#writeString String}) {@link * #getKVStoreName kvStoreName} *
  4. ({@link PartitionMap}) {@link #getPartitionMap partitionMap} *
  5. ({@link RepGroupMap}) {@link #getRepGroupMap repGroupMap} *
  6. ({@link StorageNodeMap}) {@link #getStorageNodeMap storageNodeMap} *
  7. ({@link DatacenterMap}) {@link #getDatacenterMap datacenterMap} *
*/ @Override public void writeFastExternal(DataOutput out, short serialVersion) throws IOException { writePackedInt(out, version); out.writeLong(id); writeString(out, serialVersion, kvStoreName); partitionMap.writeFastExternal(out, serialVersion); repGroupMap.writeFastExternal(out, serialVersion); storageNodeMap.writeFastExternal(out, serialVersion); datacenterMap.writeFastExternal(out, serialVersion); } @Override public MetadataType getType() { return MetadataType.TOPOLOGY; } /** * Returns the version associated with the topology. This version number * can be used, for example, during java deserialization to check for * version compatibility. Note that it's deliberately distinct from the * Entity.version() number associated with it. The two version numbers will * often be updated together, but there may be Topology changes where we * need to update just one version and not the other. * * @return the version */ public int getVersion() { return version; } /* For unit tests */ public void setVersion(int version) { this.version = version; } public long getId() { return id; } /** * For test use only. */ public void setId(long id) { this.id = id; } /* * Returns the top level maps that comprise the KVStore */ ComponentMap[] getAllComponentMaps() { return new ComponentMap[] { partitionMap, repGroupMap, storageNodeMap, datacenterMap}; } /** * Returns the name associated with the KV Store. */ public String getKVStoreName() { return kvStoreName; } /** * Returns the topology component associated with the resourceId, or null * if the component does not exist as part of the Topology. */ public Component get(ResourceId resourceId) { return resourceId.getComponent(this); } /* Overloaded versions of the above get() method. */ public Datacenter get(DatacenterId datacenterId) { return datacenterMap.get(datacenterId); } public StorageNode get(StorageNodeId storageNodeId) { return storageNodeMap.get(storageNodeId); } public RepGroup get(RepGroupId repGroupId) { return repGroupMap.get(repGroupId); } public Partition get(PartitionId partitionMapId) { return partitionMap.get(partitionMapId); } public RepNode get(RepNodeId repNodeId) { RepGroup rg = repGroupMap.get(new RepGroupId(repNodeId.getGroupId())); return (rg == null) ? null : rg.get(repNodeId); } public ArbNode get(ArbNodeId arbNodeId) { RepGroup rg = repGroupMap.get(new RepGroupId(arbNodeId.getGroupId())); return (rg == null) ? null : rg.get(arbNodeId); } /** * Returns the datacenter associated with the SN */ public Datacenter getDatacenter(StorageNodeId storageNodeId) { return get(getDatacenterId(storageNodeId)); } /** * Returns the datacenter ID associated with the SN. */ public DatacenterId getDatacenterId(StorageNodeId storageNodeId) { return get(storageNodeId).getDatacenterId(); } /** * Returns the datacenter associated with the RN */ public Datacenter getDatacenter(RepNodeId repNodeId) { return get(getDatacenterId(repNodeId)); } /** * Returns the datacenter ID associated with the RN */ public DatacenterId getDatacenterId(RepNodeId repNodeId) { return getDatacenterId(get(repNodeId)); } /** * Returns the datacenter ID associated with the RN */ public DatacenterId getDatacenterId(RepNode repNode) { return getDatacenterId(repNode.getStorageNodeId()); } /** * Returns the datacenter associated with the AN */ public DatacenterId getDatacenterId(ArbNodeId arbNodeId) { return getDatacenterId(get(arbNodeId).getStorageNodeId()); } /** * Returns the partition associated with the key. This is the basis for * request dispatching. */ public PartitionId getPartitionId(byte[] keyBytes) { if (partitionMap.size() == 0) { throw new IllegalArgumentException ("Store is not yet configured and deployed, and cannot accept " + "data"); } return partitionMap.getPartitionId(keyBytes); } /** * Returns the group associated with the partition. This is the basis for * request dispatching. If the partition is not present null is returned. */ public RepGroupId getRepGroupId(PartitionId partitionId) { return partitionMap.getRepGroupId(partitionId); } /** * Return the set of all RepGroupIds contained in this topology. */ public Set getRepGroupIds() { Set rgIdSet = new HashSet(); for (RepGroup rg : repGroupMap.getAll()) { rgIdSet.add(rg.getResourceId()); } return rgIdSet; } /** * Returns the partition map */ public PartitionMap getPartitionMap() { return partitionMap; } /** * Returns the rep group map */ public RepGroupMap getRepGroupMap() { return repGroupMap; } /** * Returns the StorageNodeMap */ public StorageNodeMap getStorageNodeMap() { return storageNodeMap; } /** * Return the list of all RepGroupIds contained in this topology sorted * by RepGroupId. */ public List getSortedRepGroupIds() { List rgIdList = new ArrayList(); for (RepGroup rg : repGroupMap.getAll()) { rgIdList.add(rg.getResourceId()); } Collections.sort(rgIdList); return rgIdList; } /** * Returns all RepNodes sorted by id. The sort is by RepGroup id, then by * node number, lowest first. */ public List getSortedRepNodes() { List srn = new ArrayList(); for (RepGroup rg: repGroupMap.getAll()) { for (RepNode rn: rg.getRepNodes()) { srn.add(rn); } } Collections.sort(srn); return srn; } /** * Returns all RepNodeIds for a given shard, sorted by id. */ public List getSortedRepNodeIds(RepGroupId rgId) { List srn = new ArrayList(); for (RepNode rn: repGroupMap.get(rgId).getRepNodes()) { srn.add(rn.getResourceId()); } Collections.sort(srn); return srn; } /** * Returns all Storage Nodes, sorted by id. */ public List getSortedStorageNodes() { List sns = new ArrayList(storageNodeMap.getAll()); Collections.sort(sns); return sns; } public List getStorageNodeIds() { List snIds = new ArrayList(); for (StorageNode sn : storageNodeMap.getAll()) { snIds.add(sn.getResourceId()); } return snIds; } public List getSortedStorageNodeIds() { final List snIds = getStorageNodeIds(); Collections.sort(snIds); return snIds; } /** * Returns a set of RepNodeIds for all RepNodes in the Topology that are * deployed to the given datacenter; or all RepNodes in the Topology if * null is input. */ public Set getRepNodeIds(DatacenterId dcid) { final Set allRNIds = new HashSet(); for (RepGroup rg : repGroupMap.getAll()) { for (RepNode rn : rg.getRepNodes()) { final RepNodeId rnid = rn.getResourceId(); if (dcid == null) { /* Return all RepNodeIds from all datacenters. */ allRNIds.add(rnid); } else { if (dcid.equals(getDatacenter(rnid).getResourceId())) { /* Return RepNodeId only if it belongs to the dc. */ allRNIds.add(rnid); } } } } return allRNIds; } /** * Returns a set of StorageNodes for all the StorageNodes in the Topology * that belong to a given datacenter; or all the StorageNodes in the * Topology if null is input. */ public Set getStorageNodes(DatacenterId dcid) { final Set allStorageNodes = new HashSet(); for (StorageNode sn : storageNodeMap.getAll()) { if (dcid == null) { /* Return all StorageNodes from all datacenters */ allStorageNodes.add(sn); } else { if (dcid.equals(sn.getDatacenterId())) { /* Return StorageNode only if it belongs to the dc. */ allStorageNodes.add(sn); } } } return allStorageNodes; } /** * Returns a set of RepNodeIds for all RepNodes in the Topology. */ public Set getRepNodeIds() { return getRepNodeIds(null); } /** * Returns the set of RepNodeIds for all RepNodes hosted on this SN */ public Set getHostedRepNodeIds(StorageNodeId snId) { Set snRNIds = new HashSet(); for (RepGroup rg : repGroupMap.getAll()) { for (RepNode rn : rg.getRepNodes()) { if (rn.getStorageNodeId().equals(snId)) { snRNIds.add(rn.getResourceId()); } } } return snRNIds; } /** * Returns a set of ArbNodeIds for all ArbNodes in the Topology. */ public Set getArbNodeIds() { return getArbNodeIds((DatacenterId)null); } /** * Returns a set of ArbNodeIds for all ArbNodes in the Topology that are * deployed to the given datacenter; or all ArbNodes in the Topology if * null is input. */ public Set getArbNodeIds(DatacenterId dcid) { final Set allARBIds = new HashSet(); for (RepGroup rg : repGroupMap.getAll()) { for (ArbNode arb : rg.getArbNodes()) { final ArbNodeId arbid = arb.getResourceId(); if (dcid == null) { /* Return all RepNodeIds from all datacenters. */ allARBIds.add(arbid); } else { if (dcid.equals(getDatacenterId(arbid))) { /* Return RepNodeId only if it belongs to the dc. */ allARBIds.add(arbid); } } } } return allARBIds; } /** * Returns the set of ArbNodeIds for all ArbNodes hosted on this SN */ public Set getHostedArbNodeIds(StorageNodeId snId) { Set snARBIds = new HashSet(); for (RepGroup rg : repGroupMap.getAll()) { for (ArbNode arb : rg.getArbNodes()) { if (arb.getStorageNodeId().equals(snId)) { snARBIds.add(arb.getResourceId()); } } } return snARBIds; } /** * Returns all ArbNodes sorted by id. The sort is by ArbGroup id, then by * node number, lowest first. */ public List getSortedArbNodes() { List san = new ArrayList(); for (RepGroup rg: repGroupMap.getAll()) { for (ArbNode an: rg.getArbNodes()) { san.add(an); } } Collections.sort(san); return san; } /** * Returns all ArbNodeIds for a given shard, sorted by id. */ public List getSortedArbNodeIds(RepGroupId rgId) { List san = new ArrayList(); for (ArbNode an: repGroupMap.get(rgId).getArbNodes()) { san.add(an.getResourceId()); } Collections.sort(san); return san; } /** * Returns the DatacenterMap */ public DatacenterMap getDatacenterMap() { return datacenterMap; } /** * Returns datacenters sorted by id. */ public List getSortedDatacenters() { List sdc = new ArrayList(datacenterMap.getAll()); Collections.sort(sdc, new Comparator() { @Override public int compare(Datacenter o1, Datacenter o2) { DatacenterId id1 = o1.getResourceId(); DatacenterId id2 = o2.getResourceId(); return id1.getDatacenterId() - id2.getDatacenterId(); }}); return sdc; } /** * Returns the datacenter object for the given datacenterName. Returns null * if the datacenter with the specified datacenterName does not exist. * * @param datacenterName the name of the datacenter (zone) * @return the Datacenter object for the given datacenterName. */ public Datacenter getDatacenter(String datacenterName) { for (final Datacenter datacenter : datacenterMap.getAll()) { if (datacenter.getName().equals(datacenterName)) { return datacenter; } } return null; } public TopologyChangeTracker getChangeTracker() { return changeTracker; } /** * Returns the current change sequence number associated with Topology. * @return the current sequence number */ @Override public int getSequenceNumber() { return changeTracker.getSeqNum(); } /** * Creates a copy of this Topology object. * * @return the new Topology instance */ public Topology getCopy() { try { ByteArrayOutputStream bos = new ByteArrayOutputStream() ; ObjectOutputStream oos = new ObjectOutputStream(bos) ; oos.writeObject(this); oos.close(); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()) ; ObjectInputStream ois = new ObjectInputStream(bis); return (Topology) ois.readObject(); } catch (IOException ioe) { throw new IllegalStateException("Unexpected exception", ioe); } catch (ClassNotFoundException e) { throw new IllegalStateException("Unexpected exception", e); } } /** * The sequence of changes to be applied to this instance of the Topology * to make it more current. Since changes must be applied in strict * sequence, the following condition must hold: *
     * changes.first.getSequenceNumber() <= (Topology.getSequenceNumber() +
     * 1)
     * 
* * Applying the changes effectively move the sequence number forward so * that *
     * Topology.getSequenceNumber() == changes.last().getSequenceNumber()
     * 
*

* The implementation is prepared to deal with multiple concurrent requests * to update the topology, by serializing such requests. * * @param changes the sequence of changes to be applied to update the * Topology in ascending change sequence order * * @return true if the Topology was in fact updated */ public boolean apply(List changes) { if (changes == null) { return false; } if (changes.isEmpty()) { return false; } /* * The change list may overlap the current topology, but there should * be no gap. If there is a gap, the changes cannot be safely applied. */ if (changes.get(0).getSequenceNumber() > (getSequenceNumber() + 1)) { throw new IllegalStateException ("Unexpected gap in topology sequence. Current sequence=" + getSequenceNumber() + ", first change =" + changes.get(0).getSequenceNumber()); } int changeCount = 0; for (TopologyChange change : changes) { if (change.getSequenceNumber() <= getSequenceNumber()) { /* Skip the changes we already have. */ continue; } changeCount++; final ResourceType rtype = change.getResourceId().getType(); if (rtype == ResourceType.REP_NODE) { RepNodeId rnId = (RepNodeId)change.getResourceId(); RepGroup rg = repGroupMap.get(new RepGroupId(rnId.getGroupId())); rg.apply(change); } else if (rtype == ResourceType.ARB_NODE) { ArbNodeId anid = (ArbNodeId)change.getResourceId(); RepGroup rg = repGroupMap.get(new RepGroupId(anid.getGroupId())); rg.applyArbChange(change); } else { typeToComponentMaps.get(rtype).apply(change); } } return changeCount > 0; } @Override public TopologyInfo getChangeInfo(int startSeqNum) { return new TopologyInfo(this, getChanges(startSeqNum)); } /** * Returns the dense list of topology changes starting with * startSequenceNumber. This method is typically used by the * {@link RequestHandler} to return changes as part of a response. * * @param startSeqNum the inclusive start of the sequence of * changes to be returned * * @return the list of changes starting with startSequenceNumber and ending * with getSequenceNumber(). Return null if startSequenceNumber > * getSequenceNumber() or if the topology does not contain changes at * startSeqNum because they have been discarded. * * @see #discardChanges */ public List getChanges(int startSeqNum) { return changeTracker.getChanges(startSeqNum); } /** * Discards the tracked changes associated with this Topology. All changes * older than or equal to startSeqNum are discarded. Any * subsequent {@link #getChanges} calls to obtain changes * startSeqNum will result in IllegalArgumentException being * thrown. */ public void discardChanges(int startSeqNum) { changeTracker.discardChanges(startSeqNum); } @Override public Topology pruneChanges(int limitSeqNum, int maxTopoChanges) { final int firstChangeSeqNum = getChangeTracker().getFirstChangeSeqNum(); if (firstChangeSeqNum == -1) { /* No changes to prune. */ return this; } final int firstRetainedChangeSeqNum = Math.min(getSequenceNumber() - maxTopoChanges + 1, limitSeqNum); if (firstRetainedChangeSeqNum <= firstChangeSeqNum) { /* Nothing to prune. */ return this; } changeTracker.discardChanges(firstRetainedChangeSeqNum - 1); return this; } /* * Methods used to add a new component to the KV Store. */ public Datacenter add(Datacenter datacenter) { return datacenterMap.add(datacenter); } public StorageNode add(StorageNode storageNode) { return storageNodeMap.add(storageNode); } public RepGroup add(RepGroup repGroup) { return repGroupMap.add(repGroup); } public Partition add(Partition partition) { return partitionMap.add(partition); } /** For use when redistributing partitions. */ public Partition add(Partition partition, PartitionId partitionId) { return partitionMap.add(partition, partitionId); } /** * Methods used to update the component associated with the resourceId with * this one. The component previously associated with the resource id * is no longer considered to be a part of the topology. */ public Datacenter update(DatacenterId datacenterId, Datacenter datacenter) { return datacenterMap.update(datacenterId, datacenter); } public StorageNode update(StorageNodeId storageNodeId, StorageNode storageNode) { return storageNodeMap.update(storageNodeId, storageNode); } public RepGroup update(RepGroupId repGroupId, RepGroup repGroup) { return repGroupMap.update(repGroupId, repGroup); } public Partition update(PartitionId partitionId, Partition partition) { return partitionMap.update(partitionId, partition); } /** * Updates the location of a partition. */ public Partition updatePartition(PartitionId partitionId, RepGroupId repGroupId) { return update(partitionId, new Partition(repGroupId)); } /** * Methods used to remove an existing component from the Topology. Note * that the caller must ensure that removal of the component does not * create dangling references. [Sam: could introduce reference counting. * worth the effort? ] */ public Datacenter remove(DatacenterId datacenterId) { return datacenterMap.remove(datacenterId); } public StorageNode remove(StorageNodeId storageNodeId) { return storageNodeMap.remove(storageNodeId); } public RepGroup remove(RepGroupId repGroupId) { return repGroupMap.remove(repGroupId); } public Partition remove(PartitionId partitionId) { return partitionMap.remove(partitionId); } public RepNode remove(RepNodeId repNodeId) { RepGroup rg = repGroupMap.get(new RepGroupId(repNodeId.getGroupId())); if (rg == null) { throw new IllegalArgumentException ("Rep Group: " + repNodeId.getGroupId() + " is not in the topology"); } return rg.remove(repNodeId); } public ArbNode remove(ArbNodeId anid) { RepGroup rg = repGroupMap.get(new RepGroupId(anid.getGroupId())); if (rg == null) { throw new IllegalArgumentException ("Rep Group: " + anid.getGroupId() + " is not in the topology"); } return rg.remove(anid); } public byte[] getSignature() { if (signature == null) { return null; } return Arrays.copyOf(signature, signature.length); } public void updateSignature(byte[] newSignature) { signature = newSignature == null ? null : Arrays.copyOf(newSignature, newSignature.length); } public void stripSignature() { signature = null; } /** * Returns a byte array of the most essential topology information which * represents a full topology for signing. The data including the id, * sequence number, kvstore name, and all component maps of the topology * object. * * @throws IOException if any problem happens in transforming to byte array */ public byte[] toByteArrayForSignature() throws IOException { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final DataOutputStream dos = new DataOutputStream(baos); try { dos.writeInt(version); dos.writeLong(id); dos.writeUTF(kvStoreName); dos.writeInt(changeTracker.getSeqNum()); for (final ComponentMap m : getAllComponentMaps()) { dos.write(m.toByteArrayForSignature()); } } finally { try { dos.close(); } catch (IOException ioe) { /* Ignore */ } } return baos.toByteArray(); } /** * Returns true when two topologies have the same layout, that is, have the * same shards, partitions, zones and storage nodes, as well as the * idSequence of each component. */ public boolean layoutEquals(Topology otherTopo) { return Arrays.equals(getAllComponentMaps(), otherTopo.getAllComponentMaps()); } private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ois.defaultReadObject(); final boolean hasSignature; try { hasSignature = ois.readBoolean(); } catch (EOFException eofe) { /* * Reaches the end, regards it as a version of topology without * any extra information like signature. */ upgrade(); return; } if (hasSignature) { final int sigSize = ois.readInt(); signature = new byte[sigSize]; ois.read(signature); } upgrade(); } private void writeObject(ObjectOutputStream oos) throws IOException { oos.defaultWriteObject(); if (signature != null) { oos.writeBoolean(true); oos.writeInt(signature.length); oos.write(signature); } else { oos.writeBoolean(false); } } /** * Upgrades an R1 topology. * * The upgrade involves just one change: The initialization of the new * Datacenter.repFactor field. This field is computed by examining the * shards defined by the topology. The algorithm used below picks a random * shard to determine the distribution of RNs across DCs and then uses this * distribution for determining the RF for each DC. If the distribution is * inconsistent across shards, it leaves the Topology unchanged and returns * false. * * @return true if the topology was updated and is now the CURRENT_VERSION. * It returns false if the Topology was not modified. This can be because * the Topology was already the current version, or because the Topology * could not be upgraded because the shard layout did not permit * computation of a replication factor. It's the caller's responsibility * to check the version in this case and decide to take appropriate action. * * @throws UnknownVersionException when invoked on a Topology with a * version that's later than the CURRENT_VERSION */ public boolean upgrade() throws UnknownVersionException { if (version == CURRENT_VERSION) { return false; } if (version > CURRENT_VERSION) { /* Only accept older topologies for upgrade. */ throw new UnknownVersionException ("Upgrade encountered unknown version", Topology.class.getName(), CURRENT_VERSION, version); } if (getRepGroupMap().getAll().size() == 0) { /* An empty r1 topology */ version = CURRENT_VERSION; return true; } final RepGroup protoGroup = getRepGroupMap().getAll().iterator().next(); final HashMap protoMap = getRFMap(protoGroup); /* Verify that shards are all consistent. */ for (RepGroup rg : getRepGroupMap().getAll()) { final HashMap rfMap = getRFMap(rg); if (!protoMap.equals(rfMap)) { return false; } } /* Shards are isomorphic, can update the topology. */ for (Entry dce : protoMap.entrySet()) { final Datacenter dc = get(dce.getKey()); dc.setRepFactor(dce.getValue()); } version = CURRENT_VERSION; return true; } /** * Returns a Map associating a RF with each DC used by the RNs in the * shard. This method is used solely during upgrades. */ private HashMap getRFMap(RepGroup rgProto) { final HashMap rfMap = new HashMap(); /* Establish the prototype distribution */ for (RepNode rn : rgProto.getRepNodes()) { final DatacenterId dcId = get(rn.getStorageNodeId()).getDatacenterId(); Integer rf = rfMap.get(dcId); if (rf == null) { rf = 0; } rfMap.put(dcId, rf + 1); } return rfMap; } /** * All components of the Topology implement this interface * * @see #writeFastExternal FastExternalizable format */ @Persistent public static abstract class Component implements FastExternalizable, Serializable, Cloneable { private static final long serialVersionUID = 1L; /* * The Topology associated with this component; it's null if the * component has not been "added" to the Topology */ private Topology topology; /* * The unique resource id; it's null if the component has not been * "added" to the Topology */ private ResourceId resourceId; private int sequenceNumber; public Component() { } /** * Reads an arbitrary topology component from the input stream. */ public static Component readFastExternal(Topology topology, DataInput in, short serialVersion) throws IOException { final ResourceId resourceId = ResourceId.readFastExternal(in, serialVersion); return resourceId.readComponent(topology, in, serialVersion); } protected Component(Topology topology, ResourceId resourceId, DataInput in, @SuppressWarnings("unused") short serialVersion) throws IOException { this.topology = topology; this.resourceId = resourceId; sequenceNumber = readPackedInt(in); } /** * Writes this object to the output stream. Format: *

    *
  1. ({@link ResourceId}) {@link #getResourceId resourceId} *
  2. ({@link SerializationUtil#writePackedInt packedInt}) * {@link #getSequenceNumber sequenceNumber} *
*/ @Override public void writeFastExternal(DataOutput out, short serialVersion) throws IOException { resourceId.writeFastExternal(out, serialVersion); writePackedInt(out, sequenceNumber); } @Override public abstract Component clone(); /* * The hashCode and equals methods are primarily defined for * testing purposes. Note that they exclude the topology field * to permit comparison of components across Topologies. */ @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((resourceId == null) ? 0 : resourceId.hashCode()); result = prime * result + sequenceNumber; return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } Componentother = (Component)obj; if (resourceId == null) { if (other.resourceId != null) { return false; } } else if (!resourceId.equals(other.resourceId)) { return false; } if (sequenceNumber != other.sequenceNumber) { return false; } return true; } /** * Convert this component to JSON representation. * @return JSON object node */ public abstract ObjectNode toJson(); /* * The hashCode and equals methods are primarily defined for * testing purposes. Note that they exclude the topology field * to permit comparison of components across Topologies. */ /** * A variation of clone used to ensure that references outside the * component are not maintained for Topology log serialization where * the component will be copied and applied to a different Topology. * * @return the standalone copy of the object */ public Component cloneForLog() { Component clone = clone(); clone.topology = null; return clone; } /** * Create a new instance that is a copy of another component. * *

Subclasses that contain references to ResourceIds where * instances might be shared in some cases and not in others should * arrange to clone those resource IDs in the new instance. This step * is necessary because we use the exact layout of the serialized form * to generate signatures, and whether or not an object is shared or * copied affects the layout of the serialized form. For example, * StorageNode needs to clone its DatacenterId because that object can * otherwise be created afresh in some cases and copied in others. */ protected Component(Component other) { this.topology = other.topology; this.resourceId = other.resourceId; this.sequenceNumber = other.sequenceNumber; } /** * Returns the topology associated with this component, or null if it's * a free-standing component. */ public Topology getTopology() { return topology; } public void setTopology(Topology topology) { assert (this.topology == null) || (topology == null); this.topology = topology; } /* Adds the component to the topology */ public void setResourceId(T resourceId) { assert (this.resourceId == null); this.resourceId = resourceId; } /** * Returns the unique resourceId associated with the Resource */ @SuppressWarnings("unchecked") public T getResourceId() { return (T)resourceId; } /* Returns the ResourceType associated with this component. */ abstract ResourceType getResourceType(); /** * Returns the sequence number associated with the * {@link TopologyChange} that last modified the component. The * following invariant must always hold. * * Component.getSequenceNumber() <= Topology.getSequenceNumber() */ public int getSequenceNumber() { return sequenceNumber; } public void setSequenceNumber(int sequenceNumber) { this.sequenceNumber = sequenceNumber; } /** * Returns the StorageNodeId of the SN hosting this component. Only * components which reside on a storage node, such as RepNodes, * StorageNodes, and AdminInstances will implement this. */ public StorageNodeId getStorageNodeId() { throw new UnsupportedOperationException ("Not supported for component " + resourceId); } /** * Returns true if this component implements a MonitorAgent protocol */ public boolean isMonitorEnabled() { return false; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy