github.com/kyma-project/kyma-environment-broker@v0.0.1/common/orchestration/resolver_test.go (about)

     1  package orchestration
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/sirupsen/logrus"
    10  	"github.com/stretchr/testify/require"
    11  
    12  	"github.com/stretchr/testify/assert"
    13  
    14  	brokerapi "github.com/pivotal-cf/brokerapi/v8/domain"
    15  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    16  	k8s "k8s.io/apimachinery/pkg/runtime"
    17  	dynamicfake "k8s.io/client-go/dynamic/fake"
    18  	k8stesting "k8s.io/client-go/testing"
    19  
    20  	"github.com/kyma-project/kyma-environment-broker/common/gardener"
    21  	"github.com/kyma-project/kyma-environment-broker/common/runtime"
    22  )
    23  
    24  const (
    25  	shootNamespace = "garden-kyma"
    26  
    27  	globalAccountID1 = "f8576376-603b-40a8-9225-0edc65052463"
    28  	globalAccountID2 = "cb4d9447-8a6c-47d4-a2cd-48fa8121a91e"
    29  	globalAccountID3 = "cb4d9447-8a6c-47d4-a2cd-48fa8121a91e"
    30  
    31  	region1 = "westeurope"
    32  	region2 = "centralus"
    33  	region3 = "uksouth"
    34  
    35  	plan1 = "azure"
    36  	plan2 = "gcp"
    37  )
    38  
    39  func TestResolver_Resolve(t *testing.T) {
    40  	client := newFakeGardenerClient()
    41  	lister := newRuntimeListerMock()
    42  	defer lister.AssertExpectations(t)
    43  	logger := newLogDummy()
    44  	resolver := NewGardenerRuntimeResolver(client, shootNamespace, lister, logger)
    45  
    46  	expectedRuntime1 := expectedRuntime{
    47  		shoot:   &shoot1,
    48  		runtime: &runtime1,
    49  	}
    50  	expectedRuntime2 := expectedRuntime{
    51  		shoot:   &shoot2,
    52  		runtime: &runtime2,
    53  	}
    54  	expectedRuntime3 := expectedRuntime{
    55  		shoot:   &shoot3,
    56  		runtime: &runtime3,
    57  	}
    58  	expectedRuntime10 := expectedRuntime{
    59  		shoot:   &shoot10,
    60  		runtime: &runtime10,
    61  	}
    62  
    63  	for tn, tc := range map[string]struct {
    64  		Target           TargetSpec
    65  		ExpectedRuntimes []expectedRuntime
    66  	}{
    67  		"IncludeAll": {
    68  			Target: TargetSpec{
    69  				Include: []RuntimeTarget{
    70  					{
    71  						Target: TargetAll,
    72  					},
    73  				},
    74  				Exclude: nil,
    75  			},
    76  			ExpectedRuntimes: []expectedRuntime{expectedRuntime1, expectedRuntime2, expectedRuntime3, expectedRuntime10},
    77  		},
    78  		"IncludeAllExcludeOne": {
    79  			Target: TargetSpec{
    80  				Include: []RuntimeTarget{
    81  					{
    82  						Target: TargetAll,
    83  					},
    84  				},
    85  				Exclude: []RuntimeTarget{
    86  					{
    87  						GlobalAccount: expectedRuntime2.runtime.GlobalAccountID,
    88  						SubAccount:    expectedRuntime2.runtime.SubAccountID,
    89  					},
    90  				},
    91  			},
    92  			ExpectedRuntimes: []expectedRuntime{expectedRuntime1, expectedRuntime3, expectedRuntime10},
    93  		},
    94  		"ExcludeAll": {
    95  			Target: TargetSpec{
    96  				Include: []RuntimeTarget{
    97  					{
    98  						Target: TargetAll,
    99  					},
   100  				},
   101  				Exclude: []RuntimeTarget{
   102  					{
   103  						Target: TargetAll,
   104  					},
   105  				},
   106  			},
   107  			ExpectedRuntimes: []expectedRuntime{},
   108  		},
   109  		"IncludeOne": {
   110  			Target: TargetSpec{
   111  				Include: []RuntimeTarget{
   112  					{
   113  						GlobalAccount: expectedRuntime2.runtime.GlobalAccountID,
   114  						SubAccount:    expectedRuntime2.runtime.SubAccountID,
   115  					},
   116  				},
   117  				Exclude: nil,
   118  			},
   119  			ExpectedRuntimes: []expectedRuntime{expectedRuntime2},
   120  		},
   121  		"IncludeRuntime": {
   122  			Target: TargetSpec{
   123  				Include: []RuntimeTarget{
   124  					{
   125  						RuntimeID: "runtime-id-1",
   126  					},
   127  				},
   128  				Exclude: nil,
   129  			},
   130  			ExpectedRuntimes: []expectedRuntime{expectedRuntime1},
   131  		},
   132  		"IncludeInstance": {
   133  			Target: TargetSpec{
   134  				Include: []RuntimeTarget{
   135  					{
   136  						InstanceID: "instance-id-1",
   137  					},
   138  				},
   139  				Exclude: nil,
   140  			},
   141  			ExpectedRuntimes: []expectedRuntime{expectedRuntime1},
   142  		},
   143  		"IncludeTenant": {
   144  			Target: TargetSpec{
   145  				Include: []RuntimeTarget{
   146  					{
   147  						GlobalAccount: globalAccountID1,
   148  					},
   149  				},
   150  				Exclude: nil,
   151  			},
   152  			ExpectedRuntimes: []expectedRuntime{expectedRuntime1, expectedRuntime2, expectedRuntime10},
   153  		},
   154  		"IncludeRegion": {
   155  			Target: TargetSpec{
   156  				Include: []RuntimeTarget{
   157  					{
   158  						Region: "europe|eu|uk",
   159  					},
   160  				},
   161  				Exclude: nil,
   162  			},
   163  			ExpectedRuntimes: []expectedRuntime{expectedRuntime1, expectedRuntime3, expectedRuntime10},
   164  		},
   165  		"IncludePlanName": {
   166  			Target: TargetSpec{
   167  				Include: []RuntimeTarget{
   168  					{
   169  						PlanName: plan1,
   170  					},
   171  				},
   172  				Exclude: nil,
   173  			},
   174  			ExpectedRuntimes: []expectedRuntime{expectedRuntime2, expectedRuntime3, expectedRuntime10},
   175  		},
   176  		"IncludeShoot": {
   177  			Target: TargetSpec{
   178  				Include: []RuntimeTarget{
   179  					{
   180  						Shoot: expectedRuntime1.shoot.GetName(),
   181  					},
   182  				},
   183  				Exclude: nil,
   184  			},
   185  			ExpectedRuntimes: []expectedRuntime{expectedRuntime1},
   186  		},
   187  	} {
   188  		t.Run(tn, func(t *testing.T) {
   189  			// when
   190  			runtimes, err := resolver.Resolve(tc.Target)
   191  
   192  			// then
   193  			assert.Nil(t, err)
   194  
   195  			if len(tc.ExpectedRuntimes) != 0 {
   196  				assertRuntimeTargets(t, tc.ExpectedRuntimes, runtimes)
   197  			} else {
   198  				assert.Empty(t, runtimes)
   199  			}
   200  		})
   201  	}
   202  }
   203  
   204  func TestResolver_Resolve_GardenerFailure(t *testing.T) {
   205  	// given
   206  	fake := k8stesting.Fake{}
   207  	client := gardener.NewDynamicFakeClient()
   208  	client.Fake = fake
   209  	fake.AddReactor("list", "shoots", func(action k8stesting.Action) (bool, k8s.Object, error) {
   210  		return true, nil, fmt.Errorf("fake gardener client failure")
   211  	})
   212  	lister := newRuntimeListerMock()
   213  	defer lister.AssertExpectations(t)
   214  	logger := newLogDummy()
   215  	resolver := NewGardenerRuntimeResolver(client, shootNamespace, lister, logger)
   216  
   217  	// when
   218  	runtimes, err := resolver.Resolve(TargetSpec{
   219  		Include: []RuntimeTarget{
   220  			{
   221  				Target: TargetAll,
   222  			},
   223  		},
   224  		Exclude: nil,
   225  	})
   226  
   227  	// then
   228  	assert.NotNil(t, err)
   229  	assert.Len(t, runtimes, 0)
   230  }
   231  
   232  func TestResolver_Resolve_StorageFailure(t *testing.T) {
   233  	// given
   234  	client := newFakeGardenerClient()
   235  	lister := &RuntimeListerMock{}
   236  	lister.On("ListAllRuntimes").Return(
   237  		nil,
   238  		fmt.Errorf("mock storage failure"),
   239  	)
   240  	defer lister.AssertExpectations(t)
   241  	logger := newLogDummy()
   242  	resolver := NewGardenerRuntimeResolver(client, shootNamespace, lister, logger)
   243  
   244  	// when
   245  	runtimes, err := resolver.Resolve(TargetSpec{
   246  		Include: []RuntimeTarget{
   247  			{
   248  				Target: TargetAll,
   249  			},
   250  		},
   251  		Exclude: nil,
   252  	})
   253  
   254  	// then
   255  	assert.NotNil(t, err)
   256  	assert.Len(t, runtimes, 0)
   257  }
   258  
   259  var (
   260  	shoot1 = fixShoot(1, globalAccountID1, region1)
   261  	shoot2 = fixShoot(2, globalAccountID1, region2)
   262  	shoot3 = fixShoot(3, globalAccountID2, region3)
   263  	shoot4 = fixShoot(4, globalAccountID3, region1)
   264  	shoot5 = fixShoot(5, globalAccountID1, region1)
   265  	shoot6 = fixShoot(6, globalAccountID1, region1)
   266  	// shoot7 is purposefully missing to test missing cluster scenario
   267  	shoot8  = fixShoot(8, globalAccountID1, region1)
   268  	shoot9  = fixShoot(9, globalAccountID1, region1)
   269  	shoot10 = fixShoot(10, globalAccountID1, region1)
   270  	shoot11 = fixShoot(11, globalAccountID1, region1)
   271  
   272  	runtime1  = fixRuntimeDTO(1, globalAccountID1, plan2, runtimeOpState{provision: string(brokerapi.Succeeded)})
   273  	runtime2  = fixRuntimeDTO(2, globalAccountID1, plan1, runtimeOpState{provision: string(brokerapi.Succeeded)})
   274  	runtime3  = fixRuntimeDTO(3, globalAccountID2, plan1, runtimeOpState{provision: string(brokerapi.Succeeded)})
   275  	runtime4  = fixRuntimeDTO(4, globalAccountID3, plan1, runtimeOpState{provision: string(brokerapi.Succeeded), deprovision: string(brokerapi.InProgress)})
   276  	runtime5  = fixRuntimeDTO(5, globalAccountID3, plan1, runtimeOpState{provision: string(brokerapi.Failed)})
   277  	runtime6  = fixRuntimeDTO(6, globalAccountID3, plan2, runtimeOpState{provision: string(brokerapi.InProgress)})
   278  	runtime7  = fixRuntimeDTO(7, globalAccountID1, plan1, runtimeOpState{provision: string(brokerapi.Succeeded)})
   279  	runtime8  = fixRuntimeDTO(8, globalAccountID1, plan1, runtimeOpState{provision: string(brokerapi.Succeeded), suspension: string(brokerapi.Succeeded)})
   280  	runtime9  = fixRuntimeDTO(9, globalAccountID1, plan1, runtimeOpState{provision: string(brokerapi.Succeeded), suspension: string(brokerapi.InProgress)})
   281  	runtime10 = fixRuntimeDTO(10, globalAccountID1, plan1, runtimeOpState{provision: string(brokerapi.Succeeded), suspension: string(brokerapi.Succeeded), unsuspension: string(brokerapi.Succeeded)})
   282  	runtime11 = fixRuntimeDTO(11, globalAccountID1, plan1, runtimeOpState{provision: string(brokerapi.Succeeded), suspension: string(brokerapi.Succeeded), unsuspension: string(brokerapi.Failed)})
   283  )
   284  
   285  func fixShoot(id int, globalAccountID, region string) unstructured.Unstructured {
   286  	return unstructured.Unstructured{
   287  		Object: map[string]interface{}{
   288  			"apiVersion": "core.gardener.cloud/v1beta1",
   289  			"kind":       "Shoot",
   290  			"metadata": map[string]interface{}{
   291  				"name":      fmt.Sprintf("shoot%d", id),
   292  				"namespace": shootNamespace,
   293  				"labels": map[string]interface{}{
   294  					globalAccountLabel: globalAccountID,
   295  					subAccountLabel:    fmt.Sprintf("subaccount-id-%d", id),
   296  				},
   297  				"annotations": map[string]interface{}{
   298  					runtimeIDAnnotation: fmt.Sprintf("runtime-id-%d", id),
   299  				},
   300  			},
   301  			"spec": map[string]interface{}{
   302  				"region": region,
   303  				"maintenance": map[string]interface{}{
   304  					"timeWindow": map[string]interface{}{
   305  						"begin": "030000+0000",
   306  						"end":   "040000+0000",
   307  					},
   308  				},
   309  			},
   310  		},
   311  	}
   312  }
   313  
   314  type runtimeOpState struct {
   315  	provision    string
   316  	deprovision  string
   317  	suspension   string
   318  	unsuspension string
   319  }
   320  
   321  func fixRuntimeDTO(id int, globalAccountID, planName string, state runtimeOpState) runtime.RuntimeDTO {
   322  	rt := runtime.RuntimeDTO{
   323  		InstanceID:      fmt.Sprintf("instance-id-%d", id),
   324  		RuntimeID:       fmt.Sprintf("runtime-id-%d", id),
   325  		GlobalAccountID: globalAccountID,
   326  		SubAccountID:    fmt.Sprintf("subaccount-id-%d", id),
   327  		ServicePlanName: planName,
   328  		Status: runtime.RuntimeStatus{
   329  			Provisioning: &runtime.Operation{
   330  				State:     state.provision,
   331  				CreatedAt: time.Now(),
   332  			},
   333  		},
   334  	}
   335  
   336  	deprovTime := time.Now().Add(time.Minute)
   337  	if state.suspension != "" {
   338  		rt.Status.Suspension = &runtime.OperationsData{}
   339  		rt.Status.Suspension.Count = 1
   340  		rt.Status.Suspension.TotalCount = 1
   341  		rt.Status.Suspension.Data = []runtime.Operation{
   342  			{
   343  				State:     state.suspension,
   344  				CreatedAt: deprovTime,
   345  			},
   346  		}
   347  		state.deprovision = state.suspension
   348  	}
   349  
   350  	if state.deprovision != "" {
   351  		rt.Status.Deprovisioning = &runtime.Operation{
   352  			State:     state.deprovision,
   353  			CreatedAt: deprovTime,
   354  		}
   355  	}
   356  
   357  	if state.unsuspension != "" {
   358  		rt.Status.Unsuspension = &runtime.OperationsData{}
   359  		rt.Status.Unsuspension.Count = 1
   360  		rt.Status.Unsuspension.TotalCount = 1
   361  		rt.Status.Unsuspension.Data = []runtime.Operation{
   362  			{
   363  				State:     state.unsuspension,
   364  				CreatedAt: deprovTime.Add(time.Minute),
   365  			},
   366  		}
   367  	}
   368  
   369  	return rt
   370  }
   371  
   372  type expectedRuntime struct {
   373  	shoot   *unstructured.Unstructured
   374  	runtime *runtime.RuntimeDTO
   375  }
   376  
   377  func newFakeGardenerClient() *dynamicfake.FakeDynamicClient {
   378  	client := gardener.NewDynamicFakeClient(
   379  		&shoot1,
   380  		&shoot2,
   381  		&shoot3,
   382  		&shoot4,
   383  		&shoot5,
   384  		&shoot6,
   385  		&shoot8,
   386  		&shoot9,
   387  		&shoot10,
   388  	)
   389  
   390  	return client
   391  }
   392  
   393  func newRuntimeListerMock() *RuntimeListerMock {
   394  	lister := &RuntimeListerMock{}
   395  	lister.On("ListAllRuntimes").Maybe().Return(
   396  		[]runtime.RuntimeDTO{
   397  			runtime1,
   398  			runtime2,
   399  			runtime3,
   400  			runtime4,
   401  			runtime5,
   402  			runtime6,
   403  			runtime7,
   404  			runtime8,
   405  			runtime9,
   406  			runtime10,
   407  		},
   408  		nil,
   409  	)
   410  	return lister
   411  }
   412  
   413  func newLogDummy() *logrus.Entry {
   414  	rawLgr := logrus.New()
   415  	rawLgr.Out = ioutil.Discard
   416  	lgr := rawLgr.WithField("testing", true)
   417  
   418  	return lgr
   419  }
   420  
   421  func lookupRuntime(runtimeID string, runtimes []Runtime) *Runtime {
   422  	for _, r := range runtimes {
   423  		if r.RuntimeID == runtimeID {
   424  			return &r
   425  		}
   426  	}
   427  
   428  	return nil
   429  }
   430  
   431  func assertRuntimeTargets(t *testing.T, expectedRuntimes []expectedRuntime, runtimes []Runtime) {
   432  	require.Equal(t, len(expectedRuntimes), len(runtimes))
   433  
   434  	for _, e := range expectedRuntimes {
   435  		r := lookupRuntime(e.runtime.RuntimeID, runtimes)
   436  		s := gardener.Shoot{*e.shoot}
   437  		require.NotNil(t, r)
   438  		assert.Equal(t, e.runtime.InstanceID, r.InstanceID)
   439  		assert.Equal(t, e.runtime.GlobalAccountID, r.GlobalAccountID)
   440  		assert.Equal(t, e.runtime.SubAccountID, r.SubAccountID)
   441  		assert.Equal(t, s.GetName(), r.ShootName)
   442  		assert.Equal(t, s.GetSpecMaintenanceTimeWindowBegin(), r.MaintenanceWindowBegin.Format(maintenanceWindowFormat))
   443  		assert.Equal(t, s.GetSpecMaintenanceTimeWindowEnd(), r.MaintenanceWindowEnd.Format(maintenanceWindowFormat))
   444  	}
   445  }