blob: 4a6fac8438d757217dcd1af43ad21fb6877b69a0 [file] [log] [blame]
// 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)
}
}
}