Package org.springframework.xd.dirt.server

Source Code of org.springframework.xd.dirt.server.ContainerRegistrar$ContainerConnectionListener

/*
* Copyright 2014 the original author or authors.
*
* 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 org.springframework.xd.dirt.server;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.state.ConnectionState;
import org.apache.curator.utils.ThreadUtils;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.beans.BeansException;
import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.xd.dirt.cluster.Container;
import org.springframework.xd.dirt.cluster.ContainerAttributes;
import org.springframework.xd.dirt.container.store.ContainerRepository;
import org.springframework.xd.dirt.zookeeper.Paths;
import org.springframework.xd.dirt.zookeeper.ZooKeeperConnection;
import org.springframework.xd.dirt.zookeeper.ZooKeeperConnectionListener;
import org.springframework.xd.dirt.zookeeper.ZooKeeperUtils;

/**
* An instance of this class, registered as a bean in the context for a Container, will handle the registration of that
* Container's metadata with ZooKeeper by creating an ephemeral node. If the {@link ZooKeeperConnection} used by this
* registrar is closed, that ephemeral node will be eagerly deleted. Since the {@link ZooKeeperConnection} typically has
* its lifecycle managed by Spring, that would be the normal behavior when the owning {@link ApplicationContext} is
* itself closed.
*
* @author Mark Fisher
* @author David Turanski
* @author Ilayaperumal Gopinathan
*/
public class ContainerRegistrar implements ApplicationListener<ApplicationEvent>,
    ApplicationContextAware {

  /**
   * Logger.
   */
  private static final Logger logger = LoggerFactory.getLogger(ContainerRegistrar.class);


  /**
   * Metadata for the current Container.
   */
  private final ContainerAttributes containerAttributes;


  /**
   * Cache of children under the deployments path.
   */
  private volatile PathChildrenCache deployments;

  /**
   * Repository where {@link Container}s are stored.
   */
  private final ContainerRepository containerRepository;

  /**
   * The ZooKeeperConnection.
   */
  private final ZooKeeperConnection zkConnection;

  /**
   * Application context within which this registrar is defined.
   */
  private volatile ApplicationContext context;

  /*
   * Deployment listener.
   */
  private final DeploymentListener deploymentListener;

  /**
   * Namespace for management context in the container's application context.
   */
  private final static String MGMT_CONTEXT_NAMESPACE = "management";


  /**
   * Create an instance that will register the provided {@link ContainerAttributes} whenever the underlying
   * {@link ZooKeeperConnection} is established. If that connection is already established at the time this instance
   * receives a {@link ContextRefreshedEvent}, the attributes will be registered then. Otherwise, registration occurs
   * within a callback that is invoked for connected events as well as reconnected events.
   *
   * @param containerAttributes runtime and configured attributes for the container
   * @param containerRepository repository for the containers
   * @param zkConnection ZooKeeper connection
   */
  public ContainerRegistrar(ZooKeeperConnection zkConnection, ContainerAttributes containerAttributes,
      ContainerRepository containerRepository, DeploymentListener deploymentListener) {
    this.zkConnection = zkConnection;
    this.containerAttributes = containerAttributes;
    this.containerRepository = containerRepository;
    this.deploymentListener = deploymentListener;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.context = applicationContext;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void onApplicationEvent(ApplicationEvent event) {
    if (event instanceof ContextRefreshedEvent) {
      if (this.context.equals(((ContextRefreshedEvent) event).getApplicationContext())) {
        if (zkConnection.isConnected()) {
          registerWithZooKeeper(zkConnection.getClient());
        }
        zkConnection.addListener(new ContainerConnectionListener());
      }
    }
    else if (event instanceof EmbeddedServletContainerInitializedEvent) {
      String namespace = ((EmbeddedServletContainerInitializedEvent) event).getApplicationContext().getNamespace();
      // Make sure management port is updated from the ManagementServer context.
      if (MGMT_CONTEXT_NAMESPACE.equals(namespace)) {
        int managementPort = ((EmbeddedServletContainerInitializedEvent) event).getEmbeddedServletContainer().getPort();
        synchronized (containerAttributes) {
          // if the container has already been registered, update
          // the attributes in the container node; the read and
          // write operation (check if exists, attribute update)
          // must be atomic since it is possible for another thread
          // executing registerWithZooKeeper to read the attributes
          // before this thread sets the management port and therefore
          // create the node before this thread checks for its existence

          containerAttributes.setManagementPort(String.valueOf(managementPort));
          String containerId = containerAttributes.getId();

          if (zkConnection.isConnected() && containerRepository.exists(containerId)) {
            containerRepository.update(new Container(containerId, containerAttributes));
          }
        }
      }
    }
  }


  /**
   * Write the Container attributes to ZooKeeper in an ephemeral node under {@code /xd/containers}.
   */
  private void registerWithZooKeeper(CuratorFramework client) {
    try {
      String containerId = containerAttributes.getId();
      String containerPath = Paths.build(Paths.CONTAINERS, containerId);
      Stat containerPathStat = client.checkExists().forPath(containerPath);

      // If the ephemeral node for this container exists, there are two
      // possibilities:
      //   1. The ZooKeeper session for this container was temporarily
      //      suspended and has now resumed. This is usually the case
      //      when a RECONNECTED event is raised after a SUSPENDED
      //      event.
      //   2. A new ZooKeeper session has been created and the existing
      //      ephemeral node has not been cleaned up yet because the
      //      previous session has not expired yet. This may happen
      //      when a RECONNECTED event follows a SUSPENDED event
      //      if Curator decides to recreate the ZooKeeper connection
      //      in the background. This may also happen when ContainerRegistar
      //      forces a new ZooKeeper connection when a RECONNECTED event
      //      follows a LOST event; see ContainerConnectionListener.onResume.
      if (containerPathStat != null) {
        long prevSession = containerPathStat.getEphemeralOwner();
        long currSession = client.getZookeeperClient().getZooKeeper().getSessionId();
        if (prevSession == currSession) {
          // the current session still exists on the server; skip the
          // rest of the registration process
          logger.info(String.format("Existing registration for container %s with session 0x%x detected",
              containerId, currSession));
          return;
        }

        logger.info(String.format("Previous registration for container %s with " +
                "session %x detected; current session: 0x%x",
            containerId, prevSession, currSession));

        // Before proceeding, wait for the server to remove the ephemeral
        // node from the previous session
        int i = 1;
        long startTime = System.currentTimeMillis();
        while (client.checkExists().forPath(containerPath) != null) {
          logger.info("Waiting for container registration cleanup (elapsed time {} seconds)...",
              ((System.currentTimeMillis() - startTime) / 1000));
          Thread.sleep(exponentialDelay(i++, 60) * 1000);
        }
      }

      String moduleDeploymentPath = Paths.build(Paths.MODULE_DEPLOYMENTS,
          Paths.ALLOCATED, containerId);

      // if the module deployment path exists, it means this container
      // was previously connected to ZooKeeper and the admin supervisor
      // has yet to complete processing of this container's exit event;
      // therefore the container startup process will block until the
      // admin has cleaned up the module deployment path (see XD-2004)
      int i = 1;
      long startTime = System.currentTimeMillis();
      while (client.checkExists().forPath(moduleDeploymentPath) != null) {
        logger.info("Waiting for supervisor to clean up prior deployments (elapsed time {} seconds)...",
            ((System.currentTimeMillis() - startTime) / 1000));
        Thread.sleep(exponentialDelay(i++, 60) * 1000);
      }

      // create the module deployment path before the ephemeral node
      // (which is created via containerRepository) - the latter
      // makes the container "available" for module deployment
      client.create().creatingParentsIfNeeded().forPath(moduleDeploymentPath);

      synchronized (containerAttributes) {
        // reading the container attributes and writing them to
        // the container node must be an atomic operation; see
        // the handling of EmbeddedServletContainerInitializedEvent
        // in onApplicationEvent
        containerRepository.save(new Container(containerId, containerAttributes));
      }

      // if this container is rejoining, clear out the old cache and recreate
      if (deployments != null) {
        try {
          deployments.close();
        }
        catch (Exception e) {
          // this exception can be ignored; logging at trace level
          logger.trace("Exception while closing deployments cache", e);
        }
      }

      deployments = new PathChildrenCache(client, moduleDeploymentPath, true,
          ThreadUtils.newThreadFactory("DeploymentsPathChildrenCache"));
      deployments.getListenable().addListener(deploymentListener);
      deployments.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);

      logger.info("Container {} joined cluster", containerAttributes);
    }
    catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      throw ZooKeeperUtils.wrapThrowable(e);
    }
    catch (Exception e) {
      throw ZooKeeperUtils.wrapThrowable(e);
    }
  }

  /**
   * Calculate an exponential delay per the algorithm described
   * in http://en.wikipedia.org/wiki/Exponential_backoff.
   *
   * @param attempts number of failed attempts
   * @param max      the maximum amount of time to wait
   *
   * @return the amount of time to wait
   */
  private long exponentialDelay(int attempts, long max) {
    long delay = ((long) Math.pow(2, attempts) - 1) / 2;
    return Math.min(delay, max);
  }

  /**
   * The listener that triggers registration of the container attributes in a ZooKeeper node.
   */
  private class ContainerConnectionListener implements ZooKeeperConnectionListener {

    /**
     * The last known connection state. This will be {@code null} prior to the
     * initial ZooKeeper connection.
     */
    private ConnectionState lastKnownState;

    /**
     * {@inheritDoc}
     */
    @Override
    public void onConnect(CuratorFramework client) {
      lastKnownState = ConnectionState.CONNECTED;
      registerWithZooKeeper(client);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onResume(CuratorFramework client) {
      if (lastKnownState == ConnectionState.LOST) {
        // force a shutdown and restart of the client;
        // this will create a new ZooKeeper session id
        // and cause expiration of the previous session
        // after which ZooKeeper will clean up the previous
        // ephemeral nodes for this container
        logger.info("ZooKeeper connection lost; restarting connection");
        zkConnection.stop();
        zkConnection.start();
      }
      else if (lastKnownState == ConnectionState.SUSPENDED) {
        logger.info("ZooKeeper connection resumed");
        registerWithZooKeeper(client);
      }

      lastKnownState = ConnectionState.RECONNECTED;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onDisconnect(CuratorFramework client) {
      logger.warn("ZooKeeper connection terminated: {}", containerAttributes.getId());
      lastKnownState = ConnectionState.LOST;
      try {
        deployments.getListenable().removeListener(deploymentListener);
        deployments.close();
      }
      catch (Exception e) {
        logger.debug("Exception closing deployments cache", e);
      }

      deploymentListener.undeployAllModules();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onSuspend(CuratorFramework client) {
      lastKnownState = ConnectionState.SUSPENDED;
      logger.info("ZooKeeper connection suspended: {}", containerAttributes.getId());
    }
  }

}
TOP

Related Classes of org.springframework.xd.dirt.server.ContainerRegistrar$ContainerConnectionListener

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.