github.com/ystia/yorc/v4@v4.3.0/plugin/operation_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 mockOperationExecutor struct { 33 execoperationCalled bool 34 execAsyncOperationCalled bool 35 ctx context.Context 36 conf config.Configuration 37 taskID, deploymentID, nodeName string 38 operation prov.Operation 39 contextCancelled bool 40 lof events.LogOptionalFields 41 } 42 43 func (m *mockOperationExecutor) ExecAsyncOperation(ctx context.Context, conf config.Configuration, taskID, deploymentID, nodeName string, operation prov.Operation, stepName string) (*prov.Action, time.Duration, error) { 44 m.execAsyncOperationCalled = true 45 m.ctx = ctx 46 m.conf = conf 47 m.taskID = taskID 48 m.deploymentID = deploymentID 49 m.nodeName = nodeName 50 m.operation = operation 51 m.lof, _ = events.FromContext(ctx) 52 53 go func() { 54 <-m.ctx.Done() 55 m.contextCancelled = true 56 }() 57 if m.deploymentID == "TestCancel" { 58 <-m.ctx.Done() 59 } 60 if m.deploymentID == "TestFailure" { 61 return nil, 0, NewRPCError(errors.New("a failure occurred during plugin exec async operation")) 62 } 63 action := prov.Action{ 64 ID: "testID", 65 ActionType: "testActionType", 66 AsyncOperation: prov.AsyncOperation{ 67 DeploymentID: deploymentID, 68 TaskID: taskID, 69 StepName: stepName, 70 NodeName: nodeName, 71 Operation: operation, 72 }, 73 Data: map[string]string{ 74 "testDataKey": "testDataValue", 75 }, 76 } 77 return &action, 5 * time.Second, nil 78 } 79 80 func (m *mockOperationExecutor) ExecOperation(ctx context.Context, conf config.Configuration, taskID, deploymentID, nodeName string, operation prov.Operation) error { 81 m.execoperationCalled = true 82 m.ctx = ctx 83 m.conf = conf 84 m.taskID = taskID 85 m.deploymentID = deploymentID 86 m.nodeName = nodeName 87 m.operation = operation 88 m.lof, _ = events.FromContext(ctx) 89 90 go func() { 91 <-m.ctx.Done() 92 m.contextCancelled = true 93 }() 94 if m.deploymentID == "TestCancel" { 95 <-m.ctx.Done() 96 } 97 if m.deploymentID == "TestFailure" { 98 return NewRPCError(errors.New("a failure occurred during plugin exec operation")) 99 } 100 return nil 101 } 102 103 func createMockOperationExecutorClient(t *testing.T) (*mockOperationExecutor, *plugin.RPCClient) { 104 mock := new(mockOperationExecutor) 105 client, _ := plugin.TestPluginRPCConn( 106 t, 107 map[string]plugin.Plugin{ 108 OperationPluginName: &OperationPlugin{F: func() prov.OperationExecutor { 109 return mock 110 }}, 111 }, 112 nil) 113 return mock, client 114 } 115 116 func setupExecOperationTestEnv(t *testing.T) (*mockOperationExecutor, *plugin.RPCClient, 117 prov.OperationExecutor, prov.Operation, events.LogOptionalFields, context.Context) { 118 119 mock, client := createMockOperationExecutorClient(t) 120 121 raw, err := client.Dispense(OperationPluginName) 122 require.Nil(t, err) 123 124 plugin := raw.(prov.OperationExecutor) 125 op := prov.Operation{ 126 Name: "myOps", 127 ImplementationArtifact: "tosca.artifacts.Implementation.Bash", 128 RelOp: prov.RelationshipOperation{ 129 IsRelationshipOperation: true, 130 RequirementIndex: "1", 131 TargetNodeName: "AnotherNode", 132 }, 133 } 134 lof := events.LogOptionalFields{ 135 events.WorkFlowID: "testWF", 136 events.InterfaceName: "delegate", 137 events.OperationName: "myTest", 138 } 139 ctx := events.NewContext(context.Background(), lof) 140 141 return mock, client, plugin, op, lof, ctx 142 143 } 144 func TestOperationExecutorExecOperation(t *testing.T) { 145 t.Parallel() 146 mock, client, plugin, op, lof, ctx := setupExecOperationTestEnv(t) 147 defer client.Close() 148 err := plugin.ExecOperation( 149 ctx, 150 config.Configuration{Consul: config.Consul{Address: "test", Datacenter: "testdc"}}, 151 "TestTaskID", "TestDepID", "TestNodeName", op) 152 require.Nil(t, err) 153 require.True(t, mock.execoperationCalled) 154 require.Equal(t, "test", mock.conf.Consul.Address) 155 require.Equal(t, "testdc", mock.conf.Consul.Datacenter) 156 require.Equal(t, "TestTaskID", mock.taskID) 157 require.Equal(t, "TestDepID", mock.deploymentID) 158 require.Equal(t, "TestNodeName", mock.nodeName) 159 require.Equal(t, op, mock.operation) 160 assert.Equal(t, lof, mock.lof) 161 } 162 163 func TestOperationExecutorExecOperationWithFailure(t *testing.T) { 164 t.Parallel() 165 mock, client, plugin, op, _, ctx := setupExecOperationTestEnv(t) 166 defer client.Close() 167 err := plugin.ExecOperation( 168 ctx, 169 config.Configuration{Consul: config.Consul{Address: "test", Datacenter: "testdc"}}, 170 "TestTaskID", "TestFailure", "TestNodeName", op) 171 require.True(t, mock.execoperationCalled) 172 require.Error(t, err, "An error was expected during executing plugin operation") 173 require.EqualError(t, err, "a failure occurred during plugin exec operation") 174 } 175 176 func TestOperationExecutorExecOperationWithCancel(t *testing.T) { 177 t.Parallel() 178 mock, client := createMockOperationExecutorClient(t) 179 defer client.Close() 180 181 raw, err := client.Dispense(OperationPluginName) 182 require.Nil(t, err) 183 184 plugin := raw.(prov.OperationExecutor) 185 lof := events.LogOptionalFields{ 186 events.WorkFlowID: "testWF", 187 events.InterfaceName: "delegate", 188 events.OperationName: "myTest", 189 } 190 ctx := events.NewContext(context.Background(), lof) 191 ctx, cancelF := context.WithCancel(ctx) 192 go func() { 193 err = plugin.ExecOperation( 194 ctx, 195 config.Configuration{Consul: config.Consul{Address: "test", Datacenter: "testdc"}}, 196 "TestTaskID", "TestCancel", "TestNodeName", prov.Operation{}) 197 require.Nil(t, err) 198 }() 199 cancelF() 200 // Wait for cancellation signal to be dispatched 201 time.Sleep(50 * time.Millisecond) 202 require.True(t, mock.contextCancelled, "Context not cancelled") 203 } 204 205 func TestOperationGetSupportedArtifactTypes(t *testing.T) { 206 mock := new(mockOperationExecutor) 207 client, _ := plugin.TestPluginRPCConn( 208 t, 209 map[string]plugin.Plugin{ 210 OperationPluginName: &OperationPlugin{ 211 F: func() prov.OperationExecutor { 212 return mock 213 }, 214 SupportedTypes: []string{"tosca.my.types", "test"}}, 215 }, 216 nil) 217 defer client.Close() 218 raw, err := client.Dispense(OperationPluginName) 219 require.Nil(t, err) 220 plugin := raw.(OperationExecutor) 221 222 supportedTypes, err := plugin.GetSupportedArtifactTypes() 223 require.Nil(t, err) 224 require.Len(t, supportedTypes, 2) 225 require.Contains(t, supportedTypes, "tosca.my.types") 226 require.Contains(t, supportedTypes, "test") 227 } 228 229 func TestOperationExecutorExecAsyncOperation(t *testing.T) { 230 t.Parallel() 231 mock, client, plugin, op, lof, ctx := setupExecOperationTestEnv(t) 232 defer client.Close() 233 action, interval, err := plugin.ExecAsyncOperation( 234 ctx, 235 config.Configuration{Consul: config.Consul{Address: "test", Datacenter: "testdc"}}, 236 "TestTaskID", "TestDepID", "TestNodeName", op, "TestStepName") 237 require.NoError(t, err, "Failed to call plugin ExecAsyncOperation") 238 require.True(t, mock.execAsyncOperationCalled) 239 require.Equal(t, "TestTaskID", action.AsyncOperation.TaskID) 240 require.Equal(t, "testDataValue", action.Data["testDataKey"]) 241 require.Equal(t, "TestNodeName", action.AsyncOperation.NodeName) 242 require.Equal(t, op, action.AsyncOperation.Operation) 243 require.Equal(t, 5*time.Second, interval) 244 assert.Equal(t, lof, mock.lof) 245 } 246 247 func TestOperationExecutorExecAsyncOperationWithFailure(t *testing.T) { 248 t.Parallel() 249 mock, client, plugin, op, _, ctx := setupExecOperationTestEnv(t) 250 defer client.Close() 251 _, _, err := plugin.ExecAsyncOperation( 252 ctx, 253 config.Configuration{Consul: config.Consul{Address: "test", Datacenter: "testdc"}}, 254 "TestTaskID", "TestFailure", "TestNodeName", op, "TestStepName") 255 require.True(t, mock.execAsyncOperationCalled) 256 require.Error(t, err, "An error was expected calling plugin ExecAsyncOperation") 257 require.EqualError(t, err, "a failure occurred during plugin exec async operation") 258 } 259 260 func TestOperationExecutorExecAsyncOperationWithCancel(t *testing.T) { 261 t.Parallel() 262 mock, client := createMockOperationExecutorClient(t) 263 defer client.Close() 264 265 raw, err := client.Dispense(OperationPluginName) 266 require.Nil(t, err) 267 268 plugin := raw.(prov.OperationExecutor) 269 lof := events.LogOptionalFields{ 270 events.WorkFlowID: "testWF", 271 events.InterfaceName: "delegate", 272 events.OperationName: "myTest", 273 } 274 ctx := events.NewContext(context.Background(), lof) 275 ctx, cancelF := context.WithCancel(ctx) 276 go func() { 277 _, _, err := plugin.ExecAsyncOperation( 278 ctx, 279 config.Configuration{Consul: config.Consul{Address: "test", Datacenter: "testdc"}}, 280 "TestTaskID", "TestCancel", "TestNodeName", prov.Operation{}, "TestStepName") 281 require.Nil(t, err) 282 }() 283 cancelF() 284 // Wait for cancellation signal to be dispatched 285 time.Sleep(50 * time.Millisecond) 286 require.True(t, mock.contextCancelled, "Context not cancelled") 287 }