diff --git a/pkg/networkservice/common/mechanisms/checkmechanism/check_client_sets_mechanism_preferences.go b/pkg/networkservice/common/mechanisms/checkmechanism/check_client_sets_mechanism_preferences.go new file mode 100644 index 000000000..17d49a42e --- /dev/null +++ b/pkg/networkservice/common/mechanisms/checkmechanism/check_client_sets_mechanism_preferences.go @@ -0,0 +1,81 @@ +// Copyright (c) 2020 Cisco and/or its affiliates. +// +// SPDX-License-Identifier: Apache-2.0 +// +// 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 checkmechanism provides TestSuites for use with implementations of a mechanism networkservice chain element. +package checkmechanism + +import ( + "context" + "testing" + + "github.com/networkservicemesh/api/pkg/api/networkservice" + "github.com/stretchr/testify/assert" + "google.golang.org/grpc" + + "github.com/networkservicemesh/sdk/pkg/networkservice/common/mechanisms" + "github.com/networkservicemesh/sdk/pkg/networkservice/core/adapters" + "github.com/networkservicemesh/sdk/pkg/networkservice/core/chain" + "github.com/networkservicemesh/sdk/pkg/networkservice/utils/checks/checkrequest" + "github.com/networkservicemesh/sdk/pkg/networkservice/utils/null" +) + +type checkClientSetsMechanismPreferences struct { + networkservice.NetworkServiceClient +} + +// CheckClientSetsMechanismPreferences - returns a NetworkServiceClient that will check to make sure that the +// clientUnderTest correctly sets the MechanismPreferences to include +// a mechanism of type mechanismType +// t - *testing.T for checking +// clientUnderTest - client we are testing +// mechanismType - Mechanism.Type implemented by clientUnderTest +// mechanismCheck - function to check for any parameters that should be present in +// the MechanismPreference +func CheckClientSetsMechanismPreferences(t *testing.T, clientUnderTest networkservice.NetworkServiceClient, mechanismType string, mechanismCheck func(*testing.T, *networkservice.Mechanism)) networkservice.NetworkServiceClient { + rv := &checkClientSetsMechanismPreferences{} + rv.NetworkServiceClient = chain.NewNetworkServiceClient( + clientUnderTest, + // Check after the clientUnderTest under test is run to make sure we have the right MechanismPreferences set + checkrequest.NewClient(t, + func(t *testing.T, req *networkservice.NetworkServiceRequest) { + var found bool + for _, mechanism := range req.GetMechanismPreferences() { + if mechanism.GetType() == mechanismType { + found = true + // Run any mechanism specific checks + mechanismCheck(t, mechanism) + } + } + assert.True(t, found, "Did not find %s Mechanism in MechanismPreferences", mechanismType) + }, + ), + // Its important to have a working 'server' to return a connection to us + adapters.NewServerToClient(mechanisms.NewServer( + map[string]networkservice.NetworkServiceServer{ + mechanismType: null.NewServer(), + }), + ), + ) + return rv +} + +func (m *checkClientSetsMechanismPreferences) Request(ctx context.Context, request *networkservice.NetworkServiceRequest, opts ...grpc.CallOption) (*networkservice.Connection, error) { + // Make sure we are creating a copy before we change it + request = request.Clone() + // Make sure no MechanismPreferences are set + request.MechanismPreferences = nil + return m.NetworkServiceClient.Request(ctx, request) +} diff --git a/pkg/networkservice/common/mechanisms/checkmechanism/check_context.go b/pkg/networkservice/common/mechanisms/checkmechanism/check_context.go new file mode 100644 index 000000000..e8d42b903 --- /dev/null +++ b/pkg/networkservice/common/mechanisms/checkmechanism/check_context.go @@ -0,0 +1,86 @@ +// Copyright (c) 2020 Cisco and/or its affiliates. +// +// SPDX-License-Identifier: Apache-2.0 +// +// 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 checkmechanism + +import ( + "context" + "testing" + + "github.com/networkservicemesh/api/pkg/api/networkservice" + + "github.com/networkservicemesh/sdk/pkg/networkservice/common/mechanisms" + "github.com/networkservicemesh/sdk/pkg/networkservice/core/adapters" + "github.com/networkservicemesh/sdk/pkg/networkservice/core/chain" + "github.com/networkservicemesh/sdk/pkg/networkservice/utils/checks/checkcontext" + "github.com/networkservicemesh/sdk/pkg/networkservice/utils/checks/checkcontextonreturn" + "github.com/networkservicemesh/sdk/pkg/networkservice/utils/null" +) + +// CheckClientContextOnReturn - returns a NetworkServiceClient that will check the state of the context.Context +// after the clientUnderTest has returned +// t - *testing.T for checks +// clientUnderTest - client we are testing - presumed to implement a mechanism +// mechanismType - Mechanism.Type implemented by the clientUnderTest +// check - function to check the state of the context.Context after the clientUnderTest has returned +func CheckClientContextOnReturn(t *testing.T, clientUnderTest networkservice.NetworkServiceClient, mechanismType string, check func(*testing.T, context.Context)) networkservice.NetworkServiceClient { + return chain.NewNetworkServiceClient( + checkcontextonreturn.NewClient(t, check), + clientUnderTest, + adapters.NewServerToClient(mechanisms.NewServer( + map[string]networkservice.NetworkServiceServer{ + mechanismType: null.NewServer(), + })), + ) +} + +// CheckClientContextAfter - returns a NetworkServiceClient that will check the state of the context.Context after it has left +// the clientUnderTest. Note: it should almost always be the case that there are no side effects +// related to implementing the Mechanism at this point, as the clientUnderTest can't know what to +// do until after the elements after it have returned a fully complete Connection. +// t - *testing.T for checks +// clientUnderTest - client we are testing - presumed to implement a mechanism +// mechanismType - Mechanism.Type implemented by the clientUnderTest +// check - function to check that the clientUnderTest has not taken action to implement the Mechanism +// (as it cannot know what to do at this stage) +func CheckClientContextAfter(t *testing.T, client networkservice.NetworkServiceClient, mechanismType string, check func(*testing.T, context.Context)) networkservice.NetworkServiceClient { + return chain.NewNetworkServiceClient( + client, + checkcontext.NewClient(t, check), + adapters.NewServerToClient(mechanisms.NewServer( + map[string]networkservice.NetworkServiceServer{ + mechanismType: null.NewServer(), + })), + ) +} + +// CheckContextAfterServer - returns a NetworkServiceServer that will check the state of the context.Context after it has +// left the serverUnderTest. At this time the context.Context should contain any side effects needed +// to implement the Mechanism, as it should have a complete Connection to work with. +// t - *testing.T for checks +// serverUnderTest - server we are testing - presumed to implement a mechanism +// mechanismType - Mechanism.Type implemented by the serverUnderTest +// check - function to check that the serverUnderTest has taken action to implement the Mechanism +func CheckContextAfterServer(t *testing.T, serverUnderTest networkservice.NetworkServiceServer, mechanismType string, check func(*testing.T, context.Context)) networkservice.NetworkServiceServer { + return chain.NewNetworkServiceServer( + mechanisms.NewServer( + map[string]networkservice.NetworkServiceServer{ + mechanismType: serverUnderTest, + }, + ), + checkcontext.NewServer(t, check), + ) +} diff --git a/pkg/networkservice/common/mechanisms/checkmechanism/client_suite.go b/pkg/networkservice/common/mechanisms/checkmechanism/client_suite.go new file mode 100644 index 000000000..b783b45b2 --- /dev/null +++ b/pkg/networkservice/common/mechanisms/checkmechanism/client_suite.go @@ -0,0 +1,137 @@ +// Copyright (c) 2020 Cisco and/or its affiliates. +// +// SPDX-License-Identifier: Apache-2.0 +// +// 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 checkmechanism + +import ( + "context" + "testing" + + "github.com/networkservicemesh/api/pkg/api/networkservice" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" + + "github.com/networkservicemesh/sdk/pkg/networkservice/utils/checks/checkerror" + "github.com/networkservicemesh/sdk/pkg/networkservice/utils/checks/checkopts" +) + +// ClientSuite - test suite to check that a NetworkServiceClient implementing a Mechanism meets basic contracts +type ClientSuite struct { + suite.Suite + clientUnderTest networkservice.NetworkServiceClient + configureContext func(ctx context.Context) context.Context + mechanismType string + mechanismCheck func(*testing.T, *networkservice.Mechanism) + contextOnReturnCheck func(*testing.T, context.Context) + contextCheck func(*testing.T, context.Context) + Request *networkservice.NetworkServiceRequest + ConnClose *networkservice.Connection +} + +// NewClientSuite - returns a ClientTestSuite +// clientUnderTest - the client we are testing to make sure it correctly implements a Mechanism +// configureContext - a function that is applied to context.Background to make sure anything +// needed by the clientUnderTest is present in the context.Context +// mechanismType - Mechanism.Type implemented by the clientUnderTest +// mechanismCheck - function to check that the clientUnderTest has properly added Mechanism.Parameters +// to the Mechanism it has appended to MechanismPreferences +// contextOnReturnCheck - function to check that the context.Context *after* clientUnderTest has returned are correct +// contextCheck - function to check that the clientUnderTest introduces no side effects to the context.Context +// before calling the next element in the chain +// request - NetworkServiceRequest to be used for testing +// connClose - Connection to be used for testing Close(...) +func NewClientSuite( + clientUnderTest networkservice.NetworkServiceClient, + configureContext func(ctx context.Context) context.Context, + mechanismType string, + mechanismCheck func(*testing.T, *networkservice.Mechanism), + contextOnReturnCheck, + contextCheck func(*testing.T, context.Context), + + request *networkservice.NetworkServiceRequest, + connClose *networkservice.Connection, +) *ClientSuite { + return &ClientSuite{ + clientUnderTest: clientUnderTest, + configureContext: configureContext, + mechanismType: mechanismType, + mechanismCheck: mechanismCheck, + contextOnReturnCheck: contextOnReturnCheck, + contextCheck: contextCheck, + Request: request, + ConnClose: connClose, + } +} + +// TestPropagatesError - tests that the clientUnderTest returns an error when the next element in the chain returned an error to it +func (m *ClientSuite) TestPropagatesError() { + check := checkerror.CheckPropagatesErrorClient(m.T(), m.clientUnderTest) + _, err := check.Request(m.configureContext(context.Background()), m.Request.Clone()) + assert.NotNil(m.T(), err) + _, err = check.Close(m.configureContext(context.Background()), m.ConnClose) + assert.NotNil(m.T(), err) +} + +// TestPropogatesOpts - tests that the clientUnderTest passes along the grpc.CallOptions to the next client in the chain +func (m *ClientSuite) TestPropogatesOpts() { + contract := checkopts.CheckPropogateOptsClient(m.T(), m.clientUnderTest) + _, err := contract.Request(m.configureContext(context.Background()), m.Request.Clone()) + assert.Nil(m.T(), err) + _, err = contract.Close(m.configureContext(context.Background()), m.ConnClose) + assert.Nil(m.T(), err) +} + +// TestSetsMechanismPreference - test that the clientUnderTest correctly sets MechanismPreferences for the Mechanism it implements +// before calling the next element in the chain +func (m *ClientSuite) TestSetsMechanismPreference() { + check := CheckClientSetsMechanismPreferences(m.T(), + m.clientUnderTest, + m.mechanismType, + m.mechanismCheck, + ) + conn, err := check.Request(m.configureContext(context.Background()), m.Request.Clone()) + assert.Nil(m.T(), err) + _, err = check.Close(m.configureContext(context.Background()), conn) + assert.Nil(m.T(), err) + _, err = check.Close(m.configureContext(context.Background()), m.ConnClose) + assert.Nil(m.T(), err) +} + +// TestContextOnReturn - test that the clientUnderTest has the correct side effects on the context.Context when it returns +func (m *ClientSuite) TestContextOnReturn() { + contract := CheckClientContextOnReturn(m.T(), + m.clientUnderTest, + m.mechanismType, + m.contextOnReturnCheck, + ) + _, err := contract.Request(m.configureContext(context.Background()), m.Request.Clone()) + assert.Nil(m.T(), err) + _, err = contract.Close(m.configureContext(context.Background()), m.ConnClose) + assert.Nil(m.T(), err) +} + +// TestContextAfter - tests that the clientUnderTest has no side effects on the context.Context before it calls the next element in the chain +func (m *ClientSuite) TestContextAfter() { + contract := CheckClientContextAfter(m.T(), + m.clientUnderTest, + m.mechanismType, + m.contextCheck, + ) + _, err := contract.Request(m.configureContext(context.Background()), m.Request.Clone()) + assert.Nil(m.T(), err) + _, err = contract.Close(m.configureContext(context.Background()), m.ConnClose) + assert.Nil(m.T(), err) +} diff --git a/pkg/networkservice/common/mechanisms/checkmechanism/server_suite.go b/pkg/networkservice/common/mechanisms/checkmechanism/server_suite.go new file mode 100644 index 000000000..7c73abf78 --- /dev/null +++ b/pkg/networkservice/common/mechanisms/checkmechanism/server_suite.go @@ -0,0 +1,88 @@ +// Copyright (c) 2020 Cisco and/or its affiliates. +// +// SPDX-License-Identifier: Apache-2.0 +// +// 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 checkmechanism + +import ( + "context" + "testing" + + "github.com/networkservicemesh/api/pkg/api/networkservice" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" + + "github.com/networkservicemesh/sdk/pkg/networkservice/utils/checks/checkerror" +) + +// ServerSuite - test suite to check that a NetworkServiceServer implementing a Mechanism meets basic contracts +type ServerSuite struct { + suite.Suite + serverUnderTest networkservice.NetworkServiceServer + configureContext func(ctx context.Context) context.Context + mechanismType string + contextCheck func(*testing.T, context.Context) + Request *networkservice.NetworkServiceRequest + ConnClose *networkservice.Connection +} + +// NewServerSuite - returns a ServerSuite +// serverUnderTest - the server being tested to make sure it correctly implements a Mechanism +// configureContext - a function that is applied to context.Background to make sure anything +// needed by the serverUnderTest is present in the context.Context +// mechanismType - Mechanism.Type implemented by the serverUnderTest +// contextCheck - function to check that the serverUnderTest introduces all needed side effects to the context.Context +// before calling the next element in the chain +// request - NetworkServiceRequest to be used for testing +// connClose - Connection to be used for testing Close(...) +func NewServerSuite( + serverUnderTest networkservice.NetworkServiceServer, + configureContext func(ctx context.Context) context.Context, + mechanismType string, + contextCheck func(*testing.T, context.Context), + request *networkservice.NetworkServiceRequest, + connClose *networkservice.Connection, +) *ServerSuite { + return &ServerSuite{ + serverUnderTest: serverUnderTest, + configureContext: configureContext, + mechanismType: mechanismType, + contextCheck: contextCheck, + Request: request, + ConnClose: connClose, + } +} + +// TestPropagatesError - tests that the serverUnderTest returns an error when the next element in the chain returned an error to it +func (m *ServerSuite) TestPropagatesError() { + contract := checkerror.CheckPropogatesErrorServer(m.T(), m.serverUnderTest) + _, err := contract.Request(m.configureContext(context.Background()), m.Request.Clone()) + assert.NotNil(m.T(), err) + _, err = contract.Close(m.configureContext(context.Background()), m.ConnClose) + assert.NotNil(m.T(), err) +} + +// TestContextAfter - tests that the serverUnderTest has all needed side effects on the context.Context before it calls the next element in the chain +func (m *ServerSuite) TestContextAfter() { + contract := CheckContextAfterServer(m.T(), + m.serverUnderTest, + m.mechanismType, + m.contextCheck, + ) + _, err := contract.Request(m.configureContext(context.Background()), m.Request.Clone()) + assert.Nil(m.T(), err) + _, err = contract.Close(m.configureContext(context.Background()), m.ConnClose) + assert.Nil(m.T(), err) +}