PageRenderTime 47ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/src/java/org/apache/cassandra/service/MigrationManager.java

https://github.com/yuvrajm/cassandra
Java | 243 lines | 177 code | 21 blank | 45 comment | 16 complexity | ab982e4fb29538eade40b6f84f100c1c MD5 | raw file
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. * <p/>
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. * <p/>
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. package org.apache.cassandra.service;
  19. import java.io.*;
  20. import java.net.InetAddress;
  21. import java.nio.ByteBuffer;
  22. import java.util.*;
  23. import java.util.concurrent.ExecutionException;
  24. import java.util.concurrent.Future;
  25. import java.util.concurrent.TimeUnit;
  26. import com.google.common.collect.Iterables;
  27. import com.google.common.collect.MapMaker;
  28. import org.apache.cassandra.config.Schema;
  29. import org.slf4j.Logger;
  30. import org.slf4j.LoggerFactory;
  31. import org.apache.cassandra.concurrent.Stage;
  32. import org.apache.cassandra.concurrent.StageManager;
  33. import org.apache.cassandra.config.ConfigurationException;
  34. import org.apache.cassandra.db.Column;
  35. import org.apache.cassandra.db.IColumn;
  36. import org.apache.cassandra.db.marshal.TimeUUIDType;
  37. import org.apache.cassandra.db.migration.Migration;
  38. import org.apache.cassandra.gms.*;
  39. import org.apache.cassandra.io.util.FastByteArrayInputStream;
  40. import org.apache.cassandra.io.util.FastByteArrayOutputStream;
  41. import org.apache.cassandra.net.Message;
  42. import org.apache.cassandra.net.MessagingService;
  43. import org.apache.cassandra.utils.ByteBufferUtil;
  44. import org.apache.cassandra.utils.FBUtilities;
  45. public class MigrationManager implements IEndpointStateChangeSubscriber
  46. {
  47. private static final Logger logger = LoggerFactory.getLogger(MigrationManager.class);
  48. // avoids re-pushing migrations that we're waiting on target to apply already
  49. private static Map<InetAddress,UUID> lastPushed = new MapMaker().expiration(1, TimeUnit.MINUTES).makeMap();
  50. public void onJoin(InetAddress endpoint, EndpointState epState) {
  51. VersionedValue value = epState.getApplicationState(ApplicationState.SCHEMA);
  52. if (value != null)
  53. {
  54. UUID theirVersion = UUID.fromString(value.value);
  55. rectify(theirVersion, endpoint);
  56. }
  57. }
  58. public void onChange(InetAddress endpoint, ApplicationState state, VersionedValue value)
  59. {
  60. if (state != ApplicationState.SCHEMA)
  61. return;
  62. UUID theirVersion = UUID.fromString(value.value);
  63. rectify(theirVersion, endpoint);
  64. }
  65. /** gets called after a this node joins a cluster */
  66. public void onAlive(InetAddress endpoint, EndpointState state)
  67. {
  68. VersionedValue value = state.getApplicationState(ApplicationState.SCHEMA);
  69. if (value != null)
  70. {
  71. UUID theirVersion = UUID.fromString(value.value);
  72. rectify(theirVersion, endpoint);
  73. }
  74. }
  75. public void onDead(InetAddress endpoint, EndpointState state) { }
  76. public void onRestart(InetAddress endpoint, EndpointState state) { }
  77. public void onRemove(InetAddress endpoint) { }
  78. /**
  79. * will either push or pull an updating depending on who is behind.
  80. * fat clients should never push their schemas (since they have no local storage).
  81. */
  82. public static void rectify(UUID theirVersion, InetAddress endpoint)
  83. {
  84. UUID myVersion = Schema.instance.getVersion();
  85. if (theirVersion.timestamp() < myVersion.timestamp()
  86. && !StorageService.instance.isClientMode())
  87. {
  88. if (lastPushed.get(endpoint) == null || theirVersion.timestamp() >= lastPushed.get(endpoint).timestamp())
  89. {
  90. logger.debug("Schema on {} is old. Sending updates since {}", endpoint, theirVersion);
  91. Collection<IColumn> migrations = Migration.getLocalMigrations(theirVersion, myVersion);
  92. pushMigrations(endpoint, migrations);
  93. lastPushed.put(endpoint, TimeUUIDType.instance.compose(Iterables.getLast(migrations).name()));
  94. }
  95. else
  96. {
  97. logger.debug("Waiting for {} to process migrations up to {} before sending more",
  98. endpoint, lastPushed.get(endpoint));
  99. }
  100. }
  101. }
  102. private static void pushMigrations(InetAddress endpoint, Collection<IColumn> migrations)
  103. {
  104. try
  105. {
  106. Message msg = makeMigrationMessage(migrations, Gossiper.instance.getVersion(endpoint));
  107. MessagingService.instance().sendOneWay(msg, endpoint);
  108. }
  109. catch (IOException ex)
  110. {
  111. throw new IOError(ex);
  112. }
  113. }
  114. /** actively announce a new version to active hosts via rpc */
  115. public static void announce(IColumn column)
  116. {
  117. Collection<IColumn> migrations = Collections.singleton(column);
  118. for (InetAddress endpoint : Gossiper.instance.getLiveMembers())
  119. pushMigrations(endpoint, migrations);
  120. }
  121. /** announce my version passively over gossip **/
  122. public static void passiveAnnounce(UUID version)
  123. {
  124. // this is for notifying nodes as they arrive in the cluster.
  125. Gossiper.instance.addLocalApplicationState(ApplicationState.SCHEMA, StorageService.instance.valueFactory.migration(version));
  126. logger.debug("Gossiping my schema version " + version);
  127. }
  128. /**
  129. * gets called during startup if we notice a mismatch between the current migration version and the one saved. This
  130. * can only happen as a result of the commit log recovering schema updates, which overwrites lastVersionId.
  131. *
  132. * This method silently eats IOExceptions thrown by Migration.apply() as a result of applying a migration out of
  133. * order.
  134. */
  135. public static void applyMigrations(final UUID from, final UUID to) throws IOException
  136. {
  137. List<Future<?>> updates = new ArrayList<Future<?>>();
  138. Collection<IColumn> migrations = Migration.getLocalMigrations(from, to);
  139. for (IColumn col : migrations)
  140. {
  141. // assuming MessagingService.version_ is a bit of a risk, but you're playing with fire if you purposefully
  142. // take down a node to upgrade it during the middle of a schema update.
  143. final Migration migration = Migration.deserialize(col.value(), MessagingService.version_);
  144. Future<?> update = StageManager.getStage(Stage.MIGRATION).submit(new Runnable()
  145. {
  146. public void run()
  147. {
  148. try
  149. {
  150. migration.apply();
  151. }
  152. catch (ConfigurationException ex)
  153. {
  154. // this happens if we try to apply something that's already been applied. ignore and proceed.
  155. logger.debug("Migration not applied " + ex.getMessage());
  156. }
  157. catch (IOException ex)
  158. {
  159. throw new RuntimeException(ex);
  160. }
  161. }
  162. });
  163. updates.add(update);
  164. }
  165. // wait on all the updates before proceeding.
  166. for (Future<?> f : updates)
  167. {
  168. try
  169. {
  170. f.get();
  171. }
  172. catch (InterruptedException e)
  173. {
  174. throw new IOException(e);
  175. }
  176. catch (ExecutionException e)
  177. {
  178. throw new IOException(e);
  179. }
  180. }
  181. passiveAnnounce(to); // we don't need to send rpcs, but we need to update gossip
  182. }
  183. // other half of transformation is in DefinitionsUpdateResponseVerbHandler.
  184. private static Message makeMigrationMessage(Collection<IColumn> migrations, int version) throws IOException
  185. {
  186. FastByteArrayOutputStream bout = new FastByteArrayOutputStream();
  187. DataOutputStream dout = new DataOutputStream(bout);
  188. dout.writeInt(migrations.size());
  189. // riddle me this: how do we know that these binary values (which contained serialized row mutations) are compatible
  190. // with the destination? Further, since these migrations may be old, how do we know if they are compatible with
  191. // the current version? The bottom line is that we don't. For this reason, running migrations from a new node
  192. // to an old node will be a crap shoot. Pushing migrations from an old node to a new node should work, so long
  193. // as the oldest migrations are only one version old. We need a way of flattening schemas so that this isn't a
  194. // problem during upgrades.
  195. for (IColumn col : migrations)
  196. {
  197. assert col instanceof Column;
  198. ByteBufferUtil.writeWithLength(col.name(), dout);
  199. ByteBufferUtil.writeWithLength(col.value(), dout);
  200. }
  201. dout.close();
  202. byte[] body = bout.toByteArray();
  203. return new Message(FBUtilities.getBroadcastAddress(), StorageService.Verb.DEFINITIONS_UPDATE, body, version);
  204. }
  205. // other half of this transformation is in MigrationManager.
  206. public static Collection<Column> makeColumns(Message msg) throws IOException
  207. {
  208. Collection<Column> cols = new ArrayList<Column>();
  209. DataInputStream in = new DataInputStream(new FastByteArrayInputStream(msg.getMessageBody()));
  210. int count = in.readInt();
  211. for (int i = 0; i < count; i++)
  212. {
  213. byte[] name = new byte[in.readInt()];
  214. in.readFully(name);
  215. byte[] value = new byte[in.readInt()];
  216. in.readFully(value);
  217. cols.add(new Column(ByteBuffer.wrap(name), ByteBuffer.wrap(value)));
  218. }
  219. in.close();
  220. return cols;
  221. }
  222. }