github.com/verrazzano/verrazzano@v1.7.1/application-operator/controllers/logging/fluentd_test.go (about)

     1  // Copyright (C) 2020, 2022, 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 logging
     5  
     6  import (
     7  	"context"
     8  	"reflect"
     9  	"testing"
    10  
    11  	"github.com/golang/mock/gomock"
    12  	asserts "github.com/stretchr/testify/assert"
    13  	vzapi "github.com/verrazzano/verrazzano/application-operator/apis/oam/v1alpha1"
    14  	"github.com/verrazzano/verrazzano/application-operator/mocks"
    15  	"go.uber.org/zap"
    16  	v1 "k8s.io/api/core/v1"
    17  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    18  	"sigs.k8s.io/controller-runtime/pkg/client"
    19  )
    20  
    21  const (
    22  	testLogPath      = "/foo/bar"
    23  	testParseRules   = "test-parse-rules"
    24  	testStorageName  = "test-fluentd-volume"
    25  	testWorkLoadType = "test-workload"
    26  )
    27  
    28  // TestFluentdApply tests the creation of all FLUENTD resources in the system for a resource
    29  // GIVEN a desired state for FLUENTD resources where no resources yet exist
    30  // WHEN Apply is called
    31  // THEN ensure that the expected FLUENTD resources are created
    32  func TestFluentdApply(t *testing.T) {
    33  	mocker := gomock.NewController(t)
    34  	mockClient := mocks.NewMockClient(mocker)
    35  
    36  	logInfo := createTestLogInfo(true)
    37  	resource := createTestResourceRelation()
    38  	fluentdPod := createTestFluentdPod()
    39  
    40  	fluentd := Fluentd{mockClient, zap.S(), context.Background(), testParseRules, testStorageName, scratchVolMountPath, testWorkLoadType}
    41  
    42  	// simulate config map not existing
    43  	mockClient.EXPECT().
    44  		List(fluentd.Context, gomock.Not(gomock.Nil()), client.InNamespace(testNamespace), client.MatchingFields{"metadata.name": configMapName + "-" + testWorkLoadType}).
    45  		DoAndReturn(func(ctx context.Context, resources *unstructured.UnstructuredList, opts ...client.ListOption) error {
    46  			asserts.Equal(t, configMapAPIVersion, resources.GetAPIVersion())
    47  			asserts.Equal(t, configMapKind, resources.GetKind())
    48  			return nil
    49  		})
    50  
    51  	mockClient.EXPECT().
    52  		Create(fluentd.Context, gomock.Not(gomock.Nil()), gomock.Not(gomock.Nil())).
    53  		DoAndReturn(func(ctx context.Context, configMap *v1.ConfigMap, options ...client.CreateOption) error {
    54  			asserts.Equal(t, *fluentd.createFluentdConfigMap(testNamespace), *configMap)
    55  			return nil
    56  		})
    57  
    58  	// invoke method being tested
    59  	err := fluentd.Apply(logInfo, resource, fluentdPod)
    60  
    61  	asserts.Nil(t, err)
    62  	testAssertFluentdPodForApply(t, fluentdPod)
    63  
    64  	mocker.Finish()
    65  }
    66  
    67  // TestFluentdApplyForUpdate tests the update of FLUENTD resources in the system for a resource
    68  // GIVEN a desired state which contains updates for existing FLUENTD resources
    69  // WHEN Apply is called
    70  // THEN ensure that the expected FLUENTD resources updates occur
    71  func TestFluentdApplyForUpdate(t *testing.T) {
    72  	mocker := gomock.NewController(t)
    73  	mockClient := mocks.NewMockClient(mocker)
    74  
    75  	logInfo := createTestLogInfo(true)
    76  	resource := createTestResourceRelation()
    77  	fluentdPod := createTestFluentdPodForUpdate()
    78  
    79  	fluentd := Fluentd{mockClient, zap.S(), context.Background(), testParseRules, testStorageName, scratchVolMountPath, testWorkLoadType}
    80  
    81  	// simulate config map existing
    82  
    83  	mockClient.EXPECT().
    84  		List(fluentd.Context, gomock.Not(gomock.Nil()), client.InNamespace(testNamespace), client.MatchingFields{"metadata.name": configMapName + "-" + testWorkLoadType}).
    85  		DoAndReturn(func(ctx context.Context, resources *unstructured.UnstructuredList, opts ...client.ListOption) error {
    86  			asserts.Equal(t, configMapAPIVersion, resources.GetAPIVersion())
    87  			asserts.Equal(t, configMapKind, resources.GetKind())
    88  
    89  			// this represents the found configmap resource. Only the length is checked not the item details
    90  			resources.Items = append(resources.Items, unstructured.Unstructured{})
    91  
    92  			return nil
    93  		})
    94  	mockClient.EXPECT().
    95  		Update(fluentd.Context, gomock.Not(gomock.Nil()), gomock.Not(gomock.Nil())).
    96  		DoAndReturn(func(ctx context.Context, configMap *v1.ConfigMap, options ...client.UpdateOption) error {
    97  			asserts.Equal(t, *fluentd.createFluentdConfigMap(testNamespace), *configMap)
    98  			return nil
    99  		})
   100  
   101  	// invoke method being tested
   102  	err := fluentd.Apply(logInfo, resource, fluentdPod)
   103  
   104  	asserts.Nil(t, err)
   105  	testAssertFluentdPodForApplyUpdate(t, fluentdPod)
   106  
   107  	mocker.Finish()
   108  }
   109  
   110  // TestFluentdApply tests the deletion of all FLUENTD resources in the system for a resource
   111  // GIVEN a resource with associated FLUENTD resources which is no longer associated with a logging logInfo
   112  // WHEN Remove is called
   113  // THEN ensure that the expexted FLUENTD resources are removed
   114  func TestFluentdRemove(t *testing.T) {
   115  	mocker := gomock.NewController(t)
   116  	mockClient := mocks.NewMockClient(mocker)
   117  
   118  	fluentd := &Fluentd{mockClient, zap.S(), context.Background(), testParseRules, testStorageName, scratchVolMountPath, testWorkLoadType}
   119  	logInfo := createTestLogInfo(true)
   120  	resource := createTestResourceRelation()
   121  	fluentdPod := createTestFluentdPod()
   122  
   123  	addFluentdArtifactsToFluentdPod(fluentd, fluentdPod, logInfo, resource.Namespace)
   124  
   125  	// simulate config map existing
   126  	mockClient.EXPECT().
   127  		List(fluentd.Context, gomock.Not(gomock.Nil()), client.InNamespace(testNamespace), client.MatchingFields{"metadata.name": configMapName + "-" + testWorkLoadType}).
   128  		DoAndReturn(func(ctx context.Context, resources *unstructured.UnstructuredList, opts ...client.ListOption) error {
   129  			asserts.Equal(t, configMapAPIVersion, resources.GetAPIVersion())
   130  			asserts.Equal(t, configMapKind, resources.GetKind())
   131  
   132  			// this represents the found configmap resource. Only the length is checked not the item details
   133  			resources.Items = append(resources.Items, unstructured.Unstructured{})
   134  
   135  			return nil
   136  		})
   137  	mockClient.EXPECT().
   138  		Delete(fluentd.Context, gomock.Not(gomock.Nil()), gomock.Not(gomock.Nil())).
   139  		DoAndReturn(func(ctx context.Context, configmap *v1.ConfigMap, options ...client.DeleteOption) error {
   140  			asserts.True(t, reflect.DeepEqual(fluentd.createFluentdConfigMap(testNamespace), configmap))
   141  			return nil
   142  		})
   143  
   144  	removeVerified := fluentd.Remove(logInfo, resource, fluentdPod)
   145  
   146  	asserts.False(t, removeVerified)
   147  	testAssertFluentdPodForRemove(t, fluentdPod)
   148  	mocker.Finish()
   149  }
   150  
   151  // TestFluentdApply_ManagedClusterElasticsearch tests the creation of all FLUENTD resources in the \
   152  // system for a resource on a MANAGED cluster
   153  // GIVEN a desired state for FLUENTD resources where no resources yet exist
   154  // WHEN Apply is called on a Managed Cluster using the default logging logInfo
   155  // THEN ensure that the expected FLUENTD resources are created and the managed cluster's elasticsearch
   156  // secret is used to determine the ES endpoint
   157  func TestFluentdApply_ManagedClusterElasticsearch(t *testing.T) {
   158  	mocker := gomock.NewController(t)
   159  	mockClient := mocks.NewMockClient(mocker)
   160  
   161  	logInfo := createTestLogInfo(true)
   162  	resource := createTestResourceRelation()
   163  	fluentdPod := createTestFluentdPod()
   164  
   165  	fluentd := Fluentd{mockClient, zap.S(), context.Background(), testParseRules, testStorageName, scratchVolMountPath, testWorkLoadType}
   166  
   167  	// simulate config map not existing
   168  	mockClient.EXPECT().
   169  		List(fluentd.Context, gomock.Not(gomock.Nil()), client.InNamespace(testNamespace), client.MatchingFields{"metadata.name": configMapName + "-" + testWorkLoadType}).
   170  		DoAndReturn(func(ctx context.Context, resources *unstructured.UnstructuredList, opts ...client.ListOption) error {
   171  			asserts.Equal(t, configMapAPIVersion, resources.GetAPIVersion())
   172  			asserts.Equal(t, configMapKind, resources.GetKind())
   173  			return nil
   174  		})
   175  
   176  	mockClient.EXPECT().
   177  		Create(fluentd.Context, gomock.Not(gomock.Nil()), gomock.Not(gomock.Nil())).
   178  		DoAndReturn(func(ctx context.Context, configMap *v1.ConfigMap, options ...client.CreateOption) error {
   179  			asserts.Equal(t, *fluentd.createFluentdConfigMap(testNamespace), *configMap)
   180  			return nil
   181  		})
   182  
   183  	// invoke method being tested
   184  	err := fluentd.Apply(logInfo, resource, fluentdPod)
   185  
   186  	asserts.Nil(t, err)
   187  	testAssertFluentdPodForApply(t, fluentdPod)
   188  
   189  	mocker.Finish()
   190  }
   191  
   192  // createTestFluentdPod creates a test FluentdPod
   193  func createTestFluentdPod() *FluentdPod {
   194  	return &FluentdPod{
   195  		Containers:   []v1.Container{{Name: "test-container"}},
   196  		Volumes:      []v1.Volume{{Name: "test-volume"}},
   197  		VolumeMounts: []v1.VolumeMount{{Name: "test-volume-mount"}},
   198  		HandlerEnv:   []v1.EnvVar{{Name: "test-env-var", Value: "test-env-value"}},
   199  		LogPath:      testLogPath,
   200  	}
   201  }
   202  
   203  // createTestFluendPodForUpdate creates a test FluentdPod to be used in testing update
   204  func createTestFluentdPodForUpdate() *FluentdPod {
   205  	env := createFluentdESEnv()
   206  	fluentdContainer := v1.Container{Name: FluentdStdoutSidecarName, Env: env}
   207  	fluentdPod := &FluentdPod{
   208  		Containers:   []v1.Container{{Name: "test-container"}, fluentdContainer},
   209  		Volumes:      []v1.Volume{{Name: "test-volume"}},
   210  		VolumeMounts: []v1.VolumeMount{{Name: "test-volume-mount"}},
   211  		HandlerEnv:   []v1.EnvVar{{Name: "test-env-var", Value: "test-env-value"}},
   212  		LogPath:      testLogPath,
   213  	}
   214  	return fluentdPod
   215  }
   216  
   217  // addFluentdArtifactsToFluentdPod adds FLUENTD artifacts to a FluentdPod
   218  func addFluentdArtifactsToFluentdPod(fluentd *Fluentd, fluentdPod *FluentdPod, logInfo *LogInfo, namespace string) {
   219  	fluentd.ensureFluentdVolumes(fluentdPod)
   220  	fluentdPod.VolumeMounts = append(fluentdPod.VolumeMounts, fluentd.createStorageVolumeMount())
   221  	fluentdPod.Containers = append(fluentdPod.Containers, fluentd.createFluentdContainer(fluentdPod, logInfo, namespace))
   222  }
   223  
   224  // testAssertFluentdPodForApply asserts FluentdPod state for Apply
   225  func testAssertFluentdPodForApply(t *testing.T, fluentdPod *FluentdPod) {
   226  	containers := fluentdPod.Containers
   227  	asserts.Len(t, containers, 2)
   228  
   229  	volumes := fluentdPod.Volumes
   230  	asserts.Len(t, volumes, 3)
   231  
   232  	volumeMounts := fluentdPod.VolumeMounts
   233  	asserts.Len(t, volumeMounts, 2)
   234  }
   235  
   236  // testAssertFluentdPodForApply asserts FluentdPod state for Apply updates
   237  func testAssertFluentdPodForApplyUpdate(t *testing.T, fluentdPod *FluentdPod) {
   238  	containers := fluentdPod.Containers
   239  	asserts.Len(t, containers, 2)
   240  
   241  	volumes := fluentdPod.Volumes
   242  	asserts.Len(t, volumes, 3)
   243  
   244  	volumeMounts := fluentdPod.VolumeMounts
   245  	asserts.Len(t, volumeMounts, 2)
   246  }
   247  
   248  // testAssertFluentdPodForRemove asserts FluendPod state for Remove
   249  func testAssertFluentdPodForRemove(t *testing.T, fluentdPod *FluentdPod) {
   250  	asserts.Len(t, fluentdPod.Containers, 1)
   251  	// WebLogic FLUENTD volumes don't get removed as a result of disassociation from logInfo
   252  	asserts.Len(t, fluentdPod.Volumes, 2)
   253  	asserts.Len(t, fluentdPod.VolumeMounts, 2)
   254  }
   255  
   256  // createFluentdESEnv creates Env Var set
   257  func createFluentdESEnv() []v1.EnvVar {
   258  	return []v1.EnvVar{
   259  		{
   260  			Name:  "LOG_PATH",
   261  			Value: testLogPath,
   262  		},
   263  		{
   264  			Name:  "FLUENTD_CONF",
   265  			Value: fluentdConfKey,
   266  		},
   267  		{
   268  			Name:  "FLUENT_ELASTICSEARCH_SED_DISABLE",
   269  			Value: "true",
   270  		},
   271  	}
   272  }
   273  
   274  // createTestResourceRelation creates a new test QualifiedResourceRelation
   275  func createTestResourceRelation() vzapi.QualifiedResourceRelation {
   276  	resource := vzapi.QualifiedResourceRelation{
   277  		APIVersion: testAPIVersion,
   278  		Kind:       "Domain",
   279  		Namespace:  testNamespace,
   280  		Name:       "testName",
   281  		Role:       "",
   282  	}
   283  
   284  	return resource
   285  }