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

com.bazaarvoice.emodb.table.db.astyanax.MoveTableTask Maven / Gradle / Ivy

package com.bazaarvoice.emodb.table.db.astyanax;

import com.bazaarvoice.emodb.common.api.impl.LimitCounter;
import com.bazaarvoice.emodb.common.dropwizard.task.TaskRegistry;
import com.bazaarvoice.emodb.common.json.JsonHelper;
import com.bazaarvoice.emodb.sor.api.AuditBuilder;
import com.bazaarvoice.emodb.sor.api.FacadeOptions;
import com.bazaarvoice.emodb.sor.api.TableOptions;
import com.bazaarvoice.emodb.table.db.MoveType;
import com.bazaarvoice.emodb.table.db.Table;
import com.bazaarvoice.emodb.table.db.TableDAO;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.base.Predicates;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
import com.google.inject.Inject;
import io.dropwizard.servlets.tasks.Task;

import javax.annotation.Nullable;
import java.io.PrintWriter;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static java.lang.String.format;

/**
 * Shows the current scheduled table maintenance (for move and drop operations) and allows scheduling table moves,
 * changing placement and #shards.
 * 

* Invoke this task as follows to see what maintenance is pending locally and across the cluster: *

 *   curl -s -XPOST http://localhost:8081/tasks/sor-move
 *   curl -s -XPOST http://localhost:8081/tasks/blob-move
 * 
*

* To move a table to a new placement execute something like this: *

 *   curl -s -XPOST "http://localhost:8081/tasks/sor-move?table=review:testcustomer&dest=ugc_global:ugc"
 * 
* To change the number of shards for a facade execute something like this (note "src" is required for facades to * identify which facade to move, and "dest" placement is required even though the placement isn't changing): *
 *   curl -s -XPOST "http://localhost:8081/tasks/sor-move?facade=review:testcustomer&src=ugc_us:ugc&dest=ugc_us:ugc&shards=16"
 * 
* To move a whole table placement to a new placement execute something like this: *
 *   curl -s -XPOST "http://localhost:8081/tasks/blob-move?placement=media_global:ugc&dest=media_global:media"
 * 
* In practice, this would be used when separating a placement to its own dedicated Cassandra cluster *

* Query parameters: *
*
table
Zero or more tables to move.
*
facade
Zero or more facades to move.
*
src
Source placement when moving facades.
*
dest
Destination placement when moving tables and/or facades.
*
shards
Optional number of shards to use for the destination, if specified overrides the normal * default. This must be a power of two between 1 and 256.
*
localonly
Boolean (true|false) specifies whether to show maintenance for the entire cluster (which * can be slow to compute) or just maintenance scheduled on the local node.
*
*/ public class MoveTableTask extends Task { private final TableDAO _tableDao; private final Map _placementsUnderMove; private final MaintenanceDAO _maintDao; private volatile Reference _scheduler = new WeakReference<>(null); @Inject public MoveTableTask(TaskRegistry taskRegistry, @Maintenance String scope, TableDAO tableDao, MaintenanceDAO maintDao, @PlacementsUnderMove Map placementsUnderMove) { super(scope + "-move"); _tableDao = checkNotNull(tableDao, "tableDAO"); _maintDao = checkNotNull(maintDao, "maintDao"); _placementsUnderMove = checkNotNull(placementsUnderMove, "placementsUnderMove"); taskRegistry.addTask(this); } void setScheduler(MaintenanceScheduler scheduler) { // Called when the scheduler wins the leadership election. Holds a weak reference so we don't have // to watch for when leadership is lost. _scheduler = new WeakReference<>(scheduler); } @Nullable private MaintenanceScheduler getMaintenanceScheduler() { MaintenanceScheduler scheduler = _scheduler.get(); return scheduler != null && scheduler.isRunningOrStarting() ? scheduler : null; } @Override public void execute(ImmutableMultimap parameters, PrintWriter out) throws Exception { // Start table and facade moves as specified in query parameters. String srcPlacement = Iterables.getFirst(parameters.get("src"), null); String destPlacement = Iterables.getFirst(parameters.get("dest"), null); String movePlacement = Iterables.getFirst(parameters.get("placement"), null); Optional numShards = parseNumShards(Iterables.getFirst(parameters.get("shards"), null)); checkArgument(destPlacement == null || _tableDao.isMoveToThisPlacementAllowed(destPlacement), "Move to this placement is not allowed since it is currently on move."); moveTables(parameters.get("table"), destPlacement, numShards, out, MoveType.SINGLE_TABLE); moveFacades(parameters.get("facade"), srcPlacement, destPlacement, numShards, out, MoveType.SINGLE_TABLE); movePlacement(movePlacement, destPlacement, numShards, out); // Print currently scheduled table maintenance. boolean localOnly = Boolean.valueOf(Iterables.getFirst(parameters.get("localonly"), "false")); Map.Entry running = getLocallyRunningMaintenance(); Map localMap = getLocallyScheduledMaintenance(); Map remoteMap = Collections.emptyMap(); if (!localOnly) { // Remote maintenance == ALL - LOCAL - RUNNING. remoteMap = Maps.filterEntries( Maps.difference(getGloballyScheduledMaintenance(), localMap).entriesOnlyOnLeft(), Predicates.not(Predicates.equalTo(running))); } if (running != null) { printMaintenance(running.getKey(), running.getValue(), "RUNNING", out); } printMaintenance(localMap, "LOCAL", out); printMaintenance(remoteMap, "REMOTE", out); if (running == null && localMap.isEmpty() && remoteMap.isEmpty()) { if (localOnly) { out.println("No local table maintenance is scheduled."); } else { out.println("No table maintenance is scheduled."); } } } private void printMaintenance(Map map, String state, PrintWriter out) { // Sort the maintenance operations by date then table name. List> entries = new Ordering>() { @Override public int compare(Map.Entry left, Map.Entry right) { return ComparisonChain.start() .compare(left.getValue(), right.getValue()) .compare(left.getKey(), right.getKey()) .result(); } }.immutableSortedCopy(map.entrySet()); for (Map.Entry entry : entries) { printMaintenance(entry.getKey(), entry.getValue(), state, out); } } private void printMaintenance(String table, MaintenanceOp op, String state, PrintWriter out) { out.printf("[%s] %s: type=%s dc=%s op=%s table=%s%n", state, JsonHelper.formatTimestamp(op.getWhen().getMillis()), op.getType(), op.getDataCenter(), op.getName(), table); } private Map.Entry getLocallyRunningMaintenance() { MaintenanceScheduler scheduler = getMaintenanceScheduler(); return scheduler != null ? scheduler.getRunningMaintenance() : null; } private Map getLocallyScheduledMaintenance() { MaintenanceScheduler scheduler = getMaintenanceScheduler(); return scheduler != null ? scheduler.getScheduledMaintenance() : Collections.emptyMap(); } private Map getGloballyScheduledMaintenance() { return toMap(_maintDao.listMaintenanceOps()); } private void moveTables(Collection tables, String destPlacement, Optional numShards, PrintWriter out, MoveType moveType) { if (!tables.isEmpty() && destPlacement == null) { out.println("The 'dest' placement query parameter is required when moving tables."); return; } for (String table : tables) { out.printf("Moving table %s to placement %s...%n", table, destPlacement); try { _tableDao.move(table, destPlacement, numShards, new AuditBuilder().build(), moveType); } catch (Exception e) { out.printf("ERROR moving table %s to placement %s: ", table, destPlacement); e.printStackTrace(out); } } } private void moveFacades(Collection facades, String srcPlacement, String destPlacement, Optional numShards, PrintWriter out, MoveType moveType) { if (!facades.isEmpty() && (srcPlacement == null || destPlacement == null)) { out.println("The 'src' and 'dest' placement query parameters are required when moving facades."); return; } for (String facade : facades) { out.printf("Moving facade %s to placement %s...%n", facade, destPlacement); try { _tableDao.moveFacade(facade, srcPlacement, destPlacement, numShards, new AuditBuilder().build(), moveType); } catch (Exception e) { out.printf("ERROR moving facade %s to placement %s: ", facade, destPlacement); e.printStackTrace(out); } } } private void movePlacement(String placement, String destPlacement, Optional numShards, PrintWriter out) { if (placement == null) { return; } if (destPlacement == null) { out.println("The 'dest' placement query parameter is required when moving placement."); return; } if (!Objects.equal(_placementsUnderMove.get(placement), destPlacement)) { out.println("The 'dest' placement should be configured as destination for the source placement"); return; } MovePlacement movePlacement = getTablesAndFacadesInSrcPlacement(placement); moveTables(movePlacement.getTables(), destPlacement, numShards, out, MoveType.FULL_PLACEMENT); moveFacades(movePlacement.getFacades(), placement, destPlacement, numShards, out, MoveType.FULL_PLACEMENT); } private Optional parseNumShards(String numShards) { return Optional.fromNullable(numShards != null ? Integer.parseInt(numShards) : null); } private Map toMap(Iterator> iter) { Map map = Maps.newLinkedHashMap(); while (iter.hasNext()) { Map.Entry entry = iter.next(); map.put(entry.getKey(), entry.getValue()); } return map; } @VisibleForTesting protected MovePlacement getTablesAndFacadesInSrcPlacement(String placement) { MovePlacement movePlacement = new MovePlacement(); // Get all tables for the placement Iterator allTables = _tableDao.list(null, new LimitCounter(12345678)); while (allTables.hasNext()) { Table table = allTables.next(); // Make sure there are no tables that are currently moving into the placement. String readPlacement = ((AstyanaxTable) table).getReadStorage().getPlacementName(); for(AstyanaxStorage storage : ((AstyanaxTable) table).getWriteStorage()) { String writePlacement = storage.getPlacementName(); checkState(readPlacement.equals(writePlacement) || !writePlacement.equals(placement), format("%s table is moving into the source placement. " + "Can't move placement if tables are moving into it.", table.getName())); } TableOptions options = table.getOptions(); // See if its master placement or any of its facades match the source placement if (options.getPlacement().equals(placement)) { movePlacement.getTables().add(table.getName()); } else { // See if any of the facades match the source placement for (FacadeOptions facadeOptions : options.getFacades()) { if (facadeOptions.getPlacement().equals(placement)) { movePlacement.getFacades().add(table.getName()); } } } } return movePlacement; } @VisibleForTesting public class MovePlacement { private Set _tables = Sets.newHashSet(); private Set _facades = Sets.newHashSet(); public Set getTables() { return _tables; } public Set getFacades() { return _facades; } } }