package org.gcube.smartgears.handlers.application.lifecycle; import static org.gcube.common.events.Observes.Kind.resilient; import static org.gcube.smartgears.Constants.profile_management; import static org.gcube.smartgears.handlers.ProfileEvents.addToContext; import static org.gcube.smartgears.handlers.ProfileEvents.changed; import static org.gcube.smartgears.handlers.ProfileEvents.removeFromContext; import static org.gcube.smartgears.lifecycle.application.ApplicationLifecycle.activation; import static org.gcube.smartgears.lifecycle.application.ApplicationLifecycle.failure; import static org.gcube.smartgears.lifecycle.application.ApplicationLifecycle.stop; import java.util.Collections; import java.util.List; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import org.gcube.common.events.Observes; import org.gcube.common.events.Observes.Kind; import org.gcube.smartgears.Constants; import org.gcube.smartgears.configuration.Mode; import org.gcube.smartgears.context.Property; import org.gcube.smartgears.context.application.ApplicationContext; import org.gcube.smartgears.handlers.application.ApplicationLifecycleEvent; import org.gcube.smartgears.handlers.application.ApplicationLifecycleHandler; import org.gcube.smartgears.lifecycle.application.ApplicationLifecycle; import org.gcube.smartgears.lifecycle.application.ApplicationState; import org.gcube.smartgears.lifecycle.container.ContainerLifecycle; import org.gcube.smartgears.provider.ProviderFactory; import org.gcube.smartgears.publishing.Publisher; import org.gcube.smartgears.utils.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * Manages the resource profile of the application. *

* * The manager: * *

* * @author Fabio Simeoni */ public class ApplicationProfileManager extends ApplicationLifecycleHandler { Logger log = LoggerFactory.getLogger(ApplicationProfileManager.class); private ApplicationContext context; private ScheduledFuture periodicUpdates; private static final String PUBLISHED_PROP = "published"; private List publishers = ProviderFactory.provider().publishers(); @Override public void onStart(ApplicationLifecycleEvent.Start e) { context = e.context(); activated(); // note we don't fire profile events, but wait for the final startup // outcome which // will result in a state change. only then we publish and store the // profile // this avoids the redundancy and performance penalty of storing and // publishing multiple // times in rapid succession (which would be correct). Revise if proves // problematic in corner // cases. } private void activated(){ publishers = context.container().configuration().mode()!=Mode.offline? ProviderFactory.provider().publishers(): Collections.emptyList(); registerObservers(); schedulePeriodicUpdates(); } // helpers private void registerObservers() { context.events().subscribe(new Object() { @Observes({ activation, stop, failure }) void onChanged(ApplicationLifecycle lc) { log.debug("moving app {} to {}",context.name(), lc.state().remoteForm()); // since we do not know the observers, they will deal with // failures and their consequences // any that comes back will be logged in this event thread context.events().fire(context, changed); } /* @Observes(value = published) void shareAfterPublish(GCoreEndpoint profile) { share(profile); // publish may produce a new profile instance }*/ @Observes(value = changed, kind = Kind.safe) void publishAfterChange(ApplicationContext context) { //if we've failed before first publication do not try to publish //(we may well have failed there) if (!context.properties().contains(PUBLISHED_PROP)) { log.info("publishing application for the first time"); context.properties().add(new Property(PUBLISHED_PROP, true)); if (context.lifecycle().state() != ApplicationState.failed) { publishers.forEach(p -> { try { p.create(context, context.container().authorizationProvider().getContexts()); }catch (Exception e) { log.error("cannot publish {} for first time with publisher type {} (see details)",context.name(), p.getClass().getCanonicalName(), e); } }); } } else publishers.forEach(p -> { try { p.update(context); }catch (Exception e) { log.error("cannot publish {} with publisher type {} (see details)",context.name(), p.getClass().getCanonicalName(), e); } }); } }); //registering ContextObserver in container HUB context.container().events().subscribe(new Object() { @Observes(value = addToContext) void addTo(String scope) { log.info("add_to_context event arrived in app {}", context.name()); for (Publisher publisher: publishers) try { log.debug("publishing application in context {}", scope); publisher.create(context, Collections.singleton(scope)); }catch (Exception e) { log.error("cannot add context {} with publisher type {} (see details)",scope, publisher.getClass().getCanonicalName(), e); // since we've failed no published event is fired and profile // will not be stored. // we do it manually to ensure we leave some local trace of the // changed profile. //TODO: CHECK --- store(profile); } } @Observes(value = removeFromContext) void removeFrom(String scope) { log.info("remove_from_context event arrived in app {}", context.name()); for (Publisher publisher: publishers) try { log.debug("unpublishing application from scope {}", scope); publisher.remove(context, Collections.singleton(scope)); }catch (Exception e) { log.error("cannot remove scope {} with publisher type {} (see details)",scope, publisher.getClass().getCanonicalName(), e); // since we've failed no published event is fired and profile // will not be stored. // we do it manually to ensure we leave some local trace of the // changed profile. //TODO: CHECK --- store(profile); } } }); } private void schedulePeriodicUpdates() { // register to cancel updates context.events().subscribe( new Object() { // we register it in response to lifecycle events so that we can stop and resume along with application @Observes(value = { activation }, kind = resilient) synchronized void restartPeriodicUpdates(ApplicationLifecycle lc) { //already running if (periodicUpdates!=null) return; if (lc.state()==ApplicationState.active) log.info("scheduling periodic updates of application {} profile", context.name()); else log.info("resuming periodic updates of application {} profile", context.name()); final Runnable updateTask = new Runnable() { public void run() { //if handling of event generates failures these will be reported //for resilience we do not fail the application log.trace("firing change event on application {} ", context.name()); context.events().fire(context,changed); } }; periodicUpdates = Utils.scheduledServicePool.scheduleAtFixedRate(updateTask, Constants.application_republish_frequency_in_minutes, Constants.application_republish_frequency_in_minutes , TimeUnit.MINUTES); } @Observes(value = { stop, failure }, kind = resilient) synchronized void cancelPeriodicUpdates(ContainerLifecycle ignore) { if (periodicUpdates != null){ log.trace("stopping periodic updates of application {} profile", context.name()); try { periodicUpdates.cancel(true); periodicUpdates=null; } catch(Exception e) { log.warn("could not stop periodic updates of application {}", context.name(),e); } } } }); } @Override public String toString() { return profile_management; } }