/**
* 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
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* 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.cassandra.service;
import org.apache.cassandra.concurrent.Stage;
import org.apache.cassandra.concurrent.StageManager;
import org.apache.cassandra.config.ConfigurationException;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.Column;
import org.apache.cassandra.db.IColumn;
import org.apache.cassandra.db.migration.Migration;
import org.apache.cassandra.gms.ApplicationState;
import org.apache.cassandra.gms.VersionedValue;
import org.apache.cassandra.gms.EndpointState;
import org.apache.cassandra.gms.IEndpointStateChangeSubscriber;
import org.apache.cassandra.net.Message;
import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.utils.FBUtilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOError;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public class MigrationManager implements IEndpointStateChangeSubscriber
{
private static final Logger logger = LoggerFactory.getLogger(MigrationManager.class);
/** I'm not going to act here. */
public void onJoin(InetAddress endpoint, EndpointState epState) { }
public void onChange(InetAddress endpoint, ApplicationState state, VersionedValue value)
{
if (state != ApplicationState.SCHEMA)
return;
UUID theirVersion = UUID.fromString(value.value);
rectify(theirVersion, endpoint);
}
/** gets called after a this node joins a cluster */
public void onAlive(InetAddress endpoint, EndpointState state)
{
VersionedValue value = state.getApplicationState(ApplicationState.SCHEMA);
if (value != null)
{
UUID theirVersion = UUID.fromString(value.value);
rectify(theirVersion, endpoint);
}
}
public void onDead(InetAddress endpoint, EndpointState state) { }
public void onRemove(InetAddress endpoint) { }
/** will either push or pull an updating depending on who is behind. */
public static void rectify(UUID theirVersion, InetAddress endpoint)
{
UUID myVersion = DatabaseDescriptor.getDefsVersion();
if (theirVersion.timestamp() == myVersion.timestamp())
return;
else if (theirVersion.timestamp() > myVersion.timestamp())
{
logger.debug("My data definitions are old. Asking for updates since {}", myVersion.toString());
announce(myVersion, Collections.singleton(endpoint));
}
else
{
logger.debug("Their data definitions are old. Sending updates since {}", theirVersion.toString());
pushMigrations(theirVersion, myVersion, endpoint);
}
}
/** announce my version to a set of hosts. They may culminate with them sending me migrations. */
public static void announce(UUID version, Set<InetAddress> hosts)
{
Message msg = makeVersionMessage(version);
for (InetAddress host : hosts)
MessagingService.instance.sendOneWay(msg, host);
}
/**
* gets called during startup if we notice a mismatch between the current migration version and the one saved. This
* can only happen as a result of the commit log recovering schema updates, which overwrites lastVersionId.
*
* This method silently eats IOExceptions thrown by Migration.apply() as a result of applying a migration out of
* order.
*/
public static void applyMigrations(final UUID from, final UUID to) throws IOException
{
List<Future> updates = new ArrayList<Future>();
Collection<IColumn> migrations = Migration.getLocalMigrations(from, to);
for (IColumn col : migrations)
{
final Migration migration = Migration.deserialize(col.value());
Future update = StageManager.getStage(Stage.MIGRATION).submit(new Runnable()
{
public void run()
{
try
{
migration.apply();
}
catch (ConfigurationException ex)
{
// this happens if we try to apply something that's already been applied. ignore and proceed.
logger.debug("Migration not applied " + ex.getMessage());
}
catch (IOException ex)
{
throw new RuntimeException(ex);
}
}
});
updates.add(update);
}
// wait on all the updates before proceeding.
for (Future f : updates)
{
try
{
f.get();
}
catch (InterruptedException e)
{
throw new IOException(e);
}
catch (ExecutionException e)
{
throw new IOException(e);
}
}
}
/** pushes migrations from this host to another host */
public static void pushMigrations(UUID from, UUID to, InetAddress host)
{
// I want all the rows from theirVersion through myVersion.
Collection<IColumn> migrations = Migration.getLocalMigrations(from, to);
try
{
Message msg = makeMigrationMessage(migrations);
MessagingService.instance.sendOneWay(msg, host);
}
catch (IOException ex)
{
throw new IOError(ex);
}
}
private static Message makeVersionMessage(UUID version)
{
byte[] body = version.toString().getBytes();
return new Message(FBUtilities.getLocalAddress(), StorageService.Verb.DEFINITIONS_ANNOUNCE, body);
}
// other half of transformation is in DefinitionsUpdateResponseVerbHandler.
private static Message makeMigrationMessage(Collection<IColumn> migrations) throws IOException
{
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);
dout.writeInt(migrations.size());
for (IColumn col : migrations)
{
assert col instanceof Column;
dout.writeInt(col.name().length);
dout.write(col.name());
dout.writeInt(col.value().length);
dout.write(col.value());
}
dout.close();
byte[] body = bout.toByteArray();
return new Message(FBUtilities.getLocalAddress(), StorageService.Verb.DEFINITIONS_UPDATE_RESPONSE, body);
}
// other half of this transformation is in MigrationManager.
public static Collection<Column> makeColumns(Message msg) throws IOException
{
Collection<Column> cols = new ArrayList<Column>();
DataInputStream in = new DataInputStream(new ByteArrayInputStream(msg.getMessageBody()));
int count = in.readInt();
for (int i = 0; i < count; i++)
{
byte[] name = new byte[in.readInt()];
in.readFully(name);
byte[] value = new byte[in.readInt()];
in.readFully(value);
cols.add(new Column(name, value));
}
in.close();
return cols;
}
}