// Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). You may not // use this file except in compliance with the License. A copy of the // License is located at // // http://aws.amazon.com/apache2.0/ // // or in the "license" file accompanying this file. This file 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 processor manage polling of associations, dispatching association to processor package processor import ( "errors" "testing" "time" processor2 "github.com/aws/amazon-ssm-agent/agent/association/mocks/processor" "github.com/aws/amazon-ssm-agent/agent/association/mocks/service" complianceUploader "github.com/aws/amazon-ssm-agent/agent/association/mocks/uploader" "github.com/aws/amazon-ssm-agent/agent/association/model" "github.com/aws/amazon-ssm-agent/agent/association/schedulemanager" "github.com/aws/amazon-ssm-agent/agent/contracts" processormock "github.com/aws/amazon-ssm-agent/agent/framework/processor/mock" "github.com/aws/amazon-ssm-agent/agent/mocks/context" "github.com/aws/amazon-ssm-agent/agent/mocks/log" messageContracts "github.com/aws/amazon-ssm-agent/agent/runcommand/contracts" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ssm" "github.com/carlescere/scheduler" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) func TestSetJob(t *testing.T) { processor := Processor{} job := scheduler.Job{} processor.SetPollJob(&job) assert.NotNil(t, processor.pollJob) assert.Equal(t, processor.pollJob, &job) } func TestProcessAssociationUnableToGetAssociation(t *testing.T) { processor := createProcessor() svcMock := service.NewMockDefault() assocRawData := createAssociationRawData() complianceUploader := complianceUploader.NewMockDefault() processor.assocSvc = svcMock processor.complianceUploader = complianceUploader svcMock.On("CreateNewServiceIfUnHealthy", mock.AnythingOfType("*context.Mock")) svcMock.On( "ListInstanceAssociations", mock.AnythingOfType("*log.Mock"), mock.AnythingOfType("string")).Return(assocRawData, errors.New("unable to load association")) svcMock.On( "LoadAssociationDetail", mock.AnythingOfType("*log.Mock"), mock.AnythingOfType("*model.InstanceAssociation")).Return(nil) complianceUploader.On("CreateNewServiceIfUnHealthy", mock.AnythingOfType("*log.Mock")) processor.ProcessAssociation() assert.True(t, complianceUploader.AssertNumberOfCalls(t, "CreateNewServiceIfUnHealthy", 1)) assert.True(t, svcMock.AssertNumberOfCalls(t, "CreateNewServiceIfUnHealthy", 1)) assert.True(t, svcMock.AssertNumberOfCalls(t, "ListInstanceAssociations", 1)) assert.True(t, svcMock.AssertNumberOfCalls(t, "LoadAssociationDetail", 0)) } func TestProcessAssociationUnableToLoadAssociationDetail(t *testing.T) { processor := createProcessor() svcMock := service.NewMockDefault() assocRawData := createAssociationRawData() parserMock := processor2.ParserMock{} complianceUploader := complianceUploader.NewMockDefault() // Arrange processor.assocSvc = svcMock processor.complianceUploader = complianceUploader assocParser = &parserMock // Mock service svcMock.On("CreateNewServiceIfUnHealthy", mock.AnythingOfType("*context.Mock")) svcMock.On( "ListInstanceAssociations", mock.AnythingOfType("*log.Mock"), mock.AnythingOfType("string")).Return(assocRawData, nil) svcMock.On( "LoadAssociationDetail", mock.AnythingOfType("*log.Mock"), mock.AnythingOfType("*model.InstanceAssociation")).Return(errors.New("unable to load detail")) svcMock.On( "UpdateInstanceAssociationStatus", mock.AnythingOfType("*log.Mock"), mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("*ssm.InstanceAssociationExecutionResult")) complianceUploader.On("CreateNewServiceIfUnHealthy", mock.AnythingOfType("*log.Mock")) complianceUploader.On( "UpdateAssociationCompliance", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("time.Time")).Return(nil) // Act processor.ProcessAssociation() // Assert assert.True(t, svcMock.AssertNumberOfCalls(t, "CreateNewServiceIfUnHealthy", 1)) assert.True(t, svcMock.AssertNumberOfCalls(t, "ListInstanceAssociations", 1)) assert.True(t, svcMock.AssertNumberOfCalls(t, "LoadAssociationDetail", 1)) assert.True(t, svcMock.AssertNumberOfCalls(t, "UpdateInstanceAssociationStatus", 1)) } func TestProcessAssociationUnableToParseAssociation(t *testing.T) { processor := createProcessor() svcMock := service.NewMockDefault() assocRawData := createAssociationRawData() output := ssm.UpdateInstanceAssociationStatusOutput{} complianceUploader := complianceUploader.NewMockDefault() parserMock := processor2.ParserMock{} // Arrange processor.assocSvc = svcMock assocParser = &parserMock processor.complianceUploader = complianceUploader // Mock service mockService(svcMock, assocRawData, &output) // Mock processor processorMock := &processormock.MockedProcessor{} processor.proc = processorMock ch := make(chan contracts.DocumentResult) processorMock.On("Start").Return(ch, nil) processorMock.On("InitialProcessing", false).Return(nil) complianceUploader.On("CreateNewServiceIfUnHealthy", mock.AnythingOfType("*log.Mock")) // Act processor.InitializeAssociationProcessor() processor.ProcessAssociation() close(ch) // Assert assert.True(t, svcMock.AssertNumberOfCalls(t, "CreateNewServiceIfUnHealthy", 1)) assert.True(t, svcMock.AssertNumberOfCalls(t, "ListInstanceAssociations", 1)) assert.True(t, svcMock.AssertNumberOfCalls(t, "LoadAssociationDetail", 1)) } func mockService(svcMock *service.AssociationServiceMock, assocRawData []*model.InstanceAssociation, output *ssm.UpdateInstanceAssociationStatusOutput) { svcMock.On("CreateNewServiceIfUnHealthy", mock.AnythingOfType("*context.Mock")) svcMock.On( "ListInstanceAssociations", mock.AnythingOfType("*log.Mock"), mock.AnythingOfType("string")).Return(assocRawData, nil) svcMock.On( "LoadAssociationDetail", mock.AnythingOfType("*log.Mock"), mock.AnythingOfType("*model.InstanceAssociation")).Return(nil) svcMock.On( "UpdateInstanceAssociationStatus", mock.AnythingOfType("*log.Mock"), mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("*ssm.InstanceAssociationExecutionResult")) } func TestProcessAssociationSuccessful(t *testing.T) { processor := createProcessor() svcMock := service.NewMockDefault() assocRawData := createAssociationRawData() output := ssm.UpdateInstanceAssociationStatusOutput{} payload := messageContracts.SendCommandPayload{} docState := contracts.DocumentState{} parserMock := processor2.ParserMock{} complianceUploader := complianceUploader.NewMockDefault() processorMock := &processormock.MockedProcessor{} // Arrange processor.assocSvc = svcMock processor.proc = processorMock assocParser = &parserMock processor.complianceUploader = complianceUploader // Mock service mockService(svcMock, assocRawData, &output) // Mock parser mockParser(&parserMock, &payload, docState) // Mock processor ch := make(chan contracts.DocumentResult) processorMock.On("Start").Return(ch, nil) processorMock.On("InitialProcessing", false).Return(nil) complianceUploader.On("CreateNewServiceIfUnHealthy", mock.AnythingOfType("*log.Mock")) // Act processor.InitializeAssociationProcessor() processor.ProcessAssociation() //make sure the processor is invoked as expected close(ch) processorMock.AssertExpectations(t) // Assert assert.True(t, svcMock.AssertNumberOfCalls(t, "CreateNewServiceIfUnHealthy", 1)) assert.True(t, svcMock.AssertNumberOfCalls(t, "ListInstanceAssociations", 1)) assert.True(t, svcMock.AssertNumberOfCalls(t, "LoadAssociationDetail", 1)) assert.True(t, svcMock.AssertNumberOfCalls(t, "UpdateInstanceAssociationStatus", 0)) assert.True(t, complianceUploader.AssertNumberOfCalls(t, "UpdateAssociationCompliance", 0)) } // make sure this operation is thread safe func TestUpdatePluginAssociationInstances(t *testing.T) { testAssociationID := "testAssociationID" testName := "testName" testAssociations := []*model.InstanceAssociation{ &model.InstanceAssociation{ Association: &ssm.InstanceAssociationSummary{ AssociationId: &testAssociationID, Name: &testName, }, }, } schedulemanager.Refresh(log.NewMockLog(), testAssociations) assert.Equal(t, len(pluginAssociationInstances), 0) testDocState := contracts.DocumentState{ InstancePluginsInformation: []contracts.PluginState{ contracts.PluginState{ Name: "pluginName", }, }, } updatePluginAssociationInstances("testAssociationID", &testDocState) assert.EqualValues(t, AssocList{testAssociationID}, testDocState.InstancePluginsInformation[0].Configuration.CurrentAssociations) resultMap := make(map[string]AssocList) resultMap["pluginName"] = []string{testAssociationID} assert.Equal(t, resultMap, pluginAssociationInstances) } func TestRemovePluginAssociationInstances(t *testing.T) { testAssociationID := "testAssociationID" testRemovedAssociationID := "removedID" testName := "testName" testAssociations := []*model.InstanceAssociation{ &model.InstanceAssociation{ Association: &ssm.InstanceAssociationSummary{ AssociationId: &testAssociationID, Name: &testName, }, }, } schedulemanager.Refresh(log.NewMockLog(), testAssociations) pluginAssociationInstances["pluginName"] = AssocList{testAssociationID, testRemovedAssociationID} assert.Equal(t, len(pluginAssociationInstances), 1) testDocState := contracts.DocumentState{ InstancePluginsInformation: []contracts.PluginState{ contracts.PluginState{ Name: "pluginName", }, }, } updatePluginAssociationInstances("testAssociationID", &testDocState) assert.EqualValues(t, AssocList{testAssociationID}, testDocState.InstancePluginsInformation[0].Configuration.CurrentAssociations) resultMap := make(map[string]AssocList) resultMap["pluginName"] = []string{testAssociationID} assert.Equal(t, resultMap, pluginAssociationInstances) } func mockParser(parserMock *processor2.ParserMock, payload *messageContracts.SendCommandPayload, docState contracts.DocumentState) { parserMock.On( "InitializeDocumentState", mock.AnythingOfType("*context.Mock"), mock.AnythingOfType("*contracts.SendCommandPayload"), mock.AnythingOfType("*model.InstanceAssociation")).Return(docState) } func createProcessor() *Processor { processor := Processor{} processor.context = context.NewMockDefault() return &processor } func createAssociationRawData() []*model.InstanceAssociation { association := ssm.InstanceAssociationSummary{ Name: aws.String("Test-Association"), DocumentVersion: aws.String("1"), AssociationId: aws.String("Id-Test"), InstanceId: aws.String("test-association-id"), Checksum: aws.String("checksum"), LastExecutionDate: aws.Time(time.Now().UTC()), ScheduleExpression: aws.String("cron(0 0/5 * 1/1 * ? *)"), } assocRawData := model.InstanceAssociation{ Association: &association, } return []*model.InstanceAssociation{&assocRawData} }