blob: 32f1730a15816773d03f5a5298d166a9a2733f85 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.hadoop.hdds.scm.container;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.time.Instant;
import java.util.Arrays;
import java.util.Comparator;
import org.apache.hadoop.hdds.client.ReplicationConfig;
import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
import org.apache.hadoop.hdds.scm.pipeline.PipelineID;
import org.apache.hadoop.util.Time;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.google.common.base.Preconditions;
import static java.lang.Math.max;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
/**
* Class wraps ozone container info.
*/
public class ContainerInfo implements Comparator<ContainerInfo>,
Comparable<ContainerInfo>, Externalizable {
private static final String SERIALIZATION_ERROR_MSG = "Java serialization not"
+ " supported. Use protobuf instead.";
private HddsProtos.LifeCycleState state;
@JsonIgnore
private PipelineID pipelineID;
private ReplicationConfig replicationConfig;
private long usedBytes;
private long numberOfKeys;
private Instant lastUsed;
// The wall-clock ms since the epoch at which the current state enters.
private Instant stateEnterTime;
private String owner;
private long containerID;
// Delete Transaction Id is updated when new transaction for a container
// is stored in SCM delete Table.
// TODO: Replication Manager should consider deleteTransactionId so that
// replica with higher deleteTransactionId is preferred over replica with
// lower deleteTransactionId.
private long deleteTransactionId;
// The sequenceId of a close container cannot change, and all the
// container replica should have the same sequenceId.
private long sequenceId;
/**
* Allows you to maintain private data on ContainerInfo. This is not
* serialized via protobuf, just allows us to maintain some private data.
*/
@JsonIgnore
private byte[] data;
@SuppressWarnings("parameternumber")
ContainerInfo(
long containerID,
HddsProtos.LifeCycleState state,
PipelineID pipelineID,
long usedBytes,
long numberOfKeys,
long stateEnterTime,
String owner,
long deleteTransactionId,
long sequenceId,
ReplicationConfig repConfig) {
this.containerID = containerID;
this.pipelineID = pipelineID;
this.usedBytes = usedBytes;
this.numberOfKeys = numberOfKeys;
this.lastUsed = Instant.ofEpochMilli(Time.now());
this.state = state;
this.stateEnterTime = Instant.ofEpochMilli(stateEnterTime);
this.owner = owner;
this.deleteTransactionId = deleteTransactionId;
this.sequenceId = sequenceId;
this.replicationConfig = repConfig;
}
/**
* Needed for serialization findbugs.
*/
public ContainerInfo() {
}
public static ContainerInfo fromProtobuf(HddsProtos.ContainerInfoProto info) {
ContainerInfo.Builder builder = new ContainerInfo.Builder();
final ReplicationConfig config = ReplicationConfig
.fromProto(info.getReplicationType(), info.getReplicationFactor());
builder.setUsedBytes(info.getUsedBytes())
.setNumberOfKeys(info.getNumberOfKeys())
.setState(info.getState())
.setStateEnterTime(info.getStateEnterTime())
.setOwner(info.getOwner())
.setContainerID(info.getContainerID())
.setDeleteTransactionId(info.getDeleteTransactionId())
.setReplicationConfig(config)
.setSequenceId(info.getSequenceId())
.build();
if (info.hasPipelineID()) {
builder.setPipelineID(PipelineID.getFromProtobuf(info.getPipelineID()));
}
return builder.build();
}
/**
* This method is depricated, use {@code containerID()} which returns
* {@link ContainerID} object.
*/
@Deprecated
public long getContainerID() {
return containerID;
}
public HddsProtos.LifeCycleState getState() {
return state;
}
public void setState(HddsProtos.LifeCycleState state) {
this.state = state;
}
public Instant getStateEnterTime() {
return stateEnterTime;
}
public ReplicationConfig getReplicationConfig() {
return replicationConfig;
}
public HddsProtos.ReplicationType getReplicationType() {
return replicationConfig.getReplicationType();
}
public PipelineID getPipelineID() {
return pipelineID;
}
public long getUsedBytes() {
return usedBytes;
}
public void setUsedBytes(long value) {
usedBytes = value;
}
public long getNumberOfKeys() {
return numberOfKeys;
}
public void setNumberOfKeys(long value) {
numberOfKeys = value;
}
public long getDeleteTransactionId() {
return deleteTransactionId;
}
public long getSequenceId() {
return sequenceId;
}
public void updateDeleteTransactionId(long transactionId) {
deleteTransactionId = max(transactionId, deleteTransactionId);
}
public void updateSequenceId(long sequenceID) {
assert (isOpen() || state == HddsProtos.LifeCycleState.QUASI_CLOSED);
sequenceId = max(sequenceID, sequenceId);
}
public ContainerID containerID() {
return ContainerID.valueOf(containerID);
}
/**
* Gets the last used time from SCM's perspective.
*
* @return time in milliseconds.
*/
public Instant getLastUsed() {
return lastUsed;
}
public void updateLastUsedTime() {
lastUsed = Instant.ofEpochMilli(Time.now());
}
@JsonIgnore
public HddsProtos.ContainerInfoProto getProtobuf() {
HddsProtos.ContainerInfoProto.Builder builder =
HddsProtos.ContainerInfoProto.newBuilder();
Preconditions.checkState(containerID > 0);
builder.setContainerID(getContainerID())
.setUsedBytes(getUsedBytes())
.setNumberOfKeys(getNumberOfKeys()).setState(getState())
.setStateEnterTime(getStateEnterTime().toEpochMilli())
.setContainerID(getContainerID())
.setDeleteTransactionId(getDeleteTransactionId())
.setOwner(getOwner())
.setSequenceId(getSequenceId());
builder.setReplicationFactor(
ReplicationConfig.getLegacyFactor(replicationConfig));
builder.setReplicationType(replicationConfig.getReplicationType());
if (getPipelineID() != null) {
builder.setPipelineID(getPipelineID().getProtobuf());
}
return builder.build();
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
@Override
public String toString() {
return "ContainerInfo{"
+ "id=" + containerID
+ ", state=" + state
+ ", pipelineID=" + pipelineID
+ ", stateEnterTime=" + stateEnterTime
+ ", owner=" + owner
+ '}';
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ContainerInfo that = (ContainerInfo) o;
return new EqualsBuilder()
.append(getContainerID(), that.getContainerID())
// TODO : Fix this later. If we add these factors some tests fail.
// So Commenting this to continue and will enforce this with
// Changes in pipeline where we remove Container Name to
// SCMContainerinfo from Pipeline.
// .append(pipeline.getFactor(), that.pipeline.getFactor())
// .append(pipeline.getType(), that.pipeline.getType())
.append(owner, that.owner)
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(11, 811)
.append(getContainerID())
.append(getOwner())
.toHashCode();
}
/**
* Compares its two arguments for order. Returns a negative integer, zero, or
* a positive integer as the first argument is less than, equal to, or greater
* than the second.<p>
*
* @param o1 the first object to be compared.
* @param o2 the second object to be compared.
* @return a negative integer, zero, or a positive integer as the first
* argument is less than, equal to, or greater than the second.
* @throws NullPointerException if an argument is null and this comparator
* does not permit null arguments
* @throws ClassCastException if the arguments' types prevent them from
* being compared by this comparator.
*/
@Override
public int compare(ContainerInfo o1, ContainerInfo o2) {
return Long.compare(
o1.getLastUsed().toEpochMilli(), o2.getLastUsed().toEpochMilli());
}
/**
* Compares this object with the specified object for order. Returns a
* negative integer, zero, or a positive integer as this object is less than,
* equal to, or greater than the specified object.
*
* @param o the object to be compared.
* @return a negative integer, zero, or a positive integer as this object is
* less than, equal to, or greater than the specified object.
* @throws NullPointerException if the specified object is null
* @throws ClassCastException if the specified object's type prevents it
* from being compared to this object.
*/
@Override
public int compareTo(ContainerInfo o) {
return this.compare(this, o);
}
/**
* Returns private data that is set on this containerInfo.
*
* @return blob, the user can interpret it any way they like.
*/
public byte[] getData() {
if (this.data != null) {
return Arrays.copyOf(this.data, this.data.length);
} else {
return null;
}
}
/**
* Set private data on ContainerInfo object.
*
* @param data -- private data.
*/
public void setData(byte[] data) {
if (data != null) {
this.data = Arrays.copyOf(data, data.length);
}
}
/**
* Throws IOException as default java serialization is not supported. Use
* serialization via protobuf instead.
*
* @param out the stream to write the object to
* @throws IOException Includes any I/O exceptions that may occur
* @serialData Overriding methods should use this tag to describe
* the data layout of this Externalizable object.
* List the sequence of element types and, if possible,
* relate the element to a public/protected field and/or
* method of this Externalizable class.
*/
@Override
public void writeExternal(ObjectOutput out) throws IOException {
throw new IOException(SERIALIZATION_ERROR_MSG);
}
/**
* Throws IOException as default java serialization is not supported. Use
* serialization via protobuf instead.
*
* @param in the stream to read data from in order to restore the object
* @throws IOException if I/O errors occur
* @throws ClassNotFoundException If the class for an object being
* restored cannot be found.
*/
@Override
public void readExternal(ObjectInput in)
throws IOException, ClassNotFoundException {
throw new IOException(SERIALIZATION_ERROR_MSG);
}
/**
* Builder class for ContainerInfo.
*/
public static class Builder {
private HddsProtos.LifeCycleState state;
private long used;
private long keys;
private long stateEnterTime;
private String owner;
private long containerID;
private long deleteTransactionId;
private long sequenceId;
private PipelineID pipelineID;
private ReplicationConfig replicationConfig;
public Builder setPipelineID(PipelineID pipelineId) {
this.pipelineID = pipelineId;
return this;
}
public Builder setReplicationConfig(ReplicationConfig repConfig) {
this.replicationConfig = repConfig;
return this;
}
public Builder setContainerID(long id) {
Preconditions.checkState(id >= 0);
this.containerID = id;
return this;
}
public Builder setState(HddsProtos.LifeCycleState lifeCycleState) {
this.state = lifeCycleState;
return this;
}
public Builder setUsedBytes(long bytesUsed) {
this.used = bytesUsed;
return this;
}
public Builder setNumberOfKeys(long keyCount) {
this.keys = keyCount;
return this;
}
public Builder setStateEnterTime(long time) {
this.stateEnterTime = time;
return this;
}
public Builder setOwner(String containerOwner) {
this.owner = containerOwner;
return this;
}
public Builder setDeleteTransactionId(long deleteTransactionID) {
this.deleteTransactionId = deleteTransactionID;
return this;
}
public Builder setSequenceId(long sequenceID) {
this.sequenceId = sequenceID;
return this;
}
public ContainerInfo build() {
return new ContainerInfo(containerID, state, pipelineID,
used, keys, stateEnterTime, owner, deleteTransactionId,
sequenceId, replicationConfig);
}
}
/**
* Check if a container is in open state, this will check if the
* container is either open or closing state. Any containers in these states
* is managed as an open container by SCM.
*/
public boolean isOpen() {
return state == HddsProtos.LifeCycleState.OPEN
|| state == HddsProtos.LifeCycleState.CLOSING;
}
}