// 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.uploader.scp; import java.io.File; import java.net.URI; import java.nio.file.Paths; import java.util.logging.Level; import java.util.logging.Logger; import com.twitter.heron.common.basics.TypeUtils; import com.twitter.heron.spi.common.Config; import com.twitter.heron.spi.common.Context; import com.twitter.heron.spi.uploader.IUploader; import com.twitter.heron.spi.uploader.UploaderException; import com.twitter.heron.spi.utils.UploaderUtils; /** * Uploader for uploading topology packages to the file system of a machine in the cluster using * the scp command. * <p> * This uploader can be used to upload the topologies to a shared machine in the cluster. Then scp * command can be used to fetch the packages from this location. In case of a failure, * it will delete the topology copied to the share location. * </p> * The config values for this uploader are: * <ul> * <li>heron.class.uploader: uploader class for transferring the topology jar/tar files to storage * <li>heron.uploader.scp.command.options: This is the first part of the scp command used by the * uploader. This has to be customized to reflect the user name, hostname and ssh keys if required. * <li>heron.uploader.ssh.command.options: The ssh command that will be used to connect to * the uploading host to execute command such as delete files, make directories * <li>heron.uploader.scp.dir.path: The directory where the file will be uploaded, make sure * the user has the necessary permissions to upload the file here. * </ul> */ public class ScpUploader implements IUploader { private static final Logger LOG = Logger.getLogger(ScpUploader.class.getName()); // get the directory containing the file private String destTopologyDirectory; private Config config; private String topologyPackageLocation; private String destTopologyFile; private URI packageURI; private ScpController controller; // Utils method protected ScpController getScpController() { String scpOptions = ScpContext.scpOptions(config); String scpConnection = ScpContext.scpConnection(config); String sshOptions = ScpContext.sshOptions(config); String sshConnection = ScpContext.sshConnection(config); if (scpOptions == null) { throw new RuntimeException("Missing " + ScpContext.HERON_UPLOADER_SCP_OPTIONS + " config value"); } if (scpConnection == null) { throw new RuntimeException("Missing " + ScpContext.HERON_UPLOADER_SCP_CONNECTION + " config value"); } if (sshOptions == null) { throw new RuntimeException("Missing " + ScpContext.HERON_UPLOADER_SSH_OPTIONS + " config value"); } if (sshConnection == null) { throw new RuntimeException("Missing " + ScpContext.HERON_UPLOADER_SSH_CONNECTION + " config value"); } return new ScpController( scpOptions, scpConnection, sshOptions, sshConnection, Context.verbose(config)); } @Override public void initialize(Config ipconfig) { this.config = ipconfig; // Instantiate the scp controller this.controller = getScpController(); // get the destination directory this.destTopologyDirectory = ScpContext.uploadDirPath(config); // get the original topology package location this.topologyPackageLocation = Context.topologyPackageFile(config); // name of the destination file is the same as the base name of the topology package file String fileName = UploaderUtils.generateFilename( Context.topologyName(config), Context.role(config)); this.destTopologyFile = Paths.get(destTopologyDirectory, fileName).toString(); packageURI = TypeUtils.getURI(String.format("%s/%s", destTopologyDirectory, fileName)); } @Override public URI uploadPackage() throws UploaderException { // first, check if the topology package exists boolean fileExists = isLocalFileExists(topologyPackageLocation); if (!fileExists) { throw new UploaderException( String.format("Topology file %s does not exist.", topologyPackageLocation)); } // create the upload directory, if not exists if (!this.controller.mkdirsIfNotExists(destTopologyDirectory)) { throw new UploaderException( String.format( "Failed to create directories required for uploading the topology %s", destTopologyDirectory)); } // now copy the file if (!this.controller.copyFromLocalFile(topologyPackageLocation, destTopologyFile)) { throw new UploaderException( String.format( "Failed to upload the file from local file system to remote machine: %s -> %s.", topologyPackageLocation, destTopologyDirectory)); } LOG.log(Level.INFO, "Package URL to download: {}", packageURI.toString()); return packageURI; } // Utils method protected boolean isLocalFileExists(String file) { return new File(file).isFile(); } @Override public boolean undo() { return this.controller.delete(destTopologyFile); } @Override public void close() { } }