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

     1  // Copyright (c) 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  	"time"
    13  
    14  	"github.com/verrazzano/verrazzano/tools/vz/pkg/constants"
    15  	"github.com/verrazzano/verrazzano/tools/vz/pkg/internal/util/files"
    16  	"github.com/verrazzano/verrazzano/tools/vz/pkg/internal/util/report"
    17  	"go.uber.org/zap"
    18  	corev1 "k8s.io/api/core/v1"
    19  )
    20  
    21  // AnalyzeNamespaceRelatedIssues is the initial entry function for namespace related issues, and it returns an error.
    22  // It checks to see whether the namespace being analyzed is in a state of terminating
    23  func AnalyzeNamespaceRelatedIssues(log *zap.SugaredLogger, clusterRoot string) (err error) {
    24  	allNamespacesFound, err = files.FindNamespaces(log, clusterRoot)
    25  	if err != nil {
    26  		return err
    27  	}
    28  	var issueReporter = report.IssueReporter{
    29  		PendingIssues: make(map[string]report.Issue),
    30  	}
    31  	timeOfCapture, err := files.GetTimeOfCapture(log, clusterRoot)
    32  	if err != nil {
    33  		return err
    34  	}
    35  	for _, namespace := range allNamespacesFound {
    36  		namespaceFile := files.FormFilePathInNamespace(clusterRoot, namespace, constants.NamespaceJSON)
    37  		namespaceObject, err := getNamespaceResource(log, namespaceFile)
    38  		if err != nil {
    39  			return err
    40  		}
    41  		if namespaceObject == nil {
    42  			continue
    43  		}
    44  		issueFound, messageList := isNamespaceCurrentlyInTerminatingStatus(namespaceObject, timeOfCapture)
    45  		if issueFound {
    46  			reportNamespaceInTerminatingStatusIssue(clusterRoot, *namespaceObject, &issueReporter, namespaceFile, messageList)
    47  		}
    48  
    49  	}
    50  
    51  	issueReporter.Contribute(log, clusterRoot)
    52  	return nil
    53  }
    54  
    55  // getNamespaceResource returns the namespace object that is in the namespace file
    56  func getNamespaceResource(log *zap.SugaredLogger, path string) (namespaceObject *corev1.Namespace, err error) {
    57  	namespaceResource := &corev1.Namespace{}
    58  	file, err := os.Open(path)
    59  	if err != nil {
    60  		log.Debug("file %s not found", path)
    61  		return nil, nil
    62  	}
    63  	defer file.Close()
    64  	fileBytes, err := io.ReadAll(file)
    65  	if err != nil {
    66  		log.Error("Failed reading namespace.json file %s", path)
    67  		return nil, err
    68  	}
    69  	err = encjson.Unmarshal(fileBytes, &namespaceResource)
    70  	if err != nil {
    71  		log.Error("Failed to unmarshal namespace resource at %s", path)
    72  		return nil, err
    73  	}
    74  	return namespaceResource, err
    75  }
    76  
    77  // isNamespaceCurrentlyInTerminatingStatus checks to see if that is the namespace currently has a status of terminating
    78  func isNamespaceCurrentlyInTerminatingStatus(namespaceObject *corev1.Namespace, timeOfCapture *time.Time) (bool, []string) {
    79  	var listOfMessagesFromRelevantConditions = []string{}
    80  	if namespaceObject.Status.Phase != corev1.NamespaceTerminating {
    81  		return false, listOfMessagesFromRelevantConditions
    82  	}
    83  	var deletionMessage string
    84  	if namespaceObject.DeletionTimestamp == nil || timeOfCapture == nil {
    85  		return false, listOfMessagesFromRelevantConditions
    86  	}
    87  	diff := timeOfCapture.Sub(namespaceObject.DeletionTimestamp.Time)
    88  	if int(diff.Minutes()) < 10 {
    89  		return false, listOfMessagesFromRelevantConditions
    90  	}
    91  	deletionMessage = "The namespace " + namespaceObject.Name + " has spent " + fmt.Sprint(int(diff.Minutes())) + " minutes and " + fmt.Sprint(int(diff.Seconds())%60) + " seconds deleting"
    92  	listOfMessagesFromRelevantConditions = append(listOfMessagesFromRelevantConditions, deletionMessage)
    93  	namespaceConditions := namespaceObject.Status.Conditions
    94  	if namespaceConditions == nil {
    95  		return true, listOfMessagesFromRelevantConditions
    96  	}
    97  	for i := range namespaceConditions {
    98  		if namespaceConditions[i].Type == corev1.NamespaceFinalizersRemaining || namespaceConditions[i].Type == corev1.NamespaceContentRemaining {
    99  			listOfMessagesFromRelevantConditions = append(listOfMessagesFromRelevantConditions, namespaceConditions[i].Message)
   100  		}
   101  	}
   102  	return true, listOfMessagesFromRelevantConditions
   103  }
   104  func reportNamespaceInTerminatingStatusIssue(clusterRoot string, namespace corev1.Namespace, issueReporter *report.IssueReporter, namespaceFile string, messagesFromConditions []string) {
   105  	files := []string{namespaceFile}
   106  	issueReporter.AddKnownIssueMessagesFiles(report.NamespaceCurrentlyInTerminatingStateForLongDuration, clusterRoot, messagesFromConditions, files)
   107  
   108  }