// Copyright 2016 Twitter. 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. package com.twitter.heron.scheduler.aurora; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Logger; import com.google.common.annotations.VisibleForTesting; import com.twitter.heron.spi.packing.PackingPlan; import com.twitter.heron.spi.utils.ShellUtils; /** * Implementation of AuroraController that shells out to the Aurora CLI to control the Aurora * scheduler workflow of a topology. */ class AuroraCLIController implements AuroraController { private static final Logger LOG = Logger.getLogger(AuroraCLIController.class.getName()); private final String jobSpec; private final boolean isVerbose; private final String auroraFilename; AuroraCLIController( String jobName, String cluster, String role, String env, String auroraFilename, boolean isVerbose) { this.auroraFilename = auroraFilename; this.isVerbose = isVerbose; this.jobSpec = String.format("%s/%s/%s/%s", cluster, role, env, jobName); } @Override public boolean createJob(Map<AuroraField, String> bindings) { List<String> auroraCmd = new ArrayList<>(Arrays.asList("aurora", "job", "create", "--wait-until", "RUNNING")); for (AuroraField field : bindings.keySet()) { auroraCmd.add("--bind"); auroraCmd.add(String.format("%s=%s", field, bindings.get(field))); } auroraCmd.add(jobSpec); auroraCmd.add(auroraFilename); if (isVerbose) { auroraCmd.add("--verbose"); } return runProcess(auroraCmd); } // Kill an aurora job @Override public boolean killJob() { List<String> auroraCmd = new ArrayList<>(Arrays.asList("aurora", "job", "killall")); auroraCmd.add(jobSpec); appendAuroraCommandOptions(auroraCmd, isVerbose); return runProcess(auroraCmd); } // Restart an aurora job @Override public boolean restart(Integer containerId) { List<String> auroraCmd = new ArrayList<>(Arrays.asList("aurora", "job", "restart")); if (containerId != null) { auroraCmd.add(String.format("%s/%d", jobSpec, containerId)); } else { auroraCmd.add(jobSpec); } appendAuroraCommandOptions(auroraCmd, isVerbose); return runProcess(auroraCmd); } @Override public void removeContainers(Set<PackingPlan.ContainerPlan> containersToRemove) { String instancesToKill = getInstancesIdsToKill(containersToRemove); //aurora job kill <cluster>/<role>/<env>/<name>/<instance_ids> List<String> auroraCmd = new ArrayList<>(Arrays.asList( "aurora", "job", "kill", jobSpec + "/" + instancesToKill)); LOG.info(String.format( "Killing %s aurora containers: %s", containersToRemove.size(), auroraCmd)); if (!runProcess(auroraCmd)) { throw new RuntimeException("Failed to kill freed aurora instances: " + instancesToKill); } } @Override public void addContainers(Integer count) { //aurora job add <cluster>/<role>/<env>/<name>/<instance_id> <count> //clone instance 0 List<String> auroraCmd = new ArrayList<>(Arrays.asList( "aurora", "job", "add", "--wait-until", "RUNNING", jobSpec + "/0", count.toString())); LOG.info(String.format("Requesting %s new aurora containers %s", count, auroraCmd)); if (!runProcess(auroraCmd)) { throw new RuntimeException("Failed to create " + count + " new aurora instances"); } } // Utils method for unit tests @VisibleForTesting boolean runProcess(List<String> auroraCmd) { StringBuilder stdout = new StringBuilder(); StringBuilder stderr = new StringBuilder(); int status = ShellUtils.runProcess(auroraCmd.toArray(new String[auroraCmd.size()]), stderr); if (status != 0) { LOG.severe(String.format( "Failed to run process. Command=%s, STDOUT=%s, STDERR=%s", auroraCmd, stdout, stderr)); } return status == 0; } private static String getInstancesIdsToKill(Set<PackingPlan.ContainerPlan> containersToRemove) { StringBuilder ids = new StringBuilder(); for (PackingPlan.ContainerPlan containerPlan : containersToRemove) { if (ids.length() > 0) { ids.append(","); } ids.append(containerPlan.getId()); } return ids.toString(); } // Static method to append verbose and batching options if needed private static void appendAuroraCommandOptions(List<String> auroraCmd, boolean isVerbose) { // Append verbose if needed if (isVerbose) { auroraCmd.add("--verbose"); } // Append batch size. // Note that we can not use "--no-batching" since "restart" command does not accept it. // So we play a small trick here by setting batch size Integer.MAX_VALUE. auroraCmd.add("--batch-size"); auroraCmd.add(Integer.toString(Integer.MAX_VALUE)); } }