github.com/ystia/yorc/v4@v4.3.0/plugin/action_test.go (about)

     1  // Copyright 2018 Bull S.A.S. Atos Technologies - Bull, Rue Jean Jaures, B.P.68, 78340, Les Clayes-sous-Bois, France.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package plugin
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"testing"
    21  	"time"
    22  
    23  	plugin "github.com/hashicorp/go-plugin"
    24  	"github.com/stretchr/testify/assert"
    25  	"github.com/stretchr/testify/require"
    26  
    27  	"github.com/ystia/yorc/v4/config"
    28  	"github.com/ystia/yorc/v4/events"
    29  	"github.com/ystia/yorc/v4/prov"
    30  )
    31  
    32  type mockActionOperator struct {
    33  	execactionCalled     bool
    34  	ctx                  context.Context
    35  	conf                 config.Configuration
    36  	taskID, deploymentID string
    37  	action               *prov.Action
    38  	contextCancelled     bool
    39  	lof                  events.LogOptionalFields
    40  }
    41  
    42  func (m *mockActionOperator) ExecAction(ctx context.Context, conf config.Configuration, taskID, deploymentID string, action *prov.Action) (deregister bool, err error) {
    43  
    44  	m.execactionCalled = true
    45  	m.ctx = ctx
    46  	m.conf = conf
    47  	m.taskID = taskID
    48  	m.deploymentID = deploymentID
    49  	m.action = action
    50  	m.lof, _ = events.FromContext(ctx)
    51  
    52  	go func() {
    53  		<-m.ctx.Done()
    54  		m.contextCancelled = true
    55  	}()
    56  	if m.deploymentID == "TestCancel" {
    57  		<-m.ctx.Done()
    58  	}
    59  	if m.deploymentID == "TestFailure" {
    60  		return false, NewRPCError(errors.New("a failure occurred during plugin exec operation"))
    61  	}
    62  	return true, nil
    63  }
    64  
    65  func createMockActionOperatorClient(t *testing.T) (*mockActionOperator, *plugin.RPCClient) {
    66  	mock := new(mockActionOperator)
    67  	client, _ := plugin.TestPluginRPCConn(
    68  		t,
    69  		map[string]plugin.Plugin{
    70  			ActionPluginName: &ActionPlugin{F: func() prov.ActionOperator {
    71  				return mock
    72  			}},
    73  		},
    74  		nil)
    75  	return mock, client
    76  }
    77  
    78  func setupExecActionTestEnv(t *testing.T) (*mockActionOperator, *plugin.RPCClient,
    79  	prov.ActionOperator, prov.Action, events.LogOptionalFields, context.Context) {
    80  
    81  	mock, client := createMockActionOperatorClient(t)
    82  
    83  	raw, err := client.Dispense(ActionPluginName)
    84  	require.Nil(t, err)
    85  
    86  	plugin := raw.(prov.ActionOperator)
    87  	testOperation := prov.Operation{
    88  		Name:                   "testOperationName",
    89  		ImplementationArtifact: "tosca.artifacts.Implementation.Bash",
    90  		RelOp: prov.RelationshipOperation{
    91  			IsRelationshipOperation: true,
    92  			RequirementIndex:        "1",
    93  			TargetNodeName:          "AnotherNode",
    94  		},
    95  	}
    96  	testAsyncOperation := prov.AsyncOperation{
    97  		DeploymentID: "testDeploymentID",
    98  		TaskID:       "testTaskID",
    99  		ExecutionID:  "testExecutionID",
   100  		WorkflowName: "testWorkflowName",
   101  		StepName:     "testStepName",
   102  		NodeName:     "testNodeName",
   103  		Operation:    testOperation,
   104  	}
   105  	testData := map[string]string{
   106  		"data1": "value1",
   107  		"data2": "value2",
   108  	}
   109  	testAction := prov.Action{
   110  		ID:             "testActionID",
   111  		ActionType:     "testActionType",
   112  		AsyncOperation: testAsyncOperation,
   113  		Data:           testData,
   114  	}
   115  	lof := events.LogOptionalFields{
   116  		events.WorkFlowID:    "testWF",
   117  		events.InterfaceName: "action",
   118  		events.OperationName: "testOperationName",
   119  	}
   120  	ctx := events.NewContext(context.Background(), lof)
   121  
   122  	return mock, client, plugin, testAction, lof, ctx
   123  
   124  }
   125  func TestActionOperatorExecAction(t *testing.T) {
   126  	t.Parallel()
   127  	mock, client, plugin, action, lof, ctx := setupExecActionTestEnv(t)
   128  	defer client.Close()
   129  	deregister, err := plugin.ExecAction(
   130  		ctx,
   131  		config.Configuration{Consul: config.Consul{Address: "test", Datacenter: "testdc"}},
   132  		"testTaskID", "testDeploymentID", &action)
   133  	require.NoError(t, err, "Failed to cal plugin ExecAction")
   134  	require.True(t, mock.execactionCalled)
   135  	require.Equal(t, "test", mock.conf.Consul.Address)
   136  	require.Equal(t, "testdc", mock.conf.Consul.Datacenter)
   137  	require.Equal(t, "testTaskID", mock.taskID)
   138  	require.Equal(t, "testDeploymentID", mock.deploymentID)
   139  	require.Equal(t, action, *mock.action)
   140  	require.True(t, deregister, "Wrong return value for ExecAction")
   141  	assert.Equal(t, lof, mock.lof)
   142  }
   143  
   144  func TestActionOperatorExecActionWrongContext(t *testing.T) {
   145  	t.Parallel()
   146  	_, client, plugin, action, _, _ := setupExecActionTestEnv(t)
   147  	defer client.Close()
   148  	var wrongContext context.Context
   149  	_, err := plugin.ExecAction(
   150  		wrongContext,
   151  		config.Configuration{Consul: config.Consul{Address: "test", Datacenter: "testdc"}},
   152  		"testTaskID", "testDeploymentID", &action)
   153  	require.Error(t, err, "Expected an error calling ExecAction with wrong context")
   154  
   155  }
   156  
   157  func TestActionOperatorExecActionWithFailure(t *testing.T) {
   158  	t.Parallel()
   159  	mock, client, plugin, action, _, ctx := setupExecActionTestEnv(t)
   160  	defer client.Close()
   161  	deregister, err := plugin.ExecAction(
   162  		ctx,
   163  		config.Configuration{Consul: config.Consul{Address: "test", Datacenter: "testdc"}},
   164  		"testTaskID", "TestFailure", &action)
   165  	require.True(t, mock.execactionCalled)
   166  	require.Error(t, err, "An error was expected during executing plugin operation")
   167  	require.EqualError(t, err, "a failure occurred during plugin exec operation")
   168  	require.False(t, deregister, "Wrong return value for ExecAction")
   169  }
   170  
   171  func TestActionOperatorExecActionWithCancel(t *testing.T) {
   172  	t.Parallel()
   173  	mock, client := createMockActionOperatorClient(t)
   174  	defer client.Close()
   175  
   176  	raw, err := client.Dispense(ActionPluginName)
   177  	require.NoError(t, err, "Unexpected error calling client.Dispense() for Action Plugin")
   178  
   179  	plugin := raw.(prov.ActionOperator)
   180  	lof := events.LogOptionalFields{
   181  		events.WorkFlowID:    "testWF",
   182  		events.InterfaceName: "delegate",
   183  		events.OperationName: "myTest",
   184  	}
   185  	ctx := events.NewContext(context.Background(), lof)
   186  	ctx, cancelF := context.WithCancel(ctx)
   187  	go func() {
   188  		_, err := plugin.ExecAction(
   189  			ctx,
   190  			config.Configuration{Consul: config.Consul{Address: "test", Datacenter: "testdc"}},
   191  			"testTaskID", "TestCancel", &prov.Action{})
   192  		require.NoError(t, err)
   193  	}()
   194  	cancelF()
   195  	// Wait for cancellation signal to be dispatched
   196  	time.Sleep(50 * time.Millisecond)
   197  	require.True(t, mock.contextCancelled, "Context not cancelled")
   198  }
   199  
   200  func TestActionOperatorGetActionTypes(t *testing.T) {
   201  	mock := new(mockActionOperator)
   202  	client, _ := plugin.TestPluginRPCConn(
   203  		t,
   204  		map[string]plugin.Plugin{
   205  			ActionPluginName: &ActionPlugin{
   206  				F: func() prov.ActionOperator {
   207  					return mock
   208  				},
   209  				ActionTypes: []string{"TestActionType1", "TestActionType2"}},
   210  		},
   211  		nil)
   212  	defer client.Close()
   213  	raw, err := client.Dispense(ActionPluginName)
   214  	require.NoError(t, err, "Unexpected error calling client.Dispense() for Action Plugin")
   215  	plugin := raw.(ActionOperator)
   216  
   217  	actionTypes, err := plugin.GetActionTypes()
   218  	require.NoError(t, err, "Unexpected error calling plugin.GetActionTypes()")
   219  	require.Len(t, actionTypes, 2)
   220  	require.Contains(t, actionTypes, "TestActionType1")
   221  	require.Contains(t, actionTypes, "TestActionType2")
   222  
   223  }