| // Copyright 2021 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package policymanagerutil |
| |
| import ( |
| "errors" |
| "reflect" |
| "testing" |
| |
| "policy_manager/mock/mockpolicymanagerutil" |
| "policy_manager/policymanagerproto" |
| |
| "github.com/golang/mock/gomock" |
| "github.com/golang/protobuf/proto" |
| ) |
| |
| // Tests that all metadata keys are parsed and applied correctly. |
| func TestGetUserConfigFromMetadata(t *testing.T) { |
| tests := []struct { |
| // Name of the test case |
| name string |
| // Key:Value pairs present in metadata. |
| metadata map[string]string |
| // Expected InstanceConfig to be returned |
| expectedConfig *policymanagerproto.InstanceConfig |
| }{ |
| { |
| "NoMetadataKeys", |
| map[string]string{}, |
| &policymanagerproto.InstanceConfig{ |
| HealthMonitorConfig: &policymanagerproto.HealthMonitorConfig{}, |
| }, |
| }, |
| { |
| "UpdateStrategyPresent", |
| map[string]string{ |
| gciKeyUpdateStrategy: "update-strategy-1", |
| }, |
| &policymanagerproto.InstanceConfig{ |
| UpdateStrategy: proto.String("update-strategy-1"), |
| HealthMonitorConfig: &policymanagerproto.HealthMonitorConfig{}, |
| }, |
| }, |
| { |
| "MultipleSettingsPresent", |
| map[string]string{ |
| gciKeyUpdateStrategy: "update-strategy-2", |
| gciKeyTargetVersionPrefix: "9090.99.", |
| gciKeyMetricsEnabled: "true", |
| gciKeyRebootAfterUpdate: "false", |
| }, |
| &policymanagerproto.InstanceConfig{ |
| UpdateStrategy: proto.String("update-strategy-2"), |
| TargetVersionPrefix: proto.String("9090.99."), |
| MetricsEnabled: proto.Bool(true), |
| RebootAfterUpdate: proto.Bool(false), |
| HealthMonitorConfig: &policymanagerproto.HealthMonitorConfig{}, |
| }, |
| }, |
| { |
| "InvalidBoolValueIgnored", |
| map[string]string{ |
| gciKeyTargetVersionPrefix: "9090.99.", |
| gciKeyMetricsEnabled: "no", |
| keyGoogleLoggingEnabled: "yes", |
| }, |
| &policymanagerproto.InstanceConfig{ |
| TargetVersionPrefix: proto.String("9090.99."), |
| HealthMonitorConfig: &policymanagerproto.HealthMonitorConfig{}, |
| }, |
| }, |
| { |
| "LegacyConfigKeyPresent", |
| map[string]string{ |
| gciLegacyConfigKey: "{\"update_strategy\":\"update-strategy-3\"}", |
| gciKeyUpdateStrategy: "update-strategy-4", |
| }, |
| &policymanagerproto.InstanceConfig{ |
| UpdateStrategy: proto.String("update-strategy-3"), |
| HealthMonitorConfig: &policymanagerproto.HealthMonitorConfig{}, |
| }, |
| }, |
| { |
| "LoggingButIngoreMonitoring", |
| map[string]string{ |
| keyGoogleLoggingEnabled: "true", |
| }, |
| &policymanagerproto.InstanceConfig{ |
| HealthMonitorConfig: &policymanagerproto.HealthMonitorConfig{ |
| Enforced: proto.Bool(true), |
| LoggingEnabled: proto.Bool(true), |
| }, |
| }, |
| }, |
| { |
| "MonitoringButIngoreLogging", |
| map[string]string{ |
| keyGoogleMonitoringEnabled: "true", |
| }, |
| &policymanagerproto.InstanceConfig{ |
| HealthMonitorConfig: &policymanagerproto.HealthMonitorConfig{ |
| Enforced: proto.Bool(true), |
| MonitoringEnabled: proto.Bool(true), |
| }, |
| }, |
| }, |
| { |
| "SpecifiedNoLogging", |
| map[string]string{ |
| keyGoogleLoggingEnabled: "false", |
| }, |
| &policymanagerproto.InstanceConfig{ |
| HealthMonitorConfig: &policymanagerproto.HealthMonitorConfig{ |
| LoggingEnabled: proto.Bool(false), |
| }, |
| }, |
| }, |
| { |
| "LoggingAndMonitoring", |
| map[string]string{ |
| keyGoogleMonitoringEnabled: "true", |
| keyGoogleLoggingEnabled: "true", |
| }, |
| &policymanagerproto.InstanceConfig{ |
| HealthMonitorConfig: &policymanagerproto.HealthMonitorConfig{ |
| Enforced: proto.Bool(true), |
| LoggingEnabled: proto.Bool(true), |
| MonitoringEnabled: proto.Bool(true), |
| }, |
| }, |
| }, |
| } |
| |
| for _, test := range tests { |
| ic := getUserConfigFromMetadata(test.metadata) |
| if !proto.Equal(ic, test.expectedConfig) { |
| t.Errorf("FAILED '%s': got %s, expect %s", |
| test.name, |
| proto.MarshalTextString(ic), |
| proto.MarshalTextString(test.expectedConfig)) |
| } |
| } |
| } |
| |
| // Tests that getUserConfig() calls FetchMetadata() and |
| // returns correct config settings. |
| func TestGetUserConfig(t *testing.T) { |
| tests := []struct { |
| // Name of the test case |
| name string |
| // The raw metadata returned by FetchMetadata() |
| rawMetadata string |
| // The expected etag to be returned |
| expectedEtag string |
| // The expected config settings to be returned |
| expectedConfig *policymanagerproto.InstanceConfig |
| // Whether expect FetchMetadata() returns error |
| expectFetchError bool |
| // Whether expect parseRawMetadata() returns error |
| expectParseError bool |
| }{ |
| { |
| "NoMetadata", |
| "{}", |
| "etag0", |
| &policymanagerproto.InstanceConfig{ |
| HealthMonitorConfig: &policymanagerproto.HealthMonitorConfig{}, |
| }, |
| false, |
| false, |
| }, |
| { |
| "NormalUpdate", |
| `{ |
| "cos-metrics-enabled":"true", |
| "cos-reboot-after-update":"false", |
| "cos-target-version-prefix":"9090.99.", |
| "cos-update-strategy":"update-strategy-1" |
| }`, |
| "etag1", |
| &policymanagerproto.InstanceConfig{ |
| UpdateStrategy: proto.String("update-strategy-1"), |
| TargetVersionPrefix: proto.String("9090.99."), |
| MetricsEnabled: proto.Bool(true), |
| RebootAfterUpdate: proto.Bool(false), |
| HealthMonitorConfig: &policymanagerproto.HealthMonitorConfig{}, |
| }, |
| false, |
| false, |
| }, |
| { |
| "MixedUpdate", |
| `{ |
| "cos-update-strategy":"update-strategy-2", |
| "key1":"value1", |
| "kdy2":"value2" |
| }`, |
| "etag2", |
| &policymanagerproto.InstanceConfig{ |
| UpdateStrategy: proto.String("update-strategy-2"), |
| HealthMonitorConfig: &policymanagerproto.HealthMonitorConfig{}, |
| }, |
| false, |
| false, |
| }, |
| { |
| "NoCosMetadataUpdate", |
| `{ |
| "key1":"value1", |
| "kdy2":"value2" |
| }`, |
| "etag3", |
| &policymanagerproto.InstanceConfig{ |
| HealthMonitorConfig: &policymanagerproto.HealthMonitorConfig{}, |
| }, |
| false, |
| false, |
| }, |
| { |
| "LoggingButNoMonitoring", |
| `{ |
| "google-logging-enabled":"true", |
| "google-monitoring-enabled":"false", |
| "key1":"value1", |
| "kdy2":"value2" |
| }`, |
| "etag4", |
| &policymanagerproto.InstanceConfig{ |
| HealthMonitorConfig: &policymanagerproto.HealthMonitorConfig{ |
| Enforced: proto.Bool(true), |
| LoggingEnabled: proto.Bool(true), |
| MonitoringEnabled: proto.Bool(false), |
| }, |
| }, |
| false, |
| false, |
| }, |
| { |
| "MonitoringButIgnoreLogging", |
| `{ |
| "google-monitoring-enabled":"true", |
| "key1":"value1", |
| "kdy2":"value2" |
| }`, |
| "etag5", |
| &policymanagerproto.InstanceConfig{ |
| HealthMonitorConfig: &policymanagerproto.HealthMonitorConfig{ |
| Enforced: proto.Bool(true), |
| MonitoringEnabled: proto.Bool(true), |
| }, |
| }, |
| false, |
| false, |
| }, |
| { |
| "LoggingAndMonitoring", |
| `{ |
| "google-logging-enabled":"true", |
| "google-monitoring-enabled":"true", |
| "key1":"value1", |
| "kdy2":"value2" |
| }`, |
| "etag6", |
| &policymanagerproto.InstanceConfig{ |
| HealthMonitorConfig: &policymanagerproto.HealthMonitorConfig{ |
| Enforced: proto.Bool(true), |
| LoggingEnabled: proto.Bool(true), |
| MonitoringEnabled: proto.Bool(true), |
| }, |
| }, |
| false, |
| false, |
| }, |
| { |
| "FetchMetadataError", |
| "", |
| "", |
| nil, |
| true, |
| false, |
| }, |
| { |
| "ParseRawMetadataError", |
| `{"key":}`, |
| "", |
| nil, |
| false, |
| true, |
| }, |
| } |
| |
| for _, test := range tests { |
| mockCtrl := gomock.NewController(t) |
| mockRetriever := mockpolicymanagerutil.NewMockMetadataRetriever(mockCtrl) |
| |
| if !test.expectFetchError { |
| mockRetriever.EXPECT().FetchMetadata(""). |
| Return(test.rawMetadata, test.expectedEtag, nil) |
| } else { |
| mockRetriever.EXPECT().FetchMetadata(""). |
| Return("", "", errors.New("Fetch metadata error")) |
| } |
| |
| actualConfig, actualEtag, err := getUserConfig(mockRetriever, "") |
| |
| if err != nil { |
| if !test.expectFetchError && !test.expectParseError { |
| t.Errorf("Test %s got unexpected error %v", |
| test.name, err) |
| } |
| } else { |
| if test.expectFetchError || test.expectParseError { |
| t.Errorf("Test %s got %s, want error", |
| test.name, proto.MarshalTextString(actualConfig)) |
| } |
| if !proto.Equal(actualConfig, test.expectedConfig) { |
| t.Errorf("FAILED '%s': got %s, expect %s", |
| test.name, |
| proto.MarshalTextString(actualConfig), |
| proto.MarshalTextString(test.expectedConfig)) |
| } |
| if actualEtag != test.expectedEtag { |
| t.Errorf("Test %s got etag %s, want %s", |
| test.name, |
| actualEtag, |
| test.expectedEtag) |
| } |
| } |
| |
| mockCtrl.Finish() |
| } |
| } |
| |
| // Tests that SubscribeUserConfig() calls FetchMetadata() |
| // with correct parameters and returns correct config settings from channel. |
| func TestSubscribeUserConfig(t *testing.T) { |
| tests := []struct { |
| // Name of the test case |
| name string |
| // Raw metadata returned by FetchMetadata() |
| rawMetadata string |
| // The expected config settings to be returned |
| expectedConfig *policymanagerproto.InstanceConfig |
| // The expected etag to be returned in the first time |
| firstEtag string |
| // The expected etag to be returned in the second time |
| secondEtag string |
| }{ |
| { |
| "SubscribeUserConfig", |
| `{"cos-update-strategy":"update-strategy"}`, |
| &policymanagerproto.InstanceConfig{ |
| UpdateStrategy: proto.String("update-strategy"), |
| HealthMonitorConfig: &policymanagerproto.HealthMonitorConfig{}, |
| }, |
| "etag-1", |
| "etag-2", |
| }, |
| } |
| |
| for _, test := range tests { |
| mockCtrl := gomock.NewController(t) |
| mockRetriever := mockpolicymanagerutil.NewMockMetadataRetriever(mockCtrl) |
| |
| // Return expected rawMetadata and etag for testcase being tested. |
| mockRetriever.EXPECT().FetchMetadata( |
| gomock.Eq("")).Return(test.rawMetadata, test.firstEtag, nil) |
| // Expect another call with the etag returned in the first call |
| mockRetriever.EXPECT().FetchMetadata( |
| gomock.Eq(test.firstEtag)).Return(test.rawMetadata, test.secondEtag, nil) |
| // Expect calls with the etag returned in the second call. |
| // It also retuens the secondEtag to avoid infinite test loop. |
| mockRetriever.EXPECT().FetchMetadata( |
| gomock.Eq(test.secondEtag)).Return( |
| test.rawMetadata, test.secondEtag, nil).AnyTimes() |
| |
| ch := SubscribeUserConfig(mockRetriever) |
| actualConfig := <-ch |
| if !proto.Equal(actualConfig, test.expectedConfig) { |
| t.Errorf("FAILED '%s': got %s, expect %s", |
| test.name, |
| proto.MarshalTextString(actualConfig), |
| proto.MarshalTextString(test.expectedConfig)) |
| } |
| |
| // make sure no more output from channel |
| select { |
| case <-ch: |
| t.Errorf("FAILED '%s': user config should only be outputed once.", |
| test.name) |
| default: |
| break |
| } |
| |
| mockCtrl.Finish() |
| } |
| } |
| |
| // Tests that parseRawMetadata() parses string correctly. |
| func TestParseRawMetadata(t *testing.T) { |
| tests := []struct { |
| name string |
| rawMetadata string |
| expectReturn map[string]string |
| expectError bool |
| }{ |
| { |
| "NoUpdate", |
| "{}", |
| map[string]string{}, |
| false, |
| }, |
| { |
| "NormalCase", |
| `{"key1":"value1","key2":"value2"}`, |
| map[string]string{ |
| "key1": "value1", |
| "key2": "value2", |
| }, |
| false, |
| }, |
| { |
| "BrokenCase", |
| `{"key2":"value2}`, |
| nil, |
| true, |
| }, |
| { |
| "BrokenCase2", |
| `{"key2":"value2"`, |
| nil, |
| true, |
| }, |
| } |
| |
| for _, test := range tests { |
| actualReturn, err := parseRawMetadata(test.rawMetadata) |
| if !reflect.DeepEqual(actualReturn, test.expectReturn) { |
| t.Errorf("FAILED '%s', got %v, expect %v", |
| test.name, actualReturn, test.expectReturn) |
| } |
| if err == nil && test.expectError { |
| t.Errorf("FAILED '%s': expect error", test.name) |
| } else if err != nil && !test.expectError { |
| t.Errorf("FAILED '%s': expect no error, got %s", |
| test.name, err) |
| } |
| } |
| } |