/** * 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.tools; import java.net.InetAddress; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import org.apache.log4j.Logger; import com.comcast.cmb.common.controller.CMBControllerServlet; import com.comcast.cmb.common.persistence.PersistenceFactory; import com.comcast.cmb.common.persistence.AbstractDurablePersistence.CMB_SERIALIZER; import com.comcast.cmb.common.util.CMBProperties; import com.comcast.cmb.common.util.PersistenceException; import com.comcast.cmb.common.util.ValueAccumulator.AccumulatorName; import com.comcast.cns.model.CNSEndpointPublishJob; import com.comcast.cns.model.CNSMessage; import com.comcast.cns.model.CNSSubscription; import com.comcast.cns.model.CNSEndpointPublishJob.CNSEndpointSubscriptionInfo; import com.comcast.cns.persistence.CNSCachedEndpointPublishJob; import com.comcast.cns.persistence.ICNSSubscriptionPersistence; import com.comcast.cns.persistence.TopicNotFoundException; import com.comcast.cqs.model.CQSMessage; /** * This class represents the Producer that reads the CNSMessage, partitions the subscribers and * creates the EndpointPublishJobs * @author aseem, bwolf, ppang * * Class is thread-safe */ public class CNSEndpointPublisherJobProducer implements CNSPublisherPartitionRunnable { private static Logger logger = Logger.getLogger(CNSEndpointPublisherJobProducer.class); private static final String CNS_PRODUCER_QUEUE_NAME_PREFIX = CMBProperties.getInstance().getCNSPublishQueueNamePrefix(); private static volatile boolean initialized = false; private static volatile ICNSSubscriptionPersistence subscriptionPersistence = PersistenceFactory.getSubscriptionPersistence(); private long processingDelayMillis = 10; /* * Read the EndpointPublishQ_<m> property and ensuring they exist (create if not) * @throws PersistenceException */ public static void initialize() throws PersistenceException { if (initialized) { return; } logger.info("event=initializing_cns_producer"); initialized = true; } public static void shutdown() { initialized = false; logger.info("event=shutdown_cns_producer"); } @Override /** * 1. Call ReceiveMessage(PublishJobQ.<n> where n in [0..numPublishJobQs] * 2. if no message, go to sleep for 100ms and go back to 1 * 3. if message found, * 3.1 read sub-list for topic, * 3.2 partition them by batch size * 3.3 Enqueue into EndpointPublishJobQ.<m> where m is randomly selected between [0..numEPPublishJobQs] * 3.4 go back to 1 */ public boolean run (int partition) { boolean messageFound = false; if (!initialized) { throw new IllegalStateException("Not initialized"); } long ts1 = System.currentTimeMillis(); CMBControllerServlet.valueAccumulator.initializeAllCounters(); try { if (CNSPublisher.lastProducerMinute.getAndSet(ts1/(1000*60)) != ts1/(1000*60)) { String hostAddress = InetAddress.getLocalHost().getHostAddress(); logger.info("event=ping version=" + CMBControllerServlet.VERSION + " ip=" + hostAddress); try { Map<String, String> values = new HashMap<String, String>(); values.put("producerTimestamp", System.currentTimeMillis() + ""); values.put("jmxport", System.getProperty("com.sun.management.jmxremote.port", "0")); values.put("mode", CNSPublisher.getModeString()); values.put("dataCenter", CMBProperties.getInstance().getCMBDataCenter()); CNSPublisher.cassandraHandler.insertRow(CMBProperties.getInstance().getCNSKeyspace(), hostAddress, "CNSWorkers", values, CMB_SERIALIZER.STRING_SERIALIZER, CMB_SERIALIZER.STRING_SERIALIZER, CMB_SERIALIZER.STRING_SERIALIZER, null); } catch (Exception ex) { logger.warn("event=ping_glitch", ex); } } String publishJobQName = CNS_PRODUCER_QUEUE_NAME_PREFIX + partition; String queueUrl = CQSHandler.getRelativeCnsInternalQueueUrl(publishJobQName); CQSMessage msg = null; int waitTimeSecs = 0; if (CMBProperties.getInstance().isCQSLongPollEnabled()) { waitTimeSecs = CMBProperties.getInstance().getCMBRequestTimeoutSec(); } List<CQSMessage> l = CQSHandler.receiveMessage(queueUrl, waitTimeSecs, 1); if (l.size() > 0) { msg = l.get(0); } CNSWorkerMonitor.getInstance().registerCQSServiceAvailable(true); if (msg != null) { // if long polling disabled and message found reset exponential backoff if (!CMBProperties.getInstance().isCQSLongPollEnabled()) { processingDelayMillis = 10; } CNSMessage publishMessage = CNSMessage.parseInstance(msg.getBody()); int messageExpirationSeconds = CMBProperties.getInstance().getCNSMessageExpirationSeconds(); if (messageExpirationSeconds != 0 && System.currentTimeMillis() - publishMessage.getTimestamp().getTime() > messageExpirationSeconds*1000) { logger.error("event=deleting_publish_job reason=message_too_old topic_arn=" + publishMessage.getTopicArn()); CQSHandler.deleteMessage(queueUrl, msg.getReceiptHandle()); CMBControllerServlet.valueAccumulator.deleteAllCounters(); return true; // return true to avoid backoff } List<CNSEndpointPublishJob.CNSEndpointSubscriptionInfo> subscriptions = null; long t1=System.currentTimeMillis(); try { subscriptions = getSubscriptionsForTopic(publishMessage.getTopicArn()); } catch (TopicNotFoundException e) { //delete this message/job since the topic was deleted. logger.error("event=deleting_publish_job reason=topic_not_found topic_arn=" + publishMessage.getTopicArn()); CQSHandler.deleteMessage(queueUrl, msg.getReceiptHandle()); CMBControllerServlet.valueAccumulator.deleteAllCounters(); return true; // return true to avoid backoff } catch (Exception ex) { logger.error("event=skipping_publish_job reason=error_fetching_subscriptions", ex); CMBControllerServlet.valueAccumulator.deleteAllCounters(); return true; // return true to avoid backoff } logger.debug("event=get_subscription_list ms="+(System.currentTimeMillis()-t1)); if (subscriptions != null && subscriptions.size() > 0) { messageFound = true; List<CNSEndpointPublishJob> epPublishJobs = createEndpointPublishJobs(publishMessage, subscriptions); for (CNSEndpointPublishJob epPublishJob: epPublishJobs) { String epQueueName = CMBProperties.getInstance().getCNSEndpointPublishQueueNamePrefix() + ((new Random()).nextInt(CMBProperties.getInstance().getCNSNumEndpointPublishJobQueues())); String epQueueUrl = CQSHandler.getRelativeCnsInternalQueueUrl(epQueueName); CQSHandler.sendMessage(epQueueUrl, epPublishJob.serialize(), msg.getMessageAttributes()); } } CQSHandler.deleteMessage(queueUrl, msg.getReceiptHandle()); long ts2 = System.currentTimeMillis(); logger.debug("event=processed_producer_job cns_cqs_ms=" + CMBControllerServlet.valueAccumulator.getCounter(AccumulatorName.CNSCQSTime) + " resp_ms=" + (ts2 - ts1)); } else { // if long polling disabled and no message found do exponential backoff if (!CMBProperties.getInstance().isCQSLongPollEnabled()) { if (processingDelayMillis < CMBProperties.getInstance().getCNSProducerProcessingMaxDelay()) { processingDelayMillis *= 2; } Thread.sleep(processingDelayMillis); } } } catch (Exception ex) { logger.error("event=job_producer_failure", ex); CQSHandler.ensureQueuesExist(CNS_PRODUCER_QUEUE_NAME_PREFIX, CMBProperties.getInstance().getCNSNumPublishJobQueues()); } CMBControllerServlet.valueAccumulator.deleteAllCounters(); return messageFound; } public static List<CNSEndpointPublishJob.CNSEndpointSubscriptionInfo> getSubscriptionsForTopic(String topicArn) throws Exception { List<CNSEndpointPublishJob.CNSEndpointSubscriptionInfo> subInfoList = new ArrayList<CNSEndpointPublishJob.CNSEndpointSubscriptionInfo>(); if (CMBProperties.getInstance().isCNSUseSubInfoCache()) { subInfoList.addAll(CNSCachedEndpointPublishJob.getSubInfos(topicArn)); } else { String nextToken = null; while (true) { List<CNSSubscription> subscriptions = subscriptionPersistence.listSubscriptionsByTopic(nextToken, topicArn, null, 1000); if (subscriptions == null || subscriptions.size() == 0) { break; } boolean allPendingConfirmation = true; for (CNSSubscription subscription: subscriptions) { if (!subscription.getArn().equals("PendingConfirmation")) { allPendingConfirmation = false; subInfoList.add(new CNSEndpointPublishJob.CNSEndpointSubscriptionInfo(subscription.getProtocol(), subscription.getEndpoint(), subscription.getArn(), subscription.getRawMessageDelivery())); nextToken = subscription.getArn(); } } if (allPendingConfirmation) { break; } } } return subInfoList; } public static List<CNSEndpointPublishJob> createEndpointPublishJobs(CNSMessage message, List<CNSEndpointPublishJob.CNSEndpointSubscriptionInfo> subscriptions) { List<CNSEndpointPublishJob> epPublishJobs = new ArrayList<CNSEndpointPublishJob>(); if (subscriptions != null) { int maxSubsPerEPPublishJob = CMBProperties.getInstance().getCNSMaxSubscriptionsPerEndpointPublishJob(); int numEPPublishJobs = (int)Math.ceil(subscriptions.size()/(float)maxSubsPerEPPublishJob); int subIndex = 0; for (int i=0; i<numEPPublishJobs; i++) { List<CNSEndpointPublishJob.CNSEndpointSubscriptionInfo> epPublishJobSubscriptions = new ArrayList<CNSEndpointPublishJob.CNSEndpointSubscriptionInfo>(); for (int j=0; j<maxSubsPerEPPublishJob; j++) { CNSEndpointSubscriptionInfo subInfo; if (CMBProperties.getInstance().isCNSUseSubInfoCache()) { subInfo = new CNSCachedEndpointPublishJob.CNSCachedEndpointSubscriptionInfo(subscriptions.get(subIndex).protocol, subscriptions.get(subIndex).endpoint, subscriptions.get(subIndex).subArn, subscriptions.get(subIndex).rawDelivery); } else { subInfo = new CNSEndpointSubscriptionInfo(subscriptions.get(subIndex).protocol, subscriptions.get(subIndex).endpoint, subscriptions.get(subIndex).subArn, subscriptions.get(subIndex).rawDelivery); } epPublishJobSubscriptions.add(subInfo); if (++subIndex==subscriptions.size()) { break; } } CNSEndpointPublishJob job; if (CMBProperties.getInstance().isCNSUseSubInfoCache()) { job = new CNSCachedEndpointPublishJob(message, epPublishJobSubscriptions); } else { job = new CNSEndpointPublishJob(message, epPublishJobSubscriptions); } epPublishJobs.add(job); } } return epPublishJobs; } }