/**
* VMware Continuent Tungsten Replicator
* Copyright (C) 2015 VMware, Inc. All rights reserved.
*
* Licensed 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.
*
* Initial developer(s):
* Contributor(s):
*/
package com.continuent.tungsten.common.cluster.resource;
import java.io.Serializable;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import javax.xml.bind.annotation.XmlRootElement;
import org.apache.log4j.Logger;
import org.codehaus.jackson.annotate.JsonCreator;
import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.annotate.JsonPropertyOrder;
import com.continuent.tungsten.common.config.TungstenProperties;
import com.continuent.tungsten.common.patterns.order.HighWaterResource;
import com.continuent.tungsten.common.patterns.order.Sequence;
@XmlRootElement
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonPropertyOrder(alphabetic = true)
public class DataSource extends Resource implements Serializable
{
private static final long serialVersionUID = 8153881753668230575L;
private static final Logger logger = Logger.getLogger(DataSource.class);
public static final String NAME = "name";
public static final String CLUSTERNAME = "dataServiceName";
public static final String PRECEDENCE = "precedence";
public static final String ISAVAILABLE = "isAvailable";
public static final String STATE = "state";
public static final String ISCOMPOSITE = "isComposite";
public static final String ALERT_STATUS = "alertStatus";
public static final String ALERT_MESSAGE = "alertMessage";
public static final String ALERT_TIME = "alertTime";
public static final String APPLIED_LATENCY = "appliedLatency";
public static final String RELATIVE_LATENCY = "relativeLatency";
public static final String HOST = "host";
public static final String ROLE = "role";
public static final String VENDOR = "vendor";
public static final String DRIVER = "driver";
public static final String URL = "url";
public static final String LASTERROR = "lastError";
public static final String LASTSHUNREASON = "lastShunReason";
public static final String HIGHWATER = "highWater";
public static final String VIPINTERFACE = "vipInterface";
public static final String VIPADDRESS = "vipAddress";
public static final String VIPISBOUND = "vipIsBound";
public static final String ACTIVE_CONNECTION_COUNT = "activeConnectionCount";
public static final String CONNECTIONS_CREATED_COUNT = "connectionsCreatedCount";
public static final String TYPE = "type";
public static final String MASTER_CONNECT_URI = "masterConnectUri";
// Defaults
public static final double DEFAULT_APPLIED_LATENCY = 0.0;
/**
* The following six properties are the absolute minimum that are required
* in order to derive a datasource that will work within the rest of the
* framework. In particular, if 'host' is missing - it refers to the host
* where the datasource is resident - the framework that associates
* replicators with datasources will fail to work.
*/
private String dataServiceName = "";
private String host = "";
private DataSourceRole role = DataSourceRole.undefined;
private String vendor = "";
private String driver = "";
private String url = "";
private boolean isComposite = false;
private int precedence = 0;
private boolean isAvailable = false;
private String masterConnectUri = "";
private ResourceState state = ResourceState.UNKNOWN;
private DataSourceAlertStatus alertStatus = DataSourceAlertStatus.UNKNOWN;
private String alertMessage = "";
private long alertTime = System.currentTimeMillis();
private String lastError = "";
private String lastShunReason = "";
private double appliedLatency = DEFAULT_APPLIED_LATENCY;
private double relativeLatency = DEFAULT_APPLIED_LATENCY;
private Date updateTimestamp = new Date();
private Date lastUpdate = new Date();
@SuppressWarnings("unused")
private boolean isStandby = false;
private HighWaterResource highWater = new HighWaterResource();
// Statistics.
private AtomicLong activeConnectionsCount = new AtomicLong(
0);
private AtomicLong connectionsCreatedCount = new AtomicLong(
0);
private AtomicLong statementsCreatedCount = new AtomicLong(
0);
private AtomicLong preparedStatementsCreatedCount = new AtomicLong(
0);
private AtomicLong callableStatementsCreatedCount = new AtomicLong(
0);
/**
* Represents a single life cycle transition for this datasource. A
* transition occurs for any update() in which
*/
private Sequence sequence = new Sequence();
private AtomicInteger enabled = new AtomicInteger(
0);
/**
* VIP management properties
*/
private String vipInterface = "";
private String vipAddress = "";
private boolean vipIsBound = false;
/** Retains all non-closed connections to this data source */
private Set<DatabaseConnection> activeConnections = Collections
.synchronizedSet(new HashSet<DatabaseConnection>());
/**
* Creates a new <code>DataSource</code> object
*/
public DataSource()
{
super(ResourceType.DATASOURCE, "unknown");
}
public DataSource(TungstenProperties props)
{
super(ResourceType.DATASOURCE, props.getString(DataSource.NAME,
"unknown", true));
props.applyProperties(this, true);
String state = props.getString(DataSource.STATE);
/*
* Backwards compatibility - previous versions don't have state.
*/
if (state == null)
{
if (props.getBoolean(DataSource.ISAVAILABLE) == true)
setState(ResourceState.ONLINE);
else
setState(ResourceState.OFFLINE);
}
else
{
setState(ResourceState.valueOf(state));
}
// Backwards compatible.
String alertStatus = props.getString(DataSource.ALERT_STATUS);
if (alertStatus == null)
{
switch (getState())
{
case ONLINE :
setAlert(DataSourceAlertStatus.OK, "");
break;
case OFFLINE :
case FAILED :
setAlert(DataSourceAlertStatus.WARN, "alert reason unknown");
break;
case SHUNNED :
setAlert(DataSourceAlertStatus.SHUNNED, "");
break;
default :
setAlert(DataSourceAlertStatus.UNKNOWN, "");
break;
}
}
else
{
String alertMessage = props.getString(DataSource.ALERT_MESSAGE, "",
false);
setAlertStatus(DataSourceAlertStatus.valueOf(alertStatus));
setAlertMessage(alertMessage);
setAlertTime(props.getLong(DataSource.ALERT_TIME));
}
}
@JsonCreator
public DataSource(@JsonProperty("name")
String key, @JsonProperty("dataServiceName")
String clusterName, @JsonProperty("host")
String host)
{
super(ResourceType.DATASOURCE, key);
this.dataServiceName = clusterName;
this.host = host;
}
public void addConnection(DatabaseConnection conn)
{
// thread-safe: ActiveConnection is a synchronizedSet
activeConnections.add(conn);
}
public void removeConnection(DatabaseConnection conn)
{
// thread-safe: ActiveConnection is a synchronizedSet
activeConnections.remove(conn);
}
public long getActiveConnectionCount()
{
return activeConnectionsCount.get();
}
/**
* Provides a reference to the synchronized set of active connections. Any
* iterator operation on this set MUST be synchronized on the set object
*
* @return the active connections set
*/
@JsonIgnore
public Set<DatabaseConnection> getActiveConnections()
{
return activeConnections;
}
static public TungstenProperties updateFromReplicatorStatus(
TungstenProperties replicatorProps, TungstenProperties dsProps)
{
dsProps.setString(NAME, replicatorProps.getString(Replicator.SOURCEID));
dsProps.setString(CLUSTERNAME,
replicatorProps.getString(Replicator.CLUSTERNAME));
dsProps.setString(HOST,
replicatorProps.getString(Replicator.DATASERVERHOST));
dsProps.setString(VENDOR,
replicatorProps.getString(Replicator.RESOURCE_VENDOR));
dsProps.setString(URL,
replicatorProps.getString(Replicator.RESOURCE_JDBC_URL));
dsProps.setString(DRIVER,
replicatorProps.getString(Replicator.RESOURCE_JDBC_DRIVER));
dsProps.setString(ROLE, replicatorProps.getString(Replicator.ROLE)
.toLowerCase());
dsProps.setString(HIGHWATER, String.format("%d(%s)",
replicatorProps.getLong(Replicator.LATEST_EPOCH_NUMBER),
replicatorProps.getString(Replicator.APPLIED_LAST_EVENT_ID)));
dsProps.setDouble(APPLIED_LATENCY,
replicatorProps.getDouble(Replicator.APPLIED_LATENCY));
dsProps.setDouble(APPLIED_LATENCY,
replicatorProps.getDouble(Replicator.RELATIVE_LATENCY));
dsProps.setString(VIPINTERFACE, replicatorProps.getString(
Replicator.RESOURCE_VIP_INTERFACE, null, true));
dsProps.setString(VIPADDRESS, replicatorProps.getString(
Replicator.RESOURCE_VIP_ADDRESS, null, true));
return dsProps;
}
static public TungstenProperties createFromReplicatorStatus(
TungstenProperties replicatorProps)
{
DataSource newDs = new DataSource(
replicatorProps.getString(Replicator.SOURCEID),
replicatorProps.getString(Replicator.CLUSTERNAME),
replicatorProps.getString(Replicator.SOURCEID));
newDs.setVendor(replicatorProps.getString(Replicator.RESOURCE_VENDOR));
newDs.setUrl(replicatorProps.getString(Replicator.RESOURCE_JDBC_URL)
.trim());
newDs.setDriver(replicatorProps.getString(
Replicator.RESOURCE_JDBC_DRIVER).trim());
newDs.setRole(replicatorProps.getString(Replicator.ROLE).toLowerCase());
boolean isStandby = replicatorProps
.getBoolean(Replicator.RESOURCE_IS_STANDBY_DATASOURCE);
if (isStandby)
{
// Standby data sources are not available for reads or writes
newDs.setIsAvailable(false);
newDs.setState(ResourceState.OFFLINE);
newDs.setStandby(true);
}
else
{
newDs.setIsAvailable(true);
newDs.setState(ResourceState.ONLINE);
newDs.setStandby(false);
}
newDs.setPrecedence(99);
newDs.setHighWater(replicatorProps.getLong(
Replicator.LATEST_EPOCH_NUMBER, "0", false), replicatorProps
.getString(Replicator.APPLIED_LAST_EVENT_ID));
newDs.setAppliedLatency(replicatorProps
.getDouble(Replicator.APPLIED_LATENCY));
newDs.setRelativeLatency(replicatorProps
.getDouble(Replicator.RELATIVE_LATENCY));
newDs.setVipInterface(replicatorProps.getString(
Replicator.RESOURCE_VIP_INTERFACE, null, true));
newDs.setVipAddress(replicatorProps.getString(
Replicator.RESOURCE_VIP_ADDRESS, null, true));
newDs.setVipIsBound(false);
newDs.setComposite(false);
return newDs.toProperties();
}
static public TungstenProperties createWitnessFromMemberHeartbeat(
TungstenProperties memberHeartbeatProps)
{
DataSource newDs = new DataSource(
memberHeartbeatProps.getString("memberName"),
memberHeartbeatProps.getString("clusterName"),
memberHeartbeatProps.getString("memberName"));
newDs.setRole(DataSourceRole.witness.toString());
newDs.setAlertStatus(DataSourceAlertStatus.OK);
// Standby data sources are not available for reads or writes
newDs.setIsAvailable(false);
newDs.setState(ResourceState.OFFLINE);
newDs.setStandby(true);
newDs.setPrecedence(99);
newDs.setVipIsBound(false);
newDs.setComposite(false);
return newDs.toProperties();
}
public String getDriver()
{
if (driver == null)
return "";
return driver;
}
public void setDriver(String driver)
{
this.driver = driver;
}
public String getUrl()
{
if (url == null)
return "";
return url;
}
public void setUrl(String url)
{
this.url = url;
}
public String getRole()
{
return role.toString();
}
public DataSourceRole getDataSourceRole()
{
return role;
}
public void setRole(String role)
{
this.role = DataSourceRole.valueOf(role.toLowerCase());
}
public void setDataSourceRole(DataSourceRole role)
{
this.role = role;
}
public String getMasterConnectUri()
{
return masterConnectUri;
}
public void setMasterConnectUri(String masterConnectUri)
{
this.masterConnectUri = masterConnectUri;
}
public int getPrecedence()
{
return precedence;
}
public void setPrecedence(int precedence)
{
this.precedence = precedence;
}
public String getVendor()
{
if (vendor == null)
return "";
return vendor;
}
public void setVendor(String vendor)
{
this.vendor = vendor;
}
/**
* @return the isAvailable
*/
public boolean isAvailable()
{
return isAvailable;
}
/**
* @return the isAvailable
*/
public boolean getIsAvailable()
{
return isAvailable;
}
public void setCritical(String message)
{
setAlert(DataSourceAlertStatus.CRITICAL, message);
}
/**
* @param isAvailable the isDateAvailable to set
*/
public void setIsAvailable(boolean isAvailable)
{
this.isAvailable = isAvailable;
if (isAvailable)
{
setState(ResourceState.ONLINE);
setAlert(DataSourceAlertStatus.OK, "");
}
else
{
setState(ResourceState.OFFLINE);
}
setLastShunReason("");
setLastError("");
}
/**
* Prevent the driver from pDaterocessing new connection requests. If the
* driver is disabled, it will either cause new connection requests to wait
* or will throw a SQLException.
*
* @throws InterruptedException
*/
public void disable() throws InterruptedException
{
// If waitFlag is true, we need to wait until all
// active connections are completed.
synchronized (enabled)
{
if (enabled.get() == 0)
{
return;
}
enabled.set(0);
}
}
/**
* Update a given datasource with values from a different datasource
*
* @param ds
*/
public void update(DataSource ds)
{
synchronized (this)
{
sequence.next();
this.setName(ds.getName());
this.setVendor(ds.getVendor());
this.setDataServiceName(ds.getDataServiceName());
this.setDriver(ds.getDriver());
this.setUrl(ds.getUrl());
this.setRole(ds.getRole());
this.setMasterConnectUri(ds.getMasterConnectUri());
this.setPrecedence(ds.getPrecedence());
this.setIsAvailable(ds.getIsAvailable());
this.setState(ds.getState());
this.setLastError(ds.getLastError());
this.setLastShunReason(ds.getLastShunReason());
this.setAppliedLatency(ds.getAppliedLatency());
this.setRelativeLatency(ds.getRelativeLatency());
this.setUpdateTimestamp(ds.getUpdateTimestamp());
this.setLastError(ds.getLastError());
this.setVipAddress(ds.getVipAddress());
this.setVipInterface(ds.getVipInterface());
this.setVipIsBound(ds.getVipIsBound());
this.setLastUpdateToNow();
this.notifyAll();
}
}
public TungstenProperties toProperties()
{
TungstenProperties props = new TungstenProperties();
props.setString(NAME, getName());
props.setString(VENDOR, getVendor());
props.setString(CLUSTERNAME, getDataServiceName());
props.setString(HOST, getHost());
props.setString(DRIVER, getDriver());
props.setString(URL, getUrl());
props.setString(ROLE, getRole().toString());
props.setString(MASTER_CONNECT_URI, getMasterConnectUri());
props.setString(ALERT_STATUS, alertStatus.toString());
props.setString(ALERT_MESSAGE, alertMessage);
props.setLong(ALERT_TIME, alertTime);
props.setInt(PRECEDENCE, getPrecedence());
props.setBoolean(ISAVAILABLE, getIsAvailable());
props.setString(STATE, getState().toString());
props.setString(LASTERROR, getLastError());
props.setString(LASTSHUNREASON, getLastShunReason());
props.setDouble(APPLIED_LATENCY, appliedLatency);
props.setDouble(RELATIVE_LATENCY, relativeLatency);
props.setLong(ACTIVE_CONNECTION_COUNT, activeConnectionsCount.get());
props.setLong(CONNECTIONS_CREATED_COUNT, connectionsCreatedCount.get());
props.setLong("statementsCreatedCount", statementsCreatedCount.get());
props.setLong("preparedStatementsCreatedCount",
preparedStatementsCreatedCount.get());
props.setLong("callableStatementsCreatedCount",
callableStatementsCreatedCount.get());
props.setString("highWater", highWater.toString());
props.setString("sequence", sequence.toString());
props.setString(VIPADDRESS, getVipAddress());
props.setString(VIPINTERFACE, getVipInterface());
props.setBoolean(VIPISBOUND, getVipIsBound());
props.setBoolean(ISCOMPOSITE, isComposite());
return props;
}
/**
* @return properties representing this datasource
*/
public Map<String, String> toMap()
{
return toProperties().hashMap();
}
/**
* Creates a new <code>DataSource</code> object
*
* @param dsProperties
*/
public DataSource(Map<String, String> dsProperties)
{
set(dsProperties);
}
public void set(Map<String, String> dsProperties)
{
TungstenProperties props = new TungstenProperties(dsProperties);
props.applyProperties(this, true);
}
/**
* Returns the sequence value.
*
* @return Returns the sequence.
*/
public Sequence getSequence()
{
return sequence;
}
public void incrementActiveConnections()
{
long count = activeConnectionsCount.incrementAndGet();
logger.debug("Incremented connections for datasource: name="
+ this.getName() + " count=" + count);
}
public void decrementActiveConnections()
{
long count = activeConnectionsCount.decrementAndGet();
logger.debug("Decremented connections for datasource: name="
+ this.getName() + " count=" + count);
}
/**
* Returns the number of connections created on this datasource.
*/
@JsonProperty()
public long getConnectionsCreated()
{
return connectionsCreatedCount.get();
}
public void incrementConnectionsCreated()
{
connectionsCreatedCount.incrementAndGet();
}
/**
* Returns the number of JDBC Statement instances created.
*/
public long getStatementsCreated()
{
return statementsCreatedCount.get();
}
public void incrementStatementsCreated()
{
this.statementsCreatedCount.incrementAndGet();
}
/**
* Returns the number of JDBC PreparedStatement instances created.
*/
public long getPreparedStatementsCreated()
{
return preparedStatementsCreatedCount.get();
}
public void incrementPreparedStatementsCreated()
{
this.preparedStatementsCreatedCount.incrementAndGet();
}
/**
* Returns the number of JDBC CallableStatement instances created.
*/
public long getCallableStatementsCreated()
{
return callableStatementsCreatedCount.get();
}
public void incrementCallableStatementsCreated()
{
this.callableStatementsCreatedCount.incrementAndGet();
}
/**
* Returns the dataServiceName value.
*
* @return Returns the dataServiceName.
*/
public String getDataServiceName()
{
return dataServiceName;
}
/**
* Sets the dataServiceName value.
*
* @param dataServiceName The dataServiceName to set.
*/
public void setDataServiceName(String dataServiceName)
{
this.dataServiceName = dataServiceName;
}
/**
* Format a datasource for display
*/
public double getAppliedLatency()
{
return appliedLatency;
}
/**
* Sets the last seen latency of this data source
*
* @param appliedLatency update appliedLatency observed
*/
public void setAppliedLatency(double appliedLatency)
{
this.appliedLatency = appliedLatency;
}
/**
* Format a datasource for display
*/
@Override
public String toString()
{
return String.format("%s@%s(%s:%s) STATUS(%s)", getName(),
getDataServiceName(), getRole(), getState(), getAlertStatus());
}
/**
* Gives the last time this data source received an update
*
* @return the last update time
*/
public Date getLastUpdate()
{
return lastUpdate;
}
/**
* Sets the lastUpdate field to the current instant in time
*/
private void setLastUpdateToNow()
{
this.lastUpdate = new Date();
}
/**
* Sets the sequence value.
*
* @param sequence The sequence to set.
*/
public void setSequence(Sequence sequence)
{
this.sequence = sequence;
}
/**
* Returns the host value.
*
* @return Returns the host.
*/
public String getHost()
{
return host;
}
/**
* Sets the host value.
*
* @param host The host to set.
*/
public void setHost(String host)
{
this.host = host;
}
@JsonIgnore
public HighWaterResource getHighWater()
{
return highWater;
}
public void setHighWater(HighWaterResource highWater)
{
if (highWater != null)
{
setHighWater(highWater.getHighWaterEpoch(),
highWater.getHighWaterEventId());
}
}
public void setHighWater(long epoch, String eventId)
{
if (this.highWater != null)
{
this.highWater.update(epoch, eventId);
}
else
{
this.setHighWater(new HighWaterResource(epoch, eventId));
}
}
public AtomicInteger getEnabled()
{
return enabled;
}
public void setEnabled(AtomicInteger enabled)
{
this.enabled = enabled;
}
public boolean isMaster()
{
return role == DataSourceRole.master;
}
public boolean isSlave()
{
return role == DataSourceRole.slave;
}
public boolean isRelay()
{
return role == DataSourceRole.relay;
}
public ResourceState getState()
{
return state;
}
public void setState(ResourceState state)
{
this.state = state;
}
public void setFailed(String error)
{
setIsAvailable(false);
this.state = ResourceState.FAILED;
setAlert(DataSourceAlertStatus.CRITICAL, "state was set to FAILED");
if (error != null)
{
this.lastError = error;
}
}
public void setShunned(String reason)
{
setIsAvailable(false);
this.state = ResourceState.SHUNNED;
setAlert(DataSourceAlertStatus.SHUNNED, "");
if (reason != null)
{
this.lastShunReason = reason;
this.lastError = "";
}
}
public String getLastError()
{
return lastError;
}
public void setLastError(String lastError)
{
this.lastError = lastError;
}
public String getLastShunReason()
{
return lastShunReason;
}
public void setLastShunReason(String lastShunReason)
{
this.lastShunReason = lastShunReason;
}
public static DataSource copy(DataSource ds)
{
return new DataSource(ds.toProperties());
}
public String getVipInterface()
{
return vipInterface;
}
public void setVipInterface(String vipInterface)
{
this.vipInterface = vipInterface;
}
public String getVipAddress()
{
return vipAddress;
}
public void setVipAddress(String vipAddress)
{
this.vipAddress = vipAddress;
}
public boolean getVipIsBound()
{
return vipIsBound;
}
public void setVipIsBound(boolean vipIsBound)
{
this.vipIsBound = vipIsBound;
}
public Date getUpdateTimestamp()
{
return updateTimestamp;
}
public void setUpdateTimestamp(Date updateTimestamp)
{
this.updateTimestamp = updateTimestamp;
}
public DataSourceAlertStatus getAlertStatus()
{
return alertStatus;
}
public void setAlertStatus(DataSourceAlertStatus alertStatus)
{
this.alertStatus = alertStatus;
}
public String getAlertMessage()
{
return alertMessage;
}
public void setAlertMessage(String alertMessage)
{
this.alertMessage = alertMessage;
}
public void setAlert(DataSourceAlertStatus status, String message)
{
alertStatus = status;
alertMessage = message;
alertTime = System.currentTimeMillis();
}
public long getAlertTime()
{
return alertTime;
}
public void setAlertTime(long alertTime)
{
this.alertTime = alertTime;
}
public boolean isComposite()
{
return isComposite;
}
public void setComposite(boolean isComposite)
{
this.isComposite = isComposite;
}
public void setIsComposite(boolean isComposite)
{
this.isComposite = isComposite;
}
/**
* Returns the standby value.
*
* @return Returns the standby.
*/
public boolean isStandby()
{
return getRole().equals(DataSourceRole.standby.toString());
}
/**
* Sets the standby value.
*/
public void setStandby(boolean isStandby)
{
this.isStandby = isStandby;
}
/**
* Set the activeConnectionCount for this data source from the count passed
* via a long
*
* @param activeConnectionCount
*/
public void setActiveConnectionCount(long activeConnectionCount)
{
this.activeConnectionsCount.set(activeConnectionCount);
}
/**
* Set the connectionsCreatedCount for this data source from the count
* passed via a long
*
* @param connectionsCreatedCount
*/
public void setConnectionsCreatedCount(long connectionsCreatedCount)
{
this.connectionsCreatedCount.set(connectionsCreatedCount);
}
/**
* Returns the isWitness value.
*
* @return Returns the isWitness.
*/
public boolean isWitness()
{
return role.equals(DataSourceRole.witness);
}
/**
* Returns the relativeLatency value.
*
* @return Returns the relativeLatency.
*/
public double getRelativeLatency()
{
return relativeLatency;
}
/**
* Sets the relativeLatency value.
*
* @param relativeLatency The relativeLatency to set.
*/
public void setRelativeLatency(double relativeLatency)
{
this.relativeLatency = relativeLatency;
}
}