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 }