github.com/verrazzano/verrazzano@v1.7.1/tools/vz/pkg/internal/util/cluster/events.go (about)

     1  // Copyright (c) 2021, 2024, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  // Package cluster handles cluster analysis
     5  package cluster
     6  
     7  import (
     8  	encjson "encoding/json"
     9  	"fmt"
    10  	"io"
    11  	"os"
    12  	"sync"
    13  
    14  	"github.com/verrazzano/verrazzano/tools/vz/pkg/constants"
    15  	"github.com/verrazzano/verrazzano/tools/vz/pkg/internal/util/files"
    16  	"go.uber.org/zap"
    17  	corev1 "k8s.io/api/core/v1"
    18  )
    19  
    20  var eventListMap = make(map[string]*corev1.EventList)
    21  var eventCacheMutex = &sync.Mutex{}
    22  
    23  // GetEventList gets an event list
    24  func GetEventList(log *zap.SugaredLogger, path string) (eventList *corev1.EventList, err error) {
    25  	// Check the cache first
    26  	eventList = getEventListIfPresent(path)
    27  	if eventList != nil {
    28  		log.Debugf("Returning cached eventList for %s", path)
    29  		return eventList, nil
    30  	}
    31  
    32  	// Not found in the cache, get it from the file
    33  	file, err := os.Open(path)
    34  	if err != nil {
    35  		log.Debugf("file %s not found", path)
    36  		return nil, err
    37  	}
    38  	defer file.Close()
    39  
    40  	fileBytes, err := io.ReadAll(file)
    41  	if err != nil {
    42  		log.Debugf("Failed reading Json file %s", path)
    43  		return nil, err
    44  	}
    45  	err = encjson.Unmarshal(fileBytes, &eventList)
    46  	if err != nil {
    47  		log.Debugf("Failed to unmarshal eventList at %s", path)
    48  		return nil, err
    49  	}
    50  	putEventListIfNotPresent(path, eventList)
    51  	return eventList, nil
    52  }
    53  
    54  // TODO: Need to add an optional time range to the get events, that will allow us to find events related to
    55  // pods/services/etc that happened only within a given time range
    56  
    57  // GetEventsRelatedToPod gets events related to a pod
    58  func GetEventsRelatedToPod(log *zap.SugaredLogger, clusterRoot string, pod corev1.Pod, timeRange *files.TimeRange) (podEvents []corev1.Event, err error) {
    59  	allEvents, err := GetEventList(log, files.FormFilePathInNamespace(clusterRoot, pod.ObjectMeta.Namespace, constants.EventsJSON))
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  	if allEvents == nil || len(allEvents.Items) == 0 {
    64  		return nil, nil
    65  	}
    66  	podEvents = make([]corev1.Event, 0, 1)
    67  	for _, event := range allEvents.Items {
    68  		if event.InvolvedObject.Kind == "Pod" &&
    69  			event.InvolvedObject.Namespace == pod.ObjectMeta.Namespace &&
    70  			event.InvolvedObject.Name == pod.ObjectMeta.Name {
    71  			podEvents = append(podEvents, event)
    72  		}
    73  	}
    74  	return podEvents, nil
    75  }
    76  
    77  // GetEventsRelatedToService gets events related to a service
    78  func GetEventsRelatedToService(log *zap.SugaredLogger, clusterRoot string, service corev1.Service, timeRange *files.TimeRange) (serviceEvents []corev1.Event, err error) {
    79  	log.Debugf("GetEventsRelatedToService called for %s in namespace %s", service.ObjectMeta.Name, service.ObjectMeta.Namespace)
    80  	allEvents, err := GetEventList(log, files.FormFilePathInNamespace(clusterRoot, service.ObjectMeta.Namespace, constants.EventsJSON))
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  	if allEvents == nil || len(allEvents.Items) == 0 {
    85  		log.Debugf("GetEventsRelatedToService: No events found")
    86  		return nil, nil
    87  	}
    88  	serviceEvents = make([]corev1.Event, 0, 1)
    89  	for _, event := range allEvents.Items {
    90  		log.Debugf("Checking event involved object kind: %s, namespace: %s, name: %s", event.InvolvedObject.Kind, event.InvolvedObject.Namespace, event.InvolvedObject.Name)
    91  		if event.InvolvedObject.Kind == "Service" &&
    92  			event.InvolvedObject.Namespace == service.ObjectMeta.Namespace &&
    93  			event.InvolvedObject.Name == service.ObjectMeta.Name {
    94  			log.Debugf("event matched service")
    95  			serviceEvents = append(serviceEvents, event)
    96  		}
    97  	}
    98  	return serviceEvents, nil
    99  }
   100  
   101  // GetEventsRelatedToComponentNamespace gets events related to a component namespace
   102  func GetEventsRelatedToComponentNamespace(log *zap.SugaredLogger, clusterRoot string, componentNamespace string, timeRange *files.TimeRange) (componentEvents []corev1.Event, err error) {
   103  	log.Debugf("GetEventsRelatedToComponentNs called for component in namespace %s", componentNamespace)
   104  
   105  	podFile := files.FormFilePathInNamespace(clusterRoot, componentNamespace, podsJSON)
   106  	podList, err := GetPodList(log, podFile)
   107  	if err != nil {
   108  		log.Debugf("Failed to get the list of pods for the given pod file %s, skipping", podFile)
   109  	}
   110  	if podList == nil {
   111  		log.Debugf("No pod was returned, skipping")
   112  		return nil, nil
   113  	}
   114  
   115  	for _, pod := range podList.Items {
   116  		eventList, err := GetEventsRelatedToPod(log, clusterRoot, pod, nil)
   117  		if err != nil {
   118  			log.Debugf("Failed to get events for %s pod in namespace %s", pod.Name, pod.Namespace)
   119  			continue
   120  		}
   121  		componentEvents = append(componentEvents, eventList...)
   122  	}
   123  	return componentEvents, nil
   124  }
   125  
   126  // CheckEventsForWarnings goes through the events list for a specific Component/Service/Pod
   127  // and checks if there exists an event with type Warning and returns the corresponding reason and message
   128  // as an additional supporting data
   129  func CheckEventsForWarnings(log *zap.SugaredLogger, events []corev1.Event, eventType string, timeRange *files.TimeRange) (message []string, err error) {
   130  	log.Debugf("Searching the events list for any additional support data")
   131  	var finalEventList []corev1.Event
   132  	for _, event := range events {
   133  		foundInvolvedObject := false
   134  		for index, previousEvent := range finalEventList {
   135  			// If we already iterated through an event for the same involved object with given eventType
   136  			// And that event occurred before this current event, replace it accordingly
   137  			if isInvolvedObjectSame(event, previousEvent) && event.LastTimestamp.Time.After(previousEvent.LastTimestamp.Time) {
   138  				foundInvolvedObject = true
   139  				// Remove the previous event from the final list
   140  				finalEventList = append(finalEventList[:index], finalEventList[index+1:]...)
   141  				if event.Type == eventType {
   142  					finalEventList = append(finalEventList, event)
   143  				}
   144  				break
   145  			}
   146  		}
   147  		// No previous event for this specific involved object
   148  		// Add to the finalEventList, if event type matches
   149  		if !foundInvolvedObject && event.Type == eventType {
   150  			finalEventList = append(finalEventList, event)
   151  		}
   152  	}
   153  
   154  	for _, event := range finalEventList {
   155  		if len(event.Message) == 0 {
   156  			continue
   157  		}
   158  		message = append(message, fmt.Sprintf("Namespace: %s, %s %s, Message: %s, Reason: %s", event.InvolvedObject.Namespace, event.InvolvedObject.Kind, event.InvolvedObject.Name, event.Message, event.Reason))
   159  	}
   160  
   161  	return message, nil
   162  }
   163  
   164  func isInvolvedObjectSame(eventA corev1.Event, eventB corev1.Event) bool {
   165  	return eventA.InvolvedObject.Kind == eventB.InvolvedObject.Kind &&
   166  		(eventA.InvolvedObject.Name == eventB.InvolvedObject.Name) &&
   167  		eventA.InvolvedObject.Namespace == eventB.InvolvedObject.Namespace
   168  }
   169  
   170  func getEventListIfPresent(path string) (eventList *corev1.EventList) {
   171  	eventCacheMutex.Lock()
   172  	eventListTest := eventListMap[path]
   173  	if eventListTest != nil {
   174  		eventList = eventListTest
   175  	}
   176  	eventCacheMutex.Unlock()
   177  	return eventList
   178  }
   179  
   180  func putEventListIfNotPresent(path string, eventList *corev1.EventList) {
   181  	eventCacheMutex.Lock()
   182  	eventListInMap := eventListMap[path]
   183  	if eventListInMap == nil {
   184  		eventListMap[path] = eventList
   185  	}
   186  	eventCacheMutex.Unlock()
   187  }