All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.fabric3.federation.jgroups.JGroupsParticipantTopologyService Maven / Gradle / Ivy

The newest version!
/*
 * Fabric3
 * Copyright (c) 2009-2015 Metaform Systems
 *
 * 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.fabric3.federation.jgroups;

import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;

import org.fabric3.api.annotation.management.Management;
import org.fabric3.api.annotation.management.ManagementOperation;
import org.fabric3.api.annotation.monitor.Monitor;
import org.fabric3.api.host.runtime.HostInfo;
import org.fabric3.federation.deployment.command.ControllerAvailableCommand;
import org.fabric3.federation.deployment.command.DeploymentCommand;
import org.fabric3.federation.deployment.command.RuntimeUpdateCommand;
import org.fabric3.federation.deployment.command.RuntimeUpdateResponse;
import org.fabric3.spi.container.ContainerException;
import org.fabric3.spi.container.executor.CommandExecutor;
import org.fabric3.spi.container.executor.CommandExecutorRegistry;
import org.fabric3.spi.container.command.Command;
import org.fabric3.spi.container.command.Response;
import org.fabric3.spi.container.command.ResponseCommand;
import org.fabric3.spi.federation.topology.ControllerNotFoundException;
import org.fabric3.spi.federation.topology.MessageException;
import org.fabric3.spi.federation.topology.MessageReceiver;
import org.fabric3.spi.federation.topology.ParticipantTopologyService;
import org.fabric3.spi.federation.topology.RemoteSystemException;
import org.fabric3.spi.federation.topology.TopologyListener;
import org.fabric3.spi.federation.topology.ZoneChannelException;
import org.fabric3.spi.runtime.event.EventService;
import org.fabric3.spi.runtime.event.Fabric3EventListener;
import org.fabric3.spi.runtime.event.JoinDomain;
import org.fabric3.spi.runtime.event.RuntimeStop;
import org.jgroups.Address;
import org.jgroups.Channel;
import org.jgroups.JChannel;
import org.jgroups.Message;
import org.jgroups.View;
import org.jgroups.blocks.MessageDispatcher;
import org.jgroups.blocks.RequestOptions;
import org.jgroups.blocks.ResponseMode;
import org.jgroups.util.UUID;
import org.oasisopen.sca.annotation.EagerInit;
import org.oasisopen.sca.annotation.Init;
import org.oasisopen.sca.annotation.Property;
import org.oasisopen.sca.annotation.Reference;
import org.w3c.dom.Element;

/**
 *
 */
@EagerInit
@Management(name = "ParticipantTopologyService", path = "/runtime/federation/zone/view")
public class JGroupsParticipantTopologyService extends AbstractTopologyService implements ParticipantTopologyService {
    private static final int NOT_UPDATED = -1;
    private static final int UPDATED = 1;

    private String zoneName;
    private Element channelConfig;
    private JChannel domainChannel;
    private Fabric3EventListener joinListener;
    private Fabric3EventListener stopListener;
    private MessageDispatcher domainDispatcher;
    private boolean synchronize = true;
    private final Object viewLock;
    private TopologyListenerMultiplexer multiplexer;

    private Map channels = new ConcurrentHashMap<>();

    private int state = NOT_UPDATED;

    public JGroupsParticipantTopologyService(@Reference HostInfo info,
                                             @Reference CommandExecutorRegistry executorRegistry,
                                             @Reference EventService eventService,
                                             @Reference Executor executor,
                                             @Reference JGroupsHelper helper,
                                             @Monitor TopologyServiceMonitor monitor) {
        super(info, executorRegistry, eventService, executor, helper, monitor);
        zoneName = info.getZoneName();
        viewLock = new Object();
        multiplexer = new TopologyListenerMultiplexer(helper, viewLock);
    }

    @Property(required = false)
    public void setChannelConfig(Element config) {
        this.channelConfig = (Element) config.getElementsByTagName("config").item(0);
    }

    @Reference(required = false)
    public void setTopologyListeners(List listeners) {
        this.multiplexer.addAll(listeners);
    }

    /**
     * Property to configure whether the runtime should attempt an update by querying a zone leader or the controller. In some topologies, the runtime may pull
     * deployment information from a persistent store, which eliminates the need to update via a peer or the controller.
     *
     * @param synchronize true if the runtime should attempt an update (the default)
     */
    @Property(required = false)
    public void setSynchronize(boolean synchronize) {
        this.synchronize = synchronize;
    }

    @Init
    public void init() throws Exception {
        super.init();
        if (!synchronize) {
            state = UPDATED;
        }
        ControllerAvailableCommandExecutor executor = new ControllerAvailableCommandExecutor();
        executorRegistry.register(ControllerAvailableCommand.class, executor);

        if (channelConfig != null) {
            domainChannel = new JChannel(channelConfig);
        } else {
            domainChannel = new JChannel();
        }
        domainChannel.setName(info.getRuntimeName());
        initializeChannel(domainChannel);

        Fabric3MessageListener messageListener = new Fabric3MessageListener();
        Fabric3RequestHandler requestHandler = new Fabric3RequestHandler();
        domainDispatcher = new MessageDispatcher(domainChannel, messageListener, multiplexer, requestHandler);
    }

    public void register(TopologyListener listener) {
        multiplexer.add(listener);
    }

    public void deregister(TopologyListener listener) {
        multiplexer.remove(listener);
    }

    @ManagementOperation(description = "True if the runtime is the zone leader")
    public boolean isZoneLeader() {
        View view = domainChannel.getView();
        Address address = domainChannel.getAddress();
        return view != null && address != null && address.equals(helper.getZoneLeader(zoneName, view));
    }

    @ManagementOperation(description = "True if the controller is reachable")
    public boolean isControllerAvailable() {
        View view = domainChannel.getView();
        return view != null && helper.getController(view) != null;
    }

    @ManagementOperation(description = "The name of the zone leader")
    public String getZoneLeaderName() {
        View view = domainChannel.getView();
        if (view == null) {
            return null;
        }
        Address address = helper.getZoneLeader(zoneName, view);
        if (address == null) {
            return null;
        }
        return UUID.get(address);
    }

    public Response sendSynchronous(String runtimeName, ResponseCommand command, long timeout) throws MessageException {
        View view = domainChannel.getView();
        if (view == null) {
            throw new MessageException("Federation channel closed or not connected when sending message to: " + runtimeName);
        }
        Address address = helper.getRuntimeAddress(runtimeName, view);
        if (address == null) {
            throw new MessageException("Runtime not found: " + runtimeName);
        }
        return send(address, command, timeout);
    }

    public Response sendSynchronousToController(ResponseCommand command, long timeout) throws MessageException {
        Address controller = helper.getController(domainChannel.getView());
        if (controller == null) {
            throw new ControllerNotFoundException("Controller could not be located");
        }
        return send(controller, command, timeout);
    }

    public boolean isChannelOpen(String name) {
        return channels.containsKey(name);
    }

    public void openChannel(String name, String configuration, MessageReceiver receiver, TopologyListener listener) throws ZoneChannelException {
        if (channels.containsKey(name)) {
            throw new ZoneChannelException("Channel already open:" + name);
        }
        try {

            Channel channel;
            if (configuration != null) {
                channel = new JChannel(configuration);
            } else if (channelConfig != null) {
                channel = new JChannel(channelConfig);
            } else {
                channel = new JChannel();
            }
            channel.setName(runtimeName);
            initializeChannel(channel);
            channels.put(name, channel);

            Object viewLock = new Object();
            List listeners = Collections.singletonList(listener);
            TopologyListenerMultiplexer multiplexer = (listener != null) ? new TopologyListenerMultiplexer(helper, viewLock, listeners) : null;

            DelegatingReceiver delegatingReceiver = new DelegatingReceiver(channel, receiver, helper, multiplexer, monitor);
            channel.setReceiver(delegatingReceiver);
            channel.connect(info.getDomain().getAuthority() + ":" + name);
        } catch (Exception e) {
            throw new ZoneChannelException(e);
        }
    }

    public void closeChannel(String name) throws ZoneChannelException {
        Channel channel = channels.remove(name);
        if (channel == null) {
            throw new ZoneChannelException("Channel not found: " + name);
        }
        channel.close();
    }

    public void sendAsynchronous(String name, Serializable message) throws MessageException {
        Channel channel = channels.get(name);
        if (channel == null) {
            throw new MessageException("Channel not found: " + name);
        }
        try {
            byte[] payload = helper.serialize(message);
            Message jMessage = new Message(null, null, payload);
            channel.send(jMessage);
        } catch (Exception e) {
            throw new MessageException(e);
        }
    }

    public void sendAsynchronous(String runtimeName, String name, Serializable message) throws MessageException {
        Channel channel = channels.get(name);
        if (channel == null) {
            throw new MessageException("Channel not found: " + name);
        }
        try {
            View view = channel.getView();
            if (view == null) {
                throw new MessageException("Federation channel closed or not connected when sending message to: " + runtimeName);
            }
            Address address = helper.getRuntimeAddress(runtimeName, view);
            byte[] payload = helper.serialize(message);
            Message jMessage = new Message(address, null, payload);
            channel.send(jMessage);
        } catch (Exception e) {
            throw new MessageException(e);
        }
    }

    Fabric3EventListener getJoinListener() {
        if (joinListener == null) {
            joinListener = new JoinEventListener();
        }
        return joinListener;
    }

    Fabric3EventListener getStopListener() {
        if (stopListener == null) {
            stopListener = new RuntimeStopEventListener();
        }
        return stopListener;
    }

    @Override
    JChannel getDomainChannel() {
        return domainChannel;
    }

    private Response send(Address address, Command command, long timeout) throws MessageException {
        try {
            Address sourceAddress = domainChannel.getAddress();
            byte[] payload = helper.serialize(command);
            Message message = new Message(address, sourceAddress, payload);
            RequestOptions options = new RequestOptions(ResponseMode.GET_ALL, timeout);
            Object val = domainDispatcher.sendMessage(message, options);
            assert val instanceof byte[] : " expected byte[] for response";
            return (Response) helper.deserialize(((byte[]) val));
        } catch (Exception e) {
            throw new MessageException("Error sending message to: " + runtimeName, e);
        }
    }

    /**
     * Attempts to update the runtime with the current set of deployments for the zone. The zone leader (i.e. oldest runtime in the zone) is queried for the
     * deployment commands. If the zone leader is unavailable or has not been updated, the controller is queried.
     *
     * @throws MessageException if an error is encountered during update
     */
    private void update() throws MessageException {
        // send the sync request
        View view = domainChannel.getView();
        Address address = helper.getZoneLeader(zoneName, view);
        if (address != null && !domainChannel.getAddress().equals(address)) {
            // check if current runtime is the zone leader - if not, attempt to retrieve cached deployment from it
            try {
                if (update(address)) {
                    return;
                }
            } catch (MessageException e) {
                monitor.error("Error retrieving deployment from zone leader: " + zoneName, e);
            }
        }
        // check the controller
        address = helper.getController(view);
        if (address == null) {
            // controller is not present
            monitor.updateDeferred();
            return;
        }
        update(address);
    }

    /**
     * Performs the actual runtime update by querying the given runtime address.
     *
     * @param address the runtime address
     * @return true if the runtime was updated
     * @throws MessageException if an error is encountered during update
     */
    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
    private boolean update(Address address) throws MessageException {
        String name = UUID.get(address);
        monitor.updating(name);
        RuntimeUpdateCommand command = new RuntimeUpdateCommand(runtimeName, zoneName, null);
        Response response = send(address, command, defaultTimeout);
        if (response instanceof RemoteSystemException) {
            RemoteSystemException exception = (RemoteSystemException) response;
            throw new MessageException("Remote system exception from " + exception.getRuntimeName() + ": " + exception.getException().getMessage());
        } else if (!(response instanceof RuntimeUpdateResponse)) {
            throw new MessageException("Unknown response type: " + response.getClass());
        }
        RuntimeUpdateResponse updateResponse = (RuntimeUpdateResponse) response;
        if (!updateResponse.isUpdated()) {
            // not updated, wait until a controller becomes available
            return false;
        }
        // mark synchronized here to avoid multiple retries in case a deployment error is encountered
        state = UPDATED;
        try {
            DeploymentCommand deploymentCommand = updateResponse.getDeploymentCommand();
            executorRegistry.execute(deploymentCommand);
        } catch (ContainerException e) {
            throw new MessageException(e);
        }
        monitor.updated();
        return true;
    }

    class JoinEventListener implements Fabric3EventListener {

        public void onEvent(JoinDomain event) {
            try {
                domainChannel.connect(domainName);
                domainDispatcher.start();
                monitor.joinedDomain(runtimeName);
                if (synchronize) {
                    while (domainChannel.getView() == null) {
                        try {
                            // Wait until the first view is available. Notification will happen when the ZoneMemberListener is called on a
                            // different thread.
                            viewLock.wait(defaultTimeout);
                        } catch (InterruptedException e) {
                            monitor.error("Timeout attempting to join the domain", e);
                            Thread.currentThread().interrupt();
                            return;
                        }
                    }
                    update();
                }
            } catch (Exception e) {
                monitor.error("Error joining the domain", e);
            }
        }
    }

    class RuntimeStopEventListener implements Fabric3EventListener {

        public void onEvent(RuntimeStop event) {
            if (domainDispatcher != null) {
                domainDispatcher.stop();
            }
            if (domainChannel != null && domainChannel.isConnected()) {
                domainChannel.disconnect();
                domainChannel.close();
            }
            for (Channel channel : channels.values()) {
                if (channel.isConnected()) {
                    channel.disconnect();
                    channel.close();
                }
            }
        }
    }

    private class ControllerAvailableCommandExecutor implements CommandExecutor {

        public void execute(ControllerAvailableCommand command) throws ContainerException {
            if (UPDATED == state) {
                return;
            }
            try {
                // A controller is now available and this runtime has not been synchronized. This can happen when the first member in a zone becomes
                // available before a controller.
                View view = domainChannel.getView();
                Address controller = helper.getController(view);
                update(controller);
            } catch (MessageException e) {
                monitor.error("Error updating the runtime", e);
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy