/** * Copyright 2012 Comcast Corporation * * 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.comcast.cns.controller; import java.lang.management.ManagementFactory; import java.net.InetAddress; import java.util.HashMap; import java.util.Map; import java.util.UUID; import java.util.concurrent.atomic.AtomicLong; import javax.management.MBeanServer; import javax.management.ObjectName; import javax.servlet.AsyncContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import org.apache.log4j.Logger; import com.comcast.cmb.common.controller.Action; import com.comcast.cmb.common.controller.CMBControllerServlet; import com.comcast.cmb.common.controller.HealthCheckShallow; import com.comcast.cmb.common.model.CMBPolicy; import com.comcast.cmb.common.model.User; import com.comcast.cmb.common.persistence.AbstractDurablePersistence; import com.comcast.cmb.common.persistence.AbstractDurablePersistence.CMB_SERIALIZER; import com.comcast.cmb.common.persistence.DurablePersistenceFactory; import com.comcast.cmb.common.persistence.PersistenceFactory; import com.comcast.cmb.common.util.CMBErrorCodes; import com.comcast.cmb.common.util.CMBException; import com.comcast.cmb.common.util.CMBProperties; import com.comcast.cns.model.CNSTopicAttributes; import com.comcast.cns.persistence.ICNSSubscriptionPersistence; import com.comcast.cns.persistence.ICNSTopicPersistence; /** * Servlet for handling all CNS actions * @author vvenkatraman, jorge, michael, bwolf, aseem, baosen */ public class CNSControllerServlet extends CMBControllerServlet { private static final long serialVersionUID = 1L; protected static volatile ICNSTopicPersistence topicHandler; private static volatile ICNSSubscriptionPersistence subscriptionHandler; private static volatile HashMap<String, Action> actionMap; public static volatile AtomicLong lastCNSPingMinute = new AtomicLong(0); private static Logger logger = Logger.getLogger(CNSControllerServlet.class); private static final String CNS_API_SERVERS = "CNSAPIServers"; /** * NodeName global constant is used to identify this process uniquely across all API servers * and is used to identify creators of recovery logs */ private static volatile String NodeName = UUID.randomUUID().toString(); public static String getNodeName() { return NodeName; } /** * Used by unit tests to ensure we can start with a fresh baseline for testing recovery * and overflow logic */ public static void resetNodeName() { NodeName = UUID.randomUUID().toString(); } /** * Default constructor. */ public CNSControllerServlet() { } protected boolean isValidAction(String action) throws ServletException { if (action == null) { return false; } if (actionMap == null) { init(); } return actionMap.containsKey(action); } @Override public void init() throws ServletException { super.init(); try { MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); ObjectName name = new ObjectName("com.comcast.cns.controller:type=CNSMonitorMBean"); if (!mbs.isRegistered(name)) { mbs.registerMBean(CNSMonitor.getInstance(), name); } } catch (Exception ex) { logger.warn("event=failed_to_register_monitor", ex); } topicHandler = PersistenceFactory.getTopicPersistence(); subscriptionHandler = PersistenceFactory.getSubscriptionPersistence(); final CNSConfirmSubscriptionAction confirmSubscription = new CNSConfirmSubscriptionAction(); final CNSPublishAction publish = new CNSPublishAction(); final CNSCreateTopicAction createTopic = new CNSCreateTopicAction(); final CNSDeleteTopicAction deleteTopic = new CNSDeleteTopicAction(); final CNSListTopicsAction listTopics = new CNSListTopicsAction(); final CNSSubscribeAction subscribe = new CNSSubscribeAction(); final CNSUnsubscribeAction unsubscribe = new CNSUnsubscribeAction(); final CNSListSubscriptionsAction listSubscriptions = new CNSListSubscriptionsAction(); final CNSListSubscriptionsByTopicAction listSubscriptionsByTopic = new CNSListSubscriptionsByTopicAction(); final CNSSetSubscriptionAttributesAction setSubscriptionAttributes = new CNSSetSubscriptionAttributesAction(); final CNSGetSubscriptionAttributesAction getSubscriptionAttributes = new CNSGetSubscriptionAttributesAction(); final CNSSetTopicAttributesAction setTopicAttributes = new CNSSetTopicAttributesAction(); final CNSGetTopicAttributesAction getTopicAttributes = new CNSGetTopicAttributesAction(); final CNSAddPermissionAction addPermission = new CNSAddPermissionAction(); final CNSRemovePermissionAction removePermission = new CNSRemovePermissionAction(); final CNSGetWorkerStatsAction getWorkerStats = new CNSGetWorkerStatsAction(); final CNSManageServiceAction manageService = new CNSManageServiceAction(); final HealthCheckShallow healthCheckShallow = new HealthCheckShallow(); final CNSGetAPIStatsAction getAPIStats = new CNSGetAPIStatsAction(); actionMap = new HashMap<String, Action>(){{ put(confirmSubscription.getName(), confirmSubscription); put(publish.getName(), publish); put(createTopic.getName(), createTopic); put(deleteTopic.getName(), deleteTopic); put(listTopics.getName(), listTopics); put(subscribe.getName(), subscribe); put(unsubscribe.getName(), unsubscribe); put(listSubscriptions.getName(), listSubscriptions); put(listSubscriptionsByTopic.getName(), listSubscriptionsByTopic); put(setSubscriptionAttributes.getName(), setSubscriptionAttributes); put(getSubscriptionAttributes.getName(), getSubscriptionAttributes); put(setTopicAttributes.getName(), setTopicAttributes); put(getTopicAttributes.getName(), getTopicAttributes); put(addPermission.getName(), addPermission); put(removePermission.getName(), removePermission); put(healthCheckShallow.getName(), healthCheckShallow); put("healthCheckShallow", healthCheckShallow); // for backward-compatibility put(getWorkerStats.getName(), getWorkerStats); put(manageService.getName(), manageService); put(getAPIStats.getName(), getAPIStats); }}; for (String action : actionMap.keySet()) { callResponseTimesByApi.putIfAbsent(action, new AtomicLong[NUM_MINUTES][NUM_BUCKETS]); AtomicLong[][] callResponseTimes = callResponseTimesByApi.get(action); for (int i=0; i<NUM_MINUTES; i++) { for (int k=0; k<NUM_BUCKETS; k++) { callResponseTimes[i][k] = new AtomicLong(); } } } } @Override protected boolean isAuthenticationRequired(String action) { if (!actionMap.containsKey(action)) { throw new IllegalArgumentException("action not supported:" + action); } return actionMap.get(action).isAuthRequired(); } @Override protected boolean handleAction(String action, User user, AsyncContext asyncContext) throws Exception { HttpServletRequest request = (HttpServletRequest)asyncContext.getRequest(); long now = System.currentTimeMillis(); if (lastCNSPingMinute.getAndSet(now/(1000*60)) != now/(1000*60)) { try { AbstractDurablePersistence cassandraHandler = DurablePersistenceFactory.getInstance(); // write ping String serverIp = InetAddress.getLocalHost().getHostAddress(); String serverPort = CMBProperties.getInstance().getCNSServerPort() + ""; logger.info("event=ping version=" + CMBControllerServlet.VERSION + " ip=" + serverIp + " port=" + serverPort); Map<String, String> values = new HashMap<String, String>(); values.put("timestamp", now + ""); values.put("jmxport", System.getProperty("com.sun.management.jmxremote.port", "0")); values.put("dataCenter", CMBProperties.getInstance().getCMBDataCenter()); values.put("serviceUrl", CMBProperties.getInstance().getCNSServiceUrl()); cassandraHandler.insertRow(AbstractDurablePersistence.CNS_KEYSPACE, serverIp + ":" + serverPort, CNS_API_SERVERS, values, CMB_SERIALIZER.STRING_SERIALIZER, CMB_SERIALIZER.STRING_SERIALIZER, CMB_SERIALIZER.STRING_SERIALIZER, null); } catch (Exception ex) { logger.warn("event=ping_failed", ex); } } if (!CMBProperties.getInstance().getCNSServiceEnabled()) { throw new CMBException(CMBErrorCodes.InternalError, "CNS service is disabled"); } if (actionMap == null || subscriptionHandler == null || topicHandler == null) { init(); } if (isAuthenticationRequired(action)) { String topicArn = request.getParameter("TopicArn"); CNSTopicAttributes attributes = CNSCache.getTopicAttributes(topicArn); if (attributes != null) { if (!actionMap.get(action).isActionAllowed(user, request, "CNS", new CMBPolicy(attributes.getPolicy()))) { throw new CMBException(CMBErrorCodes.AccessDenied, "You don't have permission for " + actionMap.get(action).getName()); } } } if (actionMap.containsKey(action)) { return actionMap.get(action).doAction(user, asyncContext); } throw new CMBException(CMBErrorCodes.InvalidAction, action + " is not a valid action"); } }