/* * Copyright (c) 2013 GigaSpaces Technologies Ltd. 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 beans; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; import beans.config.Conf; import beans.scripts.ScriptExecutor; import cloudify.widget.common.CloudifyOutputUtils; import cloudify.widget.common.asyncscriptexecutor.IAsyncExecution; import controllers.WidgetAdmin; import models.ServerNodeEvent; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import play.i18n.Messages; import models.ServerNode; import models.Widget; import models.Widget.Status; import models.WidgetInstance; import play.libs.Json; import server.*; import utils.CollectionUtils; import utils.Utils; import javax.annotation.PostConstruct; import javax.inject.Inject; /** * This class provides ability to deploy/undeploy new widget by apiKey. * Before that the user must create an account by WidgetAdmin and register a new widget. * * @author Igor Goldenberg * @see ServerPoolImpl * @see WidgetAdmin */ public class WidgetServerImpl implements WidgetServer { private static Logger logger = LoggerFactory.getLogger( WidgetServerImpl.class ); @Inject private ServerPool serverPool; @Inject private MailSender mailSender; @Inject private Conf conf; @Inject private DeployManager deployManager; @Inject private ScriptExecutor scriptExecutor; // for logging purposes. private Set<Long> serverNodeIds = new HashSet<Long>(); private static Map<Recipe.Type, Pattern> installationFinishedRegexMap = null; static { installationFinishedRegexMap = new HashMap<Recipe.Type, Pattern>(); for ( Recipe.Type type : Recipe.Type.values() ) { String pattern = type + " .* (installed|successfully) (installed|successfully)"; installationFinishedRegexMap.put(type, Pattern.compile( pattern, Pattern.CASE_INSENSITIVE) ); } } private List<String> filterOutputLines = new LinkedList<String>( ); private List<String> filterOutputStrings = new LinkedList<String>( ); @PostConstruct public void init(){ Utils.addAllTrimmed( filterOutputLines, StringUtils.split( conf.cloudify.removeOutputLines, "|" )); Utils.addAllTrimmed( filterOutputStrings, StringUtils.split( conf.cloudify.removeOutputString, "|" )); } @Override public void uninstall( ServerNode server ) { logger.info( "uninstalling [{}], [{}]", server, server.getWidgetInstance() ); if ( server.isRemote() ){ deployManager.uninstall( server ); }else{ undeploy( server ); } } public WidgetInstance deploy( Widget widget, ServerNode server, String remoteAddress ) { widget.countLaunch(); return deployManager.fork( server, widget ); } public void undeploy( ServerNode serverNode ) { serverPool.destroy( serverNode ); } private static boolean isFinished( Recipe.Type recipeType, String line ){ if( logger.isDebugEnabled() ){ logger.debug("checking to see if [{}] has finished using [{}]", recipeType, line ); } Pattern pattern = installationFinishedRegexMap.get(recipeType); return pattern != null && !StringUtils.isEmpty(line) && pattern.matcher(line).matches(); } @Override public Status getWidgetStatus(ServerNode server) { Status result = new Status(); List<String> output = new LinkedList<String>(); result.setOutput(output); // avoid autoboxing - causes NPEs. Long timeLeft = null; if ( server != null ){ Long timeLeftFromServer = server.getTimeLeft(); if ( timeLeftFromServer != null ){ timeLeft = timeLeftFromServer; } } // server is remote we don't count time if (server != null && !server.isRemote() && timeLeft != null) { result.setTimeleft((int) TimeUnit.MILLISECONDS.toMinutes(timeLeft)); result.setTimeleftMillis(timeLeft); } if (server == null || ( timeLeft != null && timeLeft.longValue() == 0 ) ) { logger.info("no more time left. trial time is over, settings status to STOPPED"); result.setState(Status.State.STOPPED); output.add("i18n:testDriveSuccessfullyCompleted"); return result; }else{ result.setInstanceId( Long.toString(server.getId()) ); // will need this to register users on specific nodes. } // NOTE - bootstrap and install write to the same file.. so it is enough to request only bootstrap execution. // this is not a hack, the interface is just not intuitive to suggest this. IAsyncExecution bootstrapExecution = scriptExecutor.getBootstrapExecution(server);// need to sort out the cache before we decide if the installation finished. result.setRawOutput( bootstrapExecution.getOutputAsList() ); result.setRemote( server.isRemote() ).setHasPemFile(!StringUtils.isEmpty(server.getPrivateKey())); // let UI know this is a remote bootstrap. boolean doneFromEvent = false; if ( !CollectionUtils.isEmpty(server.events) ){ for (ServerNodeEvent event : server.events) { switch ( event.getEventType() ) { case DONE: logger.debug( "detected that widget instance installation done by event" ); doneFromEvent = true; break; case ERROR: { result.setState( Status.State.STOPPED ); result.setMessage( event.getMsg() ); logWidgetInstanceError( server, result, "WidgetInstance threw an error\n"); return result; } case INFO: { output.add( event.getMsg() ); } break; default: { logger.error( "unknown event type while formatting : [{}]", event.getEventType() ); } } } } output.addAll(CloudifyOutputUtils.formatOutput( bootstrapExecution.getOutput(), server.getPrivateIP() + "]", filterOutputLines, filterOutputStrings)); logger.debug( ">> output= [{}]" , Arrays.toString( output.toArray( new String[ output.size() ] ) ) ); WidgetInstance widgetInstance = WidgetInstance.findByServerNode(server); if( logger.isDebugEnabled() ){ logger.debug("checking if installation finished for {} on the following output {}" , widgetInstance, output ); } if (widgetInstance != null ){ if (doneFromEvent || isFinished(widgetInstance.getRecipeType(), (String)CollectionUtils.last(output))){ // need to figure out the remote service IP for the link if ( server.isRemote() && StringUtils.isEmpty( widgetInstance.getServicePublicIp() ) && !StringUtils.isEmpty( widgetInstance.getWidget().getConsoleUrlService() ) ){ // find out the service's public IP. String servicePublicIp = deployManager.getServicePublicIp( widgetInstance ); if ( !StringUtils.isEmpty( servicePublicIp )){ logger.info( "found ip at : [{}]", servicePublicIp ); }else{ logger.info("could not find a public ip, defaulting to machine's public ip"); servicePublicIp = server.getPublicIP(); // default behavior. } widgetInstance.setServicePublicIp( servicePublicIp ); widgetInstance.save( ); } if( logger.isDebugEnabled() ){ logger.debug("detected finished installation"); } output.add( "i18n:installationCompletedSuccessfully" ); result.setCompleted(true); result.setInstanceIsAvailable(Boolean.TRUE); result.setConsoleLink(widgetInstance.getLink()); } } result.setState(Status.State.RUNNING); if (!StringUtils.isEmpty(server.getPublicIP())) { result.setPublicIp(server.getPublicIP()); result.setCloudifyUiIsAvailable(Boolean.TRUE); } try { if (server!= null && server.getBusySince() != null && ( System.currentTimeMillis() - server.getBusySince() > conf.cloudify.deployTimeoutError ) ) { logWidgetInstanceError(server, result, "widgetInstance is taking too long. More than ", Long.toString(conf.cloudify.deployTimeoutError), " millis \n"); } }catch(Exception e){ logger.warn("unable to decide if widget is running for too long",e); } return result; } private void logWidgetInstanceError( ServerNode serverNode, Widget.Status status, String ... prefix ){ if ( !serverNodeIds.contains( serverNode.getId() )){ serverNodeIds.add( serverNode.getId() ); StringBuilder sb = new StringBuilder(); for (String s : prefix) { sb.append(s); } sb.append( Json.stringify(Json.toJson( status )) ); logger.error(sb.toString()); } } public void setServerPool(ServerPool serverPool) { this.serverPool = serverPool; } public void setDeployManager(DeployManager deployManager) { this.deployManager = deployManager; } public void setConf( Conf conf ) { this.conf = conf; } }