github.com/Financial-Times/publish-availability-monitor@v1.12.0/messageHandler.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/Financial-Times/go-logger/v2"
    10  	"github.com/Financial-Times/kafka-client-go/v4"
    11  	"github.com/Financial-Times/publish-availability-monitor/checks"
    12  	"github.com/Financial-Times/publish-availability-monitor/config"
    13  	"github.com/Financial-Times/publish-availability-monitor/content"
    14  	"github.com/Financial-Times/publish-availability-monitor/envs"
    15  	"github.com/Financial-Times/publish-availability-monitor/feeds"
    16  	"github.com/Financial-Times/publish-availability-monitor/httpcaller"
    17  	"github.com/Financial-Times/publish-availability-monitor/metrics"
    18  )
    19  
    20  const systemIDKey = "Origin-System-Id"
    21  
    22  type MessageHandler interface {
    23  	HandleMessage(msg kafka.FTMessage)
    24  }
    25  
    26  func NewKafkaMessageHandler(
    27  	appConfig *config.AppConfig,
    28  	environments *envs.Environments,
    29  	subscribedFeeds map[string][]feeds.Feed,
    30  	metricSink chan metrics.PublishMetric,
    31  	metricContainer *metrics.History,
    32  	e2eTestUUIDs []string,
    33  	log *logger.UPPLogger,
    34  ) MessageHandler {
    35  	return &kafkaMessageHandler{
    36  		appConfig:       appConfig,
    37  		environments:    environments,
    38  		subscribedFeeds: subscribedFeeds,
    39  		metricSink:      metricSink,
    40  		metricContainer: metricContainer,
    41  		e2eTestUUIDs:    e2eTestUUIDs,
    42  		log:             log,
    43  	}
    44  }
    45  
    46  type kafkaMessageHandler struct {
    47  	appConfig       *config.AppConfig
    48  	environments    *envs.Environments
    49  	subscribedFeeds map[string][]feeds.Feed
    50  	metricSink      chan metrics.PublishMetric
    51  	metricContainer *metrics.History
    52  	e2eTestUUIDs    []string
    53  	log             *logger.UPPLogger
    54  }
    55  
    56  func (h *kafkaMessageHandler) HandleMessage(msg kafka.FTMessage) {
    57  	tid := msg.Headers["X-Request-Id"]
    58  	log := h.log.WithTransactionID(tid)
    59  
    60  	log.Info("Received message")
    61  
    62  	if h.isIgnorableMessage(msg) {
    63  		log.Info("Message is ignorable. Skipping...")
    64  		return
    65  	}
    66  
    67  	publishedContent, err := h.unmarshalContent(msg)
    68  	if err != nil {
    69  		h.log.WithError(err).Warn("Cannot unmarshal message")
    70  		return
    71  	}
    72  
    73  	publishDateString := msg.Headers["Message-Timestamp"]
    74  	publishDate, err := time.Parse(checks.DateLayout, publishDateString)
    75  	if err != nil {
    76  		h.log.WithError(err).Errorf("Cannot parse publish date [%v]",
    77  			publishDateString)
    78  		return
    79  	}
    80  
    81  	var paramsToSchedule []*checks.SchedulerParam
    82  
    83  	for _, preCheck := range checks.MainPreChecks() {
    84  		ok, scheduleParam := preCheck(publishedContent, tid, publishDate, h.appConfig, h.metricContainer, h.environments, h.log)
    85  		if ok {
    86  			paramsToSchedule = append(paramsToSchedule, scheduleParam)
    87  		} else {
    88  			//if a main check is not ok, additional checks make no sense
    89  			return
    90  		}
    91  	}
    92  
    93  	hC := httpcaller.NewCaller(10)
    94  
    95  	//key is the endpoint alias from the config
    96  	endpointSpecificChecks := map[string]checks.EndpointSpecificCheck{
    97  		"content":                  checks.NewContentCheck(hC),
    98  		"content-neo4j":            checks.NewContentNeo4jCheck(hC),
    99  		"content-collection-neo4j": checks.NewContentNeo4jCheck(hC),
   100  		"complementary-content":    checks.NewContentCheck(hC),
   101  		"internal-components":      checks.NewContentCheck(hC),
   102  		"enrichedContent":          checks.NewContentCheck(hC),
   103  		"lists":                    checks.NewContentCheck(hC),
   104  		"pages":                    checks.NewContentCheck(hC),
   105  		"contentrelations":         checks.NewContentCheck(hC),
   106  		"notifications":            checks.NewNotificationsCheck(hC, h.subscribedFeeds, "notifications"),
   107  		"notifications-push":       checks.NewNotificationsCheck(hC, h.subscribedFeeds, "notifications-push"),
   108  		"list-notifications":       checks.NewNotificationsCheck(hC, h.subscribedFeeds, "list-notifications"),
   109  		"list-notifications-push":  checks.NewNotificationsCheck(hC, h.subscribedFeeds, "list-notifications-push"),
   110  		"page-notifications":       checks.NewNotificationsCheck(hC, h.subscribedFeeds, "page-notifications"),
   111  		"page-notifications-push":  checks.NewNotificationsCheck(hC, h.subscribedFeeds, "page-notifications-push"),
   112  	}
   113  
   114  	for _, scheduleParam := range paramsToSchedule {
   115  		checks.ScheduleChecks(scheduleParam, endpointSpecificChecks, h.appConfig, h.metricSink, h.e2eTestUUIDs, h.log)
   116  	}
   117  }
   118  
   119  func (h *kafkaMessageHandler) isIgnorableMessage(msg kafka.FTMessage) bool {
   120  	tid := msg.Headers["X-Request-Id"]
   121  
   122  	isSynthetic := h.isSyntheticTransactionID(tid)
   123  	isE2ETest := config.IsE2ETestTransactionID(tid, h.e2eTestUUIDs)
   124  	isCarousel := h.isContentCarouselTransactionID(tid)
   125  
   126  	if isSynthetic && isE2ETest {
   127  		h.log.WithTransactionID(tid).Infof("Message is E2E Test.")
   128  		return false
   129  	}
   130  
   131  	return isSynthetic || isCarousel
   132  }
   133  
   134  func (h *kafkaMessageHandler) isSyntheticTransactionID(tid string) bool {
   135  	return strings.HasPrefix(tid, "SYNTHETIC")
   136  }
   137  
   138  func (h *kafkaMessageHandler) isContentCarouselTransactionID(tid string) bool {
   139  	return carouselTransactionIDRegExp.MatchString(tid)
   140  }
   141  
   142  // UnmarshalContent unmarshals the message body into the appropriate content type based on the systemID header.
   143  func (h *kafkaMessageHandler) unmarshalContent(msg kafka.FTMessage) (content.Content, error) {
   144  	binaryContent := []byte(msg.Body)
   145  
   146  	headers := msg.Headers
   147  	systemID := headers[systemIDKey]
   148  	switch systemID {
   149  	case "http://cmdb.ft.com/systems/next-video-editor":
   150  		if msg.Headers["Content-Type"] == "application/vnd.ft-upp-audio" {
   151  			return unmarshalGenericContent(msg)
   152  		}
   153  
   154  		var video content.Video
   155  		err := json.Unmarshal(binaryContent, &video)
   156  		if err != nil {
   157  			return nil, err
   158  		}
   159  		return video.Initialize(binaryContent), nil
   160  	case "http://cmdb.ft.com/systems/cct", "http://cmdb.ft.com/systems/spark-lists", "http://cmdb.ft.com/systems/spark", "http://cmdb.ft.com/systems/spark-clips":
   161  		return unmarshalGenericContent(msg)
   162  	default:
   163  		return nil, fmt.Errorf("unsupported content with system ID: [%s]", systemID)
   164  	}
   165  }
   166  
   167  func unmarshalGenericContent(msg kafka.FTMessage) (content.GenericContent, error) {
   168  	binaryContent := []byte(msg.Body)
   169  	var genericContent content.GenericContent
   170  	err := json.Unmarshal(binaryContent, &genericContent)
   171  	if err != nil {
   172  		return content.GenericContent{}, err
   173  	}
   174  
   175  	genericContent = genericContent.Initialize(binaryContent).(content.GenericContent)
   176  	genericContent.Type = msg.Headers["Content-Type"]
   177  
   178  	return genericContent, nil
   179  }