package org.springframework.roo.addon.cloud.foundry; import java.io.File; import java.text.DateFormat; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.List; import java.util.Timer; import java.util.TimerTask; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.lang3.StringUtils; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.Service; import org.springframework.roo.shell.osgi.AbstractFlashingObject; import org.springframework.roo.support.logging.HandlerUtils; import com.vmware.appcloud.client.AppCloudClient; import com.vmware.appcloud.client.ApplicationStats; import com.vmware.appcloud.client.CloudApplication; import com.vmware.appcloud.client.CloudInfo; import com.vmware.appcloud.client.CloudService; import com.vmware.appcloud.client.CrashInfo; import com.vmware.appcloud.client.CrashesInfo; import com.vmware.appcloud.client.InstanceStats; import com.vmware.appcloud.client.InstancesInfo; import com.vmware.appcloud.client.ServiceConfiguration; /** * Operations for Cloud Foundry add-on. TODO Move the table rendering stuff out * to a separate class in org.sfw.shell so can be used elsewhere; feel free to * try using it in AddOnOperationsImpl (talk to him) * * @author James Tyrrell * @since 1.1.3 */ @Component @Service public class CloudFoundryOperationsImpl extends AbstractFlashingObject implements CloudFoundryOperations { private abstract class CloudCommand { protected boolean displaySuccessMessage = true; protected String failureMessage = ""; protected String gerund; protected String successMessage = ""; protected CloudCommand(final String failureMessage) { this(failureMessage, null); } protected CloudCommand(final String failureMessage, final String successMessage) { this(failureMessage, successMessage, "Performing operation"); } protected CloudCommand(final String failureMessage, final String successMessage, final String gerund) { this.failureMessage = failureMessage; this.successMessage = successMessage; this.gerund = gerund; } public abstract void execute() throws Exception; public String getFailureMessage() { return failureMessage; } public String getGerund() { return gerund; } public String getSuccessMessage() { return successMessage; } public boolean isDisplaySuccessMessage() { return displaySuccessMessage; } } private static final Logger LOGGER = HandlerUtils .getLogger(CloudFoundryOperationsImpl.class); private AppCloudClient client; @Reference private CloudFoundrySession session; public void apps() { executeCommand(new CloudCommand("The applications failed to be listed.") { @Override public void execute() throws Exception { final List<CloudApplication> applications = client .getApplications(); if (applications.isEmpty()) { LOGGER.info("No applications available."); return; } final ShellTableRenderer table = new ShellTableRenderer( "Applications", "Name", "Status", "Instances", "Services", "URLs"); for (final CloudApplication application : applications) { final StringBuilder uris = new StringBuilder(); for (int i = 0; i < application.getUris().size(); i++) { uris.append(application.getUris().get(i)); if (i < application.getUris().size() - 1) { uris.append(", "); } } final StringBuilder services = new StringBuilder(); for (final String service : application.getServices()) { services.append(service); } table.addRow(application.getName(), application.getState() .name(), String.valueOf(application.getInstances()), services.toString(), uris.toString()); } LOGGER.info(table.getOutput()); } }); } public void bindService(final String service, final String appName) { final String failureMessage = "The binding of the service '" + service + "' to the application '" + appName + "' failed"; final String successMessage = "The service '" + service + "' was successfully bound to the application '" + appName + "'"; executeCommand(new CloudCommand(failureMessage, successMessage) { @Override public void execute() throws Exception { client.bindService(appName, service); } }); } public void clearStoredLoginDetails() { session.clearStoredLoginDetails(); } public void crashes(final String appName) { executeCommand(new CloudCommand("Crashes for application '" + appName + "' could not be retrieved") { @Override public void execute() throws Exception { final CrashesInfo crashes = client.getCrashes(appName); if (crashes == null) { LOGGER.severe(failureMessage); return; } if (crashes.getCrashes().isEmpty()) { LOGGER.info("The application '" + appName + "' has never crashed"); return; } final ShellTableRenderer table = new ShellTableRenderer( "Crashes", "Name", "Id", "Since"); for (final CrashInfo crash : crashes.getCrashes()) { table.addRow(appName, crash.getInstance(), DateFormat .getDateTimeInstance().format(crash.getSince())); } LOGGER.info(table.getOutput()); } }); } public void crashLogs(final String appName, final String instance) { logs(appName, instance); } public void createService(final String service, final String name, final String bind) { final String failureMessage = "The service '" + name + "' failed to be created"; final String successMessage = "The service '" + name + "' was successfully created"; executeCommand(new CloudCommand(failureMessage, successMessage) { @Override public void execute() throws Exception { final CloudService cloudService = new CloudService(); cloudService.setName(name); cloudService.setTier("free"); final List<ServiceConfiguration> serviceConfigurations = client .getServiceConfigurations(); for (final ServiceConfiguration serviceConfiguration : serviceConfigurations) { if (serviceConfiguration.getVendor().equals(service)) { cloudService .setVendor(serviceConfiguration.getVendor()); cloudService.setType(serviceConfiguration.getType()); cloudService.setVersion(serviceConfiguration .getVersion()); } } client.createService(cloudService); } }); } public void delete(final String appName) { final String failureMessage = "The application '" + appName + "' could not be deleted"; final String successMessage = "The application '" + appName + "' was successfully deleted"; executeCommand(new CloudCommand(failureMessage, successMessage) { @Override public void execute() throws Exception { client.deleteApplication(appName); } }); } public void deleteService(final String service) { final String failureMessage = "The service '" + service + "' failed to be deleted"; final String successMessage = "The service '" + service + "' was deleted successfully"; executeCommand(new CloudCommand(failureMessage, successMessage) { @Override public void execute() throws Exception { client.deleteService(service); } }); } private void executeCommand(final CloudCommand command) { final Timer timer = new Timer(); try { final char[] statusIndicators = new char[] { '|', '/', '-', '\\' }; final int[] statusCount = new int[] { 0 }; final TimerTask timerTask = new TimerTask() { @Override public void run() { flash(Level.FINE, command.getGerund() + " " + statusIndicators[statusCount[0]], MY_SLOT); if (statusCount[0] < statusIndicators.length - 1) { statusCount[0] = statusCount[0] + 1; } else { statusCount[0] = 0; } } }; timer.scheduleAtFixedRate(timerTask, 0, 100); command.execute(); if (StringUtils.isNotBlank(command.getSuccessMessage()) && command.isDisplaySuccessMessage()) { LOGGER.info(command.getSuccessMessage()); } } catch (final Exception e) { throw new IllegalStateException(command.getFailureMessage() + " - " + e.getMessage(), e); } finally { timer.cancel(); flash(Level.FINE, "Complete!", MY_SLOT); flash(Level.FINE, "", MY_SLOT); } } public void files(final String appName, final String path, final String instance) { executeCommand(new CloudCommand("The files failed to be retrieved") { @Override public void execute() throws Exception { Integer instanceIndex = getInteger(instance); if (instanceIndex == null) { instanceIndex = 1; } final String file = client .getFile(appName, instanceIndex, path); LOGGER.info(file); } }); } private String formatDurationInSeconds(final Double seconds) { final long secondsInMinute = 60; final long secondsInHour = secondsInMinute ^ 2; final long secondsInDay = secondsInHour * 24; final StringBuilder sb = new StringBuilder(); final long days = (long) (seconds / secondsInDay); sb.append(days).append("d:"); if (days > 1) { double remainder = seconds % secondsInDay; final long hours = (long) (remainder / secondsInHour); sb.append(hours).append("h:"); remainder = remainder % secondsInHour; final long minutes = (long) (remainder / 60); sb.append(minutes).append("m:"); remainder = remainder % 60; final long secs = (long) remainder; sb.append(secs).append("s"); } return sb.toString(); } private CloudApplication getApplication(final String appName) { try { return client.getApplication(appName); } catch (final Exception ignored) { } return null; } private Integer getInteger(final String potentialInt) { if (potentialInt == null) { return null; } for (final Character c : potentialInt.toCharArray()) { if (!Character.isDigit(c)) { return null; } } return Integer.valueOf(potentialInt); } public void info() { executeCommand(new CloudCommand( "Cloud information failed to be retrieved.") { @Override public void execute() throws Exception { final CloudInfo cloudInfo = client.getCloudInfo(); if (cloudInfo == null || cloudInfo.getUsage() == null || cloudInfo.getLimits() == null) { LOGGER.warning("Information could not be retrieved"); return; } LOGGER.info("\n"); LOGGER.info(cloudInfo.getDescription()); LOGGER.info("For support visit " + cloudInfo.getSupport()); LOGGER.info("\n"); LOGGER.info("Target:\t " + client.getCloudControllerUrl() + " (" + cloudInfo.getVersion() + ")"); LOGGER.info("\n"); LOGGER.info("User:\t " + cloudInfo.getUser()); LOGGER.info("Usage:\t Memory (" + cloudInfo.getUsage().getTotalMemory() + "MB of " + cloudInfo.getLimits().getMaxTotalMemory() + "MB total)"); LOGGER.info("\t Services (" + cloudInfo.getUsage().getServices() + " of " + cloudInfo.getLimits().getMaxServices() + " total)"); LOGGER.info("\t Apps (" + cloudInfo.getUsage().getApps() + " of " + cloudInfo.getLimits().getMaxApps() + " total)"); LOGGER.info("\n"); } }); } public void instances(final String appName, final String number) { executeCommand(new CloudCommand( "Retrieving instances for application '" + appName + "' failed") { @Override public void execute() throws Exception { final Integer instances = getInteger(number); if (instances == null) { final InstancesInfo instancesInfo = client .getApplicationInstances(appName); if (instancesInfo.getInstances().isEmpty()) { LOGGER.info("No running instances for '" + appName + "'"); } } else { client.updateApplicationInstances(appName, instances); } } }); } public boolean isCloudFoundryCommandAvailable() { return client != null; } public boolean isSetupCommandAvailable() { return true; } public void login(final String email, final String password, final String cloudControllerUrl) { executeCommand(new CloudCommand("Login failed") { @Override public void execute() { session.login(email, password, cloudControllerUrl); client = session.getClient(); } }); } public void logs(final String appName, final String instance) { final String failureMessage = "The logs for application '" + appName + "' failed to be retrieved"; final String successMessage = null; executeCommand(new CloudCommand(failureMessage, successMessage, "Loading") { @Override public void execute() throws Exception { Integer instanceIndex = getInteger(instance); if (instanceIndex == null) { instanceIndex = 1; } final String stderrLog = client.getFile(appName, instanceIndex, "logs/stderr.log"); final String stdoutlog = client.getFile(appName, instanceIndex, "logs/stdout.log"); LOGGER.info("\n"); LOGGER.info("==== logs/stderr.log ===="); LOGGER.info("\n"); LOGGER.info(stderrLog); LOGGER.info("\n"); LOGGER.info("==== logs/stdout.log ===="); LOGGER.info("\n"); LOGGER.info(stdoutlog); LOGGER.info("\n"); } }); } public void map(final String appName, final String url) { final String failureMessage = "The url failed to be mapped to application '" + appName + "'"; final String successMessage = "The url was successfully mapped to application '" + appName + "'"; executeCommand(new CloudCommand(failureMessage, successMessage) { @Override public void execute() throws Exception { final CloudApplication application = getApplication(appName); if (application == null) { displaySuccessMessage = false; return; } final List<String> uris = new ArrayList<String>( application.getUris()); uris.add(url); client.updateApplicationUris(appName, uris); } }); } public void mem(final String appName, final Integer memSize) { executeCommand(new CloudCommand( "Updating the memory allocation for application '" + appName + "' failed") { @Override public void execute() throws Exception { if (memSize != null) { client.updateApplicationMemory(appName, memSize); } final ShellTableRenderer shellTable = new ShellTableRenderer( "Application Memory", "Name", "Memory"); shellTable.addRow(appName, getApplication(appName).getMemory() + "MB"); LOGGER.info(shellTable.getOutput()); } }); } public void push(final String appName, final Integer instances, Integer memory, final String path, final List<String> urls) { if (path == null) { LOGGER.severe("The file path cannot be null; cannot continue"); return; } final File fileToDeploy = new File(path); if (!fileToDeploy.exists()) { LOGGER.severe("The file at path '" + path + "' doesn't exist; cannot continue"); return; } if (memory == null) { memory = 256; } final String failureMessage = "The application '" + appName + "' could not be pushed"; final String successMessage = "The application '" + appName + "' was successfully pushed"; final Integer finalMem = memory; executeCommand(new CloudCommand(failureMessage, successMessage, "Uploading") { @Override public void execute() throws Exception { final CloudApplication cloudApplication = getApplication(appName); List<String> finalUrls = urls; if (finalUrls == null) { finalUrls = new ArrayList<String>(); finalUrls.add(appName + ".cloudfoundry.com"); } if (cloudApplication == null) { client.createApplication(appName, CloudApplication.SPRING, finalMem, finalUrls, null, false); } client.uploadApplication(appName, path); Integer finalInstances = instances; if (finalInstances == null) { finalInstances = 1; if (cloudApplication != null) { finalInstances = cloudApplication.getInstances(); } } client.updateApplicationInstances(appName, finalInstances); } }); } public void register(final String email, final String password) { final String successMessage = "Registration was successful"; final String failureMessage = "Registration failed"; executeCommand(new CloudCommand(failureMessage, successMessage) { @Override public void execute() throws Exception { client.register(email, password); } }); } public void renameApp(final String appName, final String newAppName) { final String failureMessage = "The application '" + appName + "'failed to be renamed"; final String successMessage = "The application '" + appName + "' was successfully renamed as '" + newAppName + "'"; executeCommand(new CloudCommand(failureMessage, successMessage) { @Override public void execute() throws Exception { for (final CloudApplication cloudApplication : client .getApplications()) { if (cloudApplication.getName().equals(newAppName)) { LOGGER.severe("An application of that name already exists, please choose another name"); displaySuccessMessage = false; return; } } client.rename(appName, newAppName); } }); } public void restart(final String appName) { final String failureMessage = "The application '" + appName + "' could not be restarted"; final String successMessage = "The application '" + appName + "' was successfully restarted"; executeCommand(new CloudCommand(failureMessage, successMessage) { @Override public void execute() throws Exception { client.restartApplication(appName); } }); } private double roundTwoDecimals(final double d) { final DecimalFormat twoDForm = new DecimalFormat("#.##"); return Double.valueOf(twoDForm.format(d)); } public void services() { executeCommand(new CloudCommand("Services could not be retrieved") { @Override public void execute() throws Exception { final List<ServiceConfiguration> globalServices = client .getServiceConfigurations(); final List<CloudService> localServices = client.getServices(); if (globalServices.isEmpty()) { LOGGER.info("There are currently no services available."); } else { final ShellTableRenderer table = new ShellTableRenderer( "System Services", "Service", "Version", "Description"); for (final ServiceConfiguration service : globalServices) { table.addRow(service.getVendor(), service.getVersion(), service.getDescription()); } LOGGER.info(table.getOutput()); } if (localServices.isEmpty()) { LOGGER.info("There are currently no provisioned services."); } else { final ShellTableRenderer table = new ShellTableRenderer( "Provisioned Services", "Name", "Service"); for (final CloudService service : localServices) { table.addRow(service.getName(), service.getVendor()); } LOGGER.info(table.getOutput()); } } }); } public void setup() { // TODO: This is where a cloud environment profile would be added to the // application config } public void start(final String appName) { final String failureMessage = "The application '" + appName + "' could not be started"; final String successMessage = "The application '" + appName + "' was successfully started"; executeCommand(new CloudCommand(failureMessage, successMessage, "Starting") { @Override public void execute() throws Exception { if (getApplication(appName).getState() == CloudApplication.AppState.STARTED) { LOGGER.info("Application '" + appName + "' is already running."); displaySuccessMessage = false; return; } client.startApplication(appName); } }); } public void stats(final String appName) { executeCommand(new CloudCommand("The stats for application '" + appName + "' failed to be retrieved") { @Override public void execute() throws Exception { final ApplicationStats stats = client .getApplicationStats(appName); if (stats.getRecords().isEmpty()) { LOGGER.info("There is currently no stats for the application '" + appName + "'"); return; } final ShellTableRenderer table = new ShellTableRenderer( "App. Stats", "Instance", "CPU (Cores)", "Memory (limit)", "Disk (limit)", "Uptime"); for (final InstanceStats instanceStats : stats.getRecords()) { final String instance = instanceStats.getId(); final InstanceStats.Usage usage = instanceStats.getUsage(); String cpu = "N/A"; String memory = "N/A"; String disk = "N/A"; if (usage != null) { cpu = instanceStats.getUsage().getCpu() + " (" + instanceStats.getCores() + ")"; memory = roundTwoDecimals(instanceStats.getUsage() .getMem() / 1024) + "M (" + instanceStats.getMemQuota() / (1024 * 1024) + "M)"; disk = roundTwoDecimals(instanceStats.getUsage() .getDisk() / (1024 * 1024)) + "M (" + instanceStats.getDiskQuota() / (1024 * 1024) + "M)"; } Double uptime = instanceStats.getUptime(); if (uptime == null) { uptime = 0D; } final String formattedUptime = formatDurationInSeconds(uptime); table.addRow(instance, cpu, memory, disk, formattedUptime); } LOGGER.info(table.getOutput()); } }); } public void stop(final String appName) { final String failureMessage = "The application '" + appName + "' could not be stopped"; final String successMessage = "The application '" + appName + "' was successfully stopped"; executeCommand(new CloudCommand(failureMessage, successMessage, appName) { @Override public void execute() throws Exception { if (getApplication(appName).getState() == CloudApplication.AppState.STOPPED) { LOGGER.info("Application '" + appName + "' is not running."); displaySuccessMessage = false; return; } client.stopApplication(appName); } }); } public void unbindService(final String service, final String appName) { final String failureMessage = "The unbinding of the service '" + service + "' from the application '" + appName + "' failed"; final String successMessage = "The service '" + service + "' was successfully unbound from the application '" + appName + "'"; executeCommand(new CloudCommand(failureMessage, successMessage) { @Override public void execute() throws Exception { client.unbindService(appName, service); } }); } public void unMap(final String appName, final String url) { final String failureMessage = "The url failed to be unmapped from application '" + appName + "'"; final String successMessage = "The url was successfully unmapped from application '" + appName + "'"; executeCommand(new CloudCommand(failureMessage, successMessage) { @Override public void execute() throws Exception { final CloudApplication application = getApplication(appName); if (application == null) { displaySuccessMessage = false; return; } final List<String> uris = new ArrayList<String>( application.getUris()); uris.remove(url); client.updateApplicationUris(appName, uris); } }); } public void update(final String appName) { } }