Package com.hazelcast.management

Source Code of com.hazelcast.management.ManagementCenterService$LifecycleListenerImpl

/*
* Copyright (c) 2008-2013, Hazelcast, Inc. 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 com.hazelcast.management;

import com.hazelcast.ascii.rest.HttpCommand;
import com.hazelcast.config.GroupConfig;
import com.hazelcast.config.ManagementCenterConfig;
import com.hazelcast.core.*;
import com.hazelcast.core.LifecycleEvent.LifecycleState;
import com.hazelcast.instance.GroupProperties;
import com.hazelcast.instance.HazelcastInstanceImpl;
import com.hazelcast.instance.MemberImpl;
import com.hazelcast.logging.ILogger;
import com.hazelcast.management.operation.UpdateManagementCenterUrlOperation;
import com.hazelcast.management.request.*;
import com.hazelcast.map.MapService;
import com.hazelcast.monitor.TimedMemberState;
import com.hazelcast.nio.Address;
import com.hazelcast.nio.IOUtil;
import com.hazelcast.nio.serialization.ObjectDataInputStream;
import com.hazelcast.nio.serialization.ObjectDataOutputStream;
import com.hazelcast.nio.serialization.SerializationService;
import com.hazelcast.spi.Operation;
import com.hazelcast.spi.OperationService;

import java.io.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;

import static com.hazelcast.instance.OutOfMemoryErrorDispatcher.inspectOutputMemoryError;
import static com.hazelcast.nio.IOUtil.closeResource;
import static com.hazelcast.util.StringUtil.isNullOrEmpty;

public class ManagementCenterService {

    private final static AtomicBoolean DISPLAYED_HOSTED_MANAGEMENT_CENTER_INFO = new AtomicBoolean(false);
    public static final int HTTP_SUCCESS = 200;

    private final HazelcastInstanceImpl instance;
    private final TaskPollThread taskPollThread;
    private final StateSendThread stateSendThread;
    private final ILogger logger;

    private final ConsoleCommandHandler commandHandler;
    private final ManagementCenterConfig managementCenterConfig;
    private final SerializationService serializationService;
    private final ManagementCenterIdentifier identifier;
    private final AtomicBoolean isRunning = new AtomicBoolean(false);
    private final String clusterId;
    private final String securityToken;

    private volatile String managementCenterUrl;
    private volatile boolean urlChanged = false;
    private volatile boolean versionMismatch = false;

    public ManagementCenterService(HazelcastInstanceImpl instance) {
        this.instance = instance;
        logger = instance.node.getLogger(ManagementCenterService.class);
        managementCenterConfig = getManagementCenterConfig();
        securityToken = managementCenterConfig.getSecurityToken();
        managementCenterUrl = getManagementCenterUrl();
        clusterId = getClusterId();
        commandHandler = new ConsoleCommandHandler(instance);
        taskPollThread = new TaskPollThread();
        stateSendThread = new StateSendThread();
        serializationService = instance.node.getSerializationService();
        identifier = newManagementCenterIdentifier();
        registerListeners();
        logHostedManagementCenterMessages();
    }

    private void logHostedManagementCenterMessages() {
        if (isHostedManagementCenterEnabled()) {
            if (isSecurityTokenAvailable()) {
                logHostedManagementCenterLoginUrl();
            } else {
                logHostedManagementCenterRegisterUrl();
            }
        }
    }

    private boolean isSecurityTokenAvailable() {
        return !isNullOrEmpty(managementCenterConfig.getSecurityToken());
    }

    private String getManagementCenterUrl() {
        if (isHostedManagementCenterEnabled()) {
            return getHostedManagementCenterUrl();
        } else {
            return managementCenterConfig.getUrl();
        }
    }

    private boolean isHostedManagementCenterEnabled() {
        if (!getGroupProperties().HOSTED_MANAGEMENT_ENABLED.getBoolean()) {
            return false;
        }

        return isNullOrEmpty(managementCenterConfig.getUrl());
    }

    private GroupProperties getGroupProperties() {
        return instance.node.getGroupProperties();
    }

    private String getHostedManagementCenterUrl() {
        return getGroupProperties().HOSTED_MANAGEMENT_URL.getString();
    }

    private void registerListeners() {
        if(!managementCenterConfig.isEnabled()){
            return;
        }

        instance.getLifecycleService().addLifecycleListener(new LifecycleListenerImpl());
        instance.getCluster().addMembershipListener(new MemberListenerImpl());
    }

    private void logHostedManagementCenterLoginUrl() {
        if (managementCenterConfig.isEnabled()) {
            logger.info("======================================================");
            logger.info("You can access your Hazelcast instance at:");
            logger.info(getHostedManagementCenterUrl() + "/start.do?clusterid=" + clusterId);
            logger.info("======================================================");
        } else {
            logger.info("======================================================");
            logger.info("To see your application on the Hosted Management Center, " +
                    "you need to enable the ManagementCenterConfig.");
            logger.info("======================================================");
        }
    }

    private void logHostedManagementCenterRegisterUrl() {
        //we only want to display the page for hosted management registration once. We don't want to pollute
        //the logfile.
        if (!DISPLAYED_HOSTED_MANAGEMENT_CENTER_INFO.compareAndSet(false, true)) {
            return;
        }

        logger.info("======================================================");
        logger.info("Manage your Hazelcast cluster with the Management Center SaaS Application");
        logger.info("To register, copy/paste the following url in your browser and follow the instructions.");
        logger.info(getHostedManagementCenterUrl() + "/register.jsp");
        logger.info("======================================================");
    }

    private String getClusterId() {
        String clusterId = managementCenterConfig.getClusterId();

        if(!isNullOrEmpty(clusterId)){
            return clusterId;
        }

        if (!isHostedManagementCenterEnabled()) {
            return null;
        }

        return newClusterId();
    }

    private String newClusterId() {
        IAtomicReference<String> clusterIdReference = instance.getAtomicReference("___clusterIdGenerator");
        String id = clusterIdReference.get();
        if (id == null) {
            id = UUID.randomUUID().toString().replace("-", "");
            if (!clusterIdReference.compareAndSet(null, id)) {
                id = clusterIdReference.get();
            }
        }
        return id;
    }

    private ManagementCenterConfig getManagementCenterConfig() {
        ManagementCenterConfig config = instance.node.config.getManagementCenterConfig();
        if (config == null) {
            throw new IllegalStateException("ManagementCenterConfig can't be null!");
        }
        return config;
    }

    private ManagementCenterIdentifier newManagementCenterIdentifier() {
        Address address = instance.node.address;
        String groupName = instance.getConfig().getGroupConfig().getName();
        String version = instance.node.getBuildInfo().getVersion();
        return new ManagementCenterIdentifier(version, groupName, address.getHost() + ":" + address.getPort());
    }

    private static String cleanupUrl(String url) {
        if (url == null) {
            return null;
        }

        return url.endsWith("/") ? url : url + '/';
    }

    public void start() {
        if (managementCenterUrl == null) {
            logger.warning("Can't start Hazelcast Management Center Service: web-server URL is null!");
            return;
        }

        if (!isRunning.compareAndSet(false, true)) {
            //it is already started
            return;
        }

        taskPollThread.start();
        stateSendThread.start();
        logger.info("Hazelcast will connect to Hazelcast Management Center on address: \n" + managementCenterUrl);
    }

    public void shutdown() {
        if (!isRunning.compareAndSet(true, false)) {
            //it is already shutdown.
            return;
        }

        logger.info("Shutting down Hazelcast Management Center Service");
        try {
            interruptThread(stateSendThread);
            interruptThread(taskPollThread);
        } catch (Throwable ignored) {
        }
    }

    public byte[] clusterWideUpdateManagementCenterUrl(String groupName, String groupPass, String newUrl) {
        try {
            GroupConfig groupConfig = instance.getConfig().getGroupConfig();
            if (!(groupConfig.getName().equals(groupName) && groupConfig.getPassword().equals(groupPass))) {
                return HttpCommand.RES_403;
            }

            final Collection<MemberImpl> memberList = instance.node.clusterService.getMemberList();
            for (MemberImpl member : memberList) {
                send(member.getAddress(), new UpdateManagementCenterUrlOperation(newUrl));
            }

            return HttpCommand.RES_204;
        } catch (Throwable throwable) {
            logger.warning("New Management Center url cannot be assigned.", throwable);
            return HttpCommand.RES_500;
        }
    }

    public void updateManagementCenterUrl(String newUrl) {
        if (newUrl == null) {
            return;
        }

        if (newUrl.equals(managementCenterUrl)) {
            return;
        }
        managementCenterUrl = newUrl;

        if (!isRunning()) {
            start();
        }

        urlChanged = true;
        logger.info("Management Center URL has changed. " +
                "Hazelcast will connect to Management Center on address: \n" + managementCenterUrl);
    }

    private void interruptThread(Thread t) {
        if (t != null) {
            t.interrupt();
        }
    }

    public void signalVersionMismatch() {
        versionMismatch = true;
    }

    public Object callOnAddress(Address address, Operation operation) {
        //todo: why are we always executing on the mapservice??
        OperationService operationService = instance.node.nodeEngine.getOperationService();
        Future future = operationService.invokeOnTarget(MapService.SERVICE_NAME, operation, address);
        try {
            return future.get();
        } catch (Throwable t) {
            StringWriter s = new StringWriter();
            t.printStackTrace(new PrintWriter(s));
            return s.toString();
        }
    }

    public Object callOnMember(Member member, Operation operation) {
        Address address = ((MemberImpl) member).getAddress();
        return callOnAddress(address, operation);
    }

    public void send(Address address, Operation operation) {
        //todo: clean up needed.
        OperationService operationService = instance.node.nodeEngine.getOperationService();

        operationService.createInvocationBuilder(MapService.SERVICE_NAME, operation, address).invoke();
    }

    public HazelcastInstanceImpl getHazelcastInstance() {
        return instance;
    }

    public ConsoleCommandHandler getCommandHandler() {
        return commandHandler;
    }

    private boolean isRunning() {
        return isRunning.get();
    }

    private void post(HttpURLConnection connection) throws IOException {
        //we need to call 'getResponseCode'. If we don't the data placed in the outputstream, will not be send to the
        //managementcenter. For more information see:
        //http://stackoverflow.com/questions/4844535/why-do-you-have-to-call-urlconnectiongetinputstream-to-be-able-to-write-out-to
        int responseCode = connection.getResponseCode();

        if (responseCode != HTTP_SUCCESS) {
            logger.warning("Failed to send response, responseCode:" + responseCode + " url:" + connection.getURL());
        }
    }

    private void sleepOnVersionMismatch() throws InterruptedException {
        if (versionMismatch) {
            Thread.sleep(1000 * 60);
            versionMismatch = false;
        }
    }

    private class StateSendThread extends Thread {
        private final TimedMemberStateFactory timedMemberStateFactory;
        private final int updateIntervalMs;

        private StateSendThread() {
            super(instance.getThreadGroup(), instance.node.getThreadNamePrefix("MC.State.Sender"));
            timedMemberStateFactory = new TimedMemberStateFactory(instance);
            updateIntervalMs = calcUpdateInterval();
        }

        private int calcUpdateInterval() {
            int updateInterval = managementCenterConfig.getUpdateInterval();
            return updateInterval > 0 ? updateInterval * 1000 : 5000;
        }

        @Override
        public void run() {
            try {
                while (isRunning()) {
                    sleepOnVersionMismatch();
                    sendState();
                    sleep();
                }
            } catch (Throwable throwable) {
                inspectOutputMemoryError(throwable);
                logger.warning("Hazelcast Management Center Service will be shutdown due to exception.", throwable);
                shutdown();
            }
        }

        private void sleep() throws InterruptedException {
            Thread.sleep(updateIntervalMs);
        }

        private void sendState() throws InterruptedException, MalformedURLException {
            URL url = newCollectorUrl();
            try {
                //todo: does the connection not need to be closed?
                HttpURLConnection connection = openConnection(url);
                OutputStream outputStream = connection.getOutputStream();
                try {
                    identifier.write(outputStream);
                    ObjectDataOutputStream out = serializationService.createObjectDataOutputStream(outputStream);
                    TimedMemberState timedMemberState = timedMemberStateFactory.createTimedMemberState();
                    timedMemberState.writeData(out);
                    outputStream.flush();
                    post(connection);
                } finally {
                    closeResource(outputStream);
                }
            } catch (ConnectException e) {
                if (logger.isFinestEnabled()) {
                    logger.finest(e);
                } else {
                    logger.info("Failed to connect to:" + url);
                }
            } catch (Exception e) {
                logger.warning(e);
            }
        }

        private HttpURLConnection openConnection(URL url) throws IOException {
            if (logger.isFinestEnabled()) {
                logger.finest("Opening collector connection:" + url);
            }

            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setDoOutput(true);
            connection.setRequestMethod("POST");
            connection.setConnectTimeout(5000);
            connection.setReadTimeout(5000);
            return connection;
        }

        private URL newCollectorUrl() throws MalformedURLException {
            String url = cleanupUrl(managementCenterUrl) + "collector.do";
            if (clusterId != null) {
                url += "?clusterid=" + clusterId;
            }

            if (securityToken != null) {
                if (clusterId == null) {
                    url += "?securitytoken=" + securityToken;
                } else {
                    url += "&securitytoken=" + securityToken;
                }
            }

            return new URL(url);
        }
    }

    private class TaskPollThread extends Thread {
        private final Map<Integer, Class<? extends ConsoleRequest>> consoleRequests =
                new HashMap<Integer, Class<? extends ConsoleRequest>>();
        private final Random rand = new Random();

        TaskPollThread() {
            super(instance.node.threadGroup, instance.node.getThreadNamePrefix("MC.Task.Poller"));
            register(new RuntimeStateRequest());
            register(new ThreadDumpRequest());
            register(new ExecuteScriptRequest());
            register(new EvictLocalMapRequest());
            register(new ConsoleCommandRequest());
            register(new MapConfigRequest());
            register(new MemberConfigRequest());
            register(new ClusterPropsRequest());
            register(new GetLogsRequest());
            register(new RunGcRequest());
            register(new GetMemberSystemPropertiesRequest());
            register(new GetMapEntryRequest());
            register(new VersionMismatchLogRequest());
            register(new ShutdownMemberRequest());
            register(new GetSystemWarningsRequest());
        }

        public void register(ConsoleRequest consoleRequest) {
            consoleRequests.put(consoleRequest.getType(), consoleRequest.getClass());
        }

        public void processTaskAndPostResponse(int taskId, ConsoleRequest task) {
            try {
                //todo: don't we need to close this connection?
                HttpURLConnection connection = openPostResponseConnection();
                OutputStream outputStream = connection.getOutputStream();
                try {
                    identifier.write(outputStream);
                    ObjectDataOutputStream out = serializationService.createObjectDataOutputStream(outputStream);
                    out.writeInt(taskId);
                    out.writeInt(task.getType());
                    task.writeResponse(ManagementCenterService.this, out);
                    out.flush();
                    post(connection);
                } finally {
                    closeResource(outputStream);
                }
            } catch (Exception e) {
                logger.warning("Failed process task:" + task, e);
            }
        }

        private HttpURLConnection openPostResponseConnection() throws IOException {
            URL url = newPostResponseUrl();
            if (logger.isFinestEnabled()) {
                logger.finest("Opening sendResponse connection:" + url);
            }
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setDoOutput(true);
            connection.setRequestMethod("POST");
            connection.setConnectTimeout(2000);
            connection.setReadTimeout(2000);
            return connection;
        }

        private URL newPostResponseUrl() throws MalformedURLException {
            return new URL(cleanupUrl(managementCenterUrl) + "putResponse.do");
        }

        @Override
        public void run() {
            try {
                while (isRunning()) {
                    sleepOnVersionMismatch();
                    processTask();
                    sleep();
                }
            } catch (Throwable throwable) {
                inspectOutputMemoryError(throwable);
                logger.warning("Problem on Hazelcast Management Center Service while polling for a task.", throwable);
            }
        }

        private void sleep() throws InterruptedException {
            //todo: magic numbers are no good.
            //todo: why the random part
            //todo: we want configurable frequency for task polling
            Thread.sleep(700 + rand.nextInt(300));
        }

        private void processTask() {
            ObjectDataInputStream inputStream = null;
            try {
                //todo: don't we need to close the connection?
                inputStream = openTaskInputStream();
                int taskId = inputStream.readInt();
                if (taskId <= 0) {
                    return;
                }

                ConsoleRequest task = newTask(inputStream);
                processTaskAndPostResponse(taskId, task);
            } catch (Exception e) {
                //todo: even if there is an internal error with the task, we don't see it. That is kinda shitty
                logger.finest(e);
            } finally {
                IOUtil.closeResource(inputStream);
            }
        }

        private ObjectDataInputStream openTaskInputStream() throws IOException {
            URLConnection connection = openGetTaskConnection();
            InputStream inputStream = connection.getInputStream();
            return serializationService.createObjectDataInputStream(inputStream);
        }

        private ConsoleRequest newTask(ObjectDataInputStream inputStream)
                throws InstantiationException, IllegalAccessException, IOException {

            int requestType = inputStream.readInt();

            Class<? extends ConsoleRequest> requestClass = consoleRequests.get(requestType);
            if (requestClass == null) {
                throw new RuntimeException("Failed to find a request for requestType:" + requestType);
            }

            ConsoleRequest task = requestClass.newInstance();
            task.readData(inputStream);
            return task;
        }

        private URLConnection openGetTaskConnection() throws IOException {
            URL url = newGetTaskUrl();
            if (logger.isFinestEnabled()) {
                logger.finest("Opening getTask connection:" + url);
            }

            URLConnection connection = url.openConnection();
            //todo: why do we set this property if the connection is not going to be re-used?
            connection.setRequestProperty("Connection", "keep-alive");
            return connection;
        }

        private URL newGetTaskUrl() throws MalformedURLException {
            GroupConfig groupConfig = instance.getConfig().getGroupConfig();

            Address localAddress = ((MemberImpl) instance.node.getClusterService().getLocalMember()).getAddress();

            String urlString = cleanupUrl(managementCenterUrl) + "getTask.do?member=" + localAddress.getHost()
                    + ":" + localAddress.getPort() + "&cluster=" + groupConfig.getName();

            if (clusterId != null) {
                urlString += "&clusterid=" + clusterId;
            }

            if (securityToken != null) {
                urlString += "&securitytoken=" + securityToken;
            }
            return new URL(urlString);
        }
    }


    private class LifecycleListenerImpl implements LifecycleListener {

        @Override
        public void stateChanged(final LifecycleEvent event) {
            if (event.getState() == LifecycleState.STARTED) {
                try {
                    start();
                } catch (Exception e) {
                    logger.severe("ManagementCenterService could not be started!", e);
                }
            }
        }
    }

    public class MemberListenerImpl implements MembershipListener {

        @Override
        public void memberAdded(MembershipEvent membershipEvent) {
            try {
                Member member = membershipEvent.getMember();
                if (member != null && instance.node.isMaster() && urlChanged) {
                    Operation operation = new UpdateManagementCenterUrlOperation(managementCenterUrl);
                    callOnMember(member, operation);
                }
            } catch (Exception e) {
                logger.warning("Web server url cannot be send to the newly joined member", e);
            }
        }

        @Override
        public void memberRemoved(MembershipEvent membershipEvent) {
        }

        @Override
        public void memberAttributeChanged(MemberAttributeEvent memberAttributeEvent) {
        }
    }
}
TOP

Related Classes of com.hazelcast.management.ManagementCenterService$LifecycleListenerImpl

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.