/**
* 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
*
* 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 org.apache.ambari.shell.commands;
import static org.apache.ambari.shell.support.TableRenderer.renderMultiValueMap;
import static org.apache.ambari.shell.support.TableRenderer.renderSingleMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.ambari.groovy.client.AmbariClient;
import org.apache.ambari.groovy.client.InvalidHostGroupHostAssociation;
import org.apache.ambari.shell.completion.Blueprint;
import org.apache.ambari.shell.completion.Host;
import org.apache.ambari.shell.flash.FlashService;
import org.apache.ambari.shell.model.AmbariContext;
import org.apache.ambari.shell.model.FocusType;
import org.apache.ambari.shell.model.Hints;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.shell.core.CommandMarker;
import org.springframework.shell.core.annotation.CliAvailabilityIndicator;
import org.springframework.shell.core.annotation.CliCommand;
import org.springframework.shell.core.annotation.CliOption;
import org.springframework.stereotype.Component;
import groovyx.net.http.HttpResponseException;
/**
* Cluster related commands used in the shell.
*
* @see org.apache.ambari.groovy.client.AmbariClient
*/
@Component
public class ClusterCommands implements CommandMarker {
private AmbariClient client;
private AmbariContext context;
private FlashService flashService;
private Map<String, List<String>> hostGroups;
@Autowired
public ClusterCommands(AmbariClient client, AmbariContext context, FlashService flashService) {
this.client = client;
this.context = context;
this.flashService = flashService;
}
/**
* Checks whether the cluster build command is available or not.
*
* @return true if available false otherwise
*/
@CliAvailabilityIndicator("cluster build")
public boolean isClusterBuildCommandAvailable() {
return !context.isConnectedToCluster() && !context.isFocusOnClusterBuild() && context.areBlueprintsAvailable();
}
/**
* Sets the focus on cluster building. Takes a blueprint id, if it does not exists it wont focus.
* After focus the users are able to assign hosts to host groups.
*
* @param id id of the blueprint
* @return prints the blueprint as formatted table if exists, otherwise error message
*/
@CliCommand(value = "cluster build", help = "Starts to build a cluster")
public String buildCluster(
@CliOption(key = "blueprint", mandatory = true, help = "Id of the blueprint, use 'blueprints' command to see the list") Blueprint id) {
String message;
String blueprint = id.getName();
if (client.doesBlueprintExist(blueprint)) {
context.setFocus(blueprint, FocusType.CLUSTER_BUILD);
context.setHint(Hints.ASSIGN_HOSTS);
message = String.format("%s\n%s",
renderSingleMap(client.getHostNames(), "HOSTNAME", "STATE"),
renderMultiValueMap(client.getBlueprintMap(blueprint), "HOSTGROUP", "COMPONENT"));
createNewHostGroups();
} else {
message = "Not a valid blueprint id";
}
return message;
}
/**
* Checks whether the cluster assign command is available or not.
*
* @return true if available false otherwise
*/
@CliAvailabilityIndicator("cluster assign")
public boolean isAssignCommandAvailable() {
return context.isFocusOnClusterBuild();
}
/**
* Assign hosts to host groups provided in the blueprint.
*
* @param host host to assign
* @param group which host group to
* @return status message
*/
@CliCommand(value = "cluster assign", help = "Assign host to host group")
public String assign(
@CliOption(key = "host", mandatory = true, help = "Fully qualified host name") Host host,
@CliOption(key = "hostGroup", mandatory = true, help = "Host group which to assign the host") String group) {
String message;
String hostName = host.getName();
if (client.getHostNames().keySet().contains(hostName)) {
if (addHostToGroup(hostName, group)) {
context.setHint(Hints.CREATE_CLUSTER);
message = String.format("%s has been added to %s", hostName, group);
} else {
message = String.format("%s is not a valid host group", group);
}
} else {
message = String.format("%s is not a valid hostname", hostName);
}
return message;
}
/**
* Checks whether the cluster auto command is available or not.
*
* @return true if available false otherwise
*/
@CliAvailabilityIndicator(value = "cluster autoAssign")
public boolean isClusterAutoAssignAvailable() {
return context.isFocusOnClusterBuild() && !isHostAssigned();
}
/**
* Tries to auto associate hosts to host groups.
*
* @return prints the auto assignments
*/
@CliCommand(value = "cluster autoAssign", help = "Automatically assigns hosts to different host groups base on the provided strategy")
public String autoAssign() throws InvalidHostGroupHostAssociation {
Map<String, List<String>> assignments = client.recommendAssignments(context.getFocusValue());
if (!assignments.isEmpty()) {
hostGroups = assignments;
context.setHint(Hints.CREATE_CLUSTER);
}
return showAssignments();
}
/**
* Checks whether the cluster preview command is available or not.
*
* @return true if available false otherwise
*/
@CliAvailabilityIndicator("cluster preview")
public boolean isClusterPreviewCommandAvailable() {
return context.isFocusOnClusterBuild() && isHostAssigned();
}
/**
* Shows the currently assigned hosts.
*
* @return formatted host - host group table
*/
@CliCommand(value = "cluster preview", help = "Shows the currently assigned hosts")
public String showAssignments() {
return renderMultiValueMap(hostGroups, "HOSTGROUP", "HOST");
}
/**
* Checks whether the cluster create command is available or not.
*
* @return true if available false otherwise
*/
@CliAvailabilityIndicator("cluster create")
public boolean isCreateClusterCommandAvailable() {
return context.isFocusOnClusterBuild() && isHostAssigned();
}
/**
* Creates a new cluster based on the provided host - host group associations and the selected blueprint.
* If the cluster creation fails, deletes the cluster.
*
* @return status message
*/
@CliCommand(value = "cluster create", help = "Create a cluster based on current blueprint and assigned hosts")
public String createCluster(
@CliOption(key = "exitOnFinish", mandatory = false, help = "Quits the shell when the cluster creation finishes") Boolean exit) {
String message = "Successfully created the cluster";
String blueprint = context.getFocusValue();
try {
client.createCluster(blueprint, blueprint, hostGroups);
context.setCluster(blueprint);
context.resetFocus();
context.setHint(Hints.PROGRESS);
flashService.showInstallProgress(exit == null ? false : exit);
} catch (HttpResponseException e) {
createNewHostGroups();
message = "Failed to create the cluster: " + e.getMessage();
try {
deleteCluster(blueprint);
} catch (HttpResponseException e1) {
message += ". Failed to cleanup cluster creation: " + e1.getMessage();
}
}
return message;
}
/**
* Checks whether the cluster delete command is available or not.
*
* @return true if available false otherwise
*/
@CliAvailabilityIndicator("cluster delete")
public boolean isDeleteClusterCommandAvailable() {
return context.isConnectedToCluster();
}
/**
* Deletes the cluster.
*
* @return status message
*/
@CliCommand(value = "cluster delete", help = "Delete the cluster")
public String deleteCluster() {
String message = "Successfully deleted the cluster";
try {
deleteCluster(context.getCluster());
} catch (HttpResponseException e) {
message = "Could not delete the cluster: " + e.getMessage();
}
return message;
}
/**
* Checks whether the cluster reset command is available or not.
*
* @return true if available false otherwise
*/
@CliAvailabilityIndicator(value = "cluster reset")
public boolean isClusterResetCommandAvailable() {
return context.isFocusOnClusterBuild() && isHostAssigned();
}
@CliCommand(value = "cluster reset", help = "Clears the host - host group assignments")
public void reset() {
context.setHint(Hints.ASSIGN_HOSTS);
createNewHostGroups();
}
private void deleteCluster(String id) throws HttpResponseException {
client.deleteCluster(id);
}
private void createNewHostGroups() {
Map<String, List<String>> groups = new HashMap<String, List<String>>();
for (String hostGroup : client.getHostGroups(context.getFocusValue())) {
groups.put(hostGroup, new ArrayList<String>());
}
this.hostGroups = groups;
}
private boolean addHostToGroup(String host, String group) {
boolean result = true;
List<String> hosts = hostGroups.get(group);
if (hosts == null) {
result = false;
} else {
hosts.add(host);
}
return result;
}
private boolean isHostAssigned() {
boolean result = false;
for (String group : hostGroups.keySet()) {
if (!hostGroups.get(group).isEmpty()) {
result = true;
break;
}
}
return result;
}
}