/
unique_instance_names_interceptor.go
146 lines (124 loc) · 4.92 KB
/
unique_instance_names_interceptor.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/*
* Copyright 2018 The Service Manager Authors
*
* 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 interceptors
import (
"context"
"fmt"
"net/http"
"github.com/Peripli/service-manager/operations/opcontext"
"github.com/Peripli/service-manager/pkg/log"
"github.com/Peripli/service-manager/pkg/util"
"github.com/Peripli/service-manager/pkg/query"
"github.com/Peripli/service-manager/pkg/types"
"github.com/Peripli/service-manager/storage"
)
const (
UniqueInstanceNameCreateInterceptorName = "UniqueInstanceNameCreateInterceptor"
UniqueInstanceNameUpdateInterceptorName = "UniqueInstanceNameUpdateInterceptor"
nameProperty = "name"
)
// UniqueInstanceNameCreateInterceptorProvider provides an interceptor that forbids creation of instances with the same name in a given tenant
type UniqueInstanceNameCreateInterceptorProvider struct {
TenantIdentifier string
Repository storage.TransactionalRepository
}
func (c *UniqueInstanceNameCreateInterceptorProvider) Name() string {
return UniqueInstanceNameCreateInterceptorName
}
func (c *UniqueInstanceNameCreateInterceptorProvider) Provide() storage.CreateAroundTxInterceptor {
return &uniqueInstanceNameInterceptor{
TenantIdentifier: c.TenantIdentifier,
Repository: c.Repository,
}
}
// UniqueInstanceNameUpdateInterceptorProvider provides an interceptor that forbids updating an instance name that breaks uniqueness in a given tenant
type UniqueInstanceNameUpdateInterceptorProvider struct {
TenantIdentifier string
Repository storage.TransactionalRepository
}
func (c *UniqueInstanceNameUpdateInterceptorProvider) Name() string {
return UniqueInstanceNameUpdateInterceptorName
}
func (c *UniqueInstanceNameUpdateInterceptorProvider) Provide() storage.UpdateAroundTxInterceptor {
return &uniqueInstanceNameInterceptor{
TenantIdentifier: c.TenantIdentifier,
Repository: c.Repository,
}
}
type uniqueInstanceNameInterceptor struct {
TenantIdentifier string
Repository storage.TransactionalRepository
}
func (c *uniqueInstanceNameInterceptor) AroundTxCreate(h storage.InterceptCreateAroundTxFunc) storage.InterceptCreateAroundTxFunc {
return func(ctx context.Context, obj types.Object) (object types.Object, err error) {
if err := c.checkUniqueName(ctx, obj.(*types.ServiceInstance)); err != nil {
return nil, err
}
return h(ctx, obj)
}
}
func (c *uniqueInstanceNameInterceptor) AroundTxUpdate(h storage.InterceptUpdateAroundTxFunc) storage.InterceptUpdateAroundTxFunc {
return func(ctx context.Context, newObj types.Object, labelChanges ...*types.LabelChange) (object types.Object, err error) {
oldObj, err := c.Repository.Get(ctx, types.ServiceInstanceType, query.ByField(query.EqualsOperator, "id", newObj.GetID()))
if err != nil {
return nil, err
}
oldInstance := oldObj.(*types.ServiceInstance)
newInstance := newObj.(*types.ServiceInstance)
if newInstance.Name != oldInstance.Name {
if err := c.checkUniqueName(ctx, newInstance); err != nil {
return nil, err
}
}
return h(ctx, newObj, labelChanges...)
}
}
func (c *uniqueInstanceNameInterceptor) checkUniqueName(ctx context.Context, instance *types.ServiceInstance) error {
operation, operationFound := opcontext.Get(ctx)
if !operationFound {
log.C(ctx).Debug("operation missing from context")
}
rescheduledOperation := operationFound && operation.Reschedule
if instance.PlatformID != types.SMPlatform || rescheduledOperation {
if rescheduledOperation {
log.C(ctx).Info("skipping unique check of instance name as this is a rescheduled operation")
}
return nil
}
countCriteria := []query.Criterion{
query.ByField(query.EqualsOperator, "platform_id", types.SMPlatform),
query.ByField(query.EqualsOperator, nameProperty, instance.Name),
}
criteriaForContext := query.CriteriaForContext(ctx)
for _, criterion := range criteriaForContext {
// use labelQuery criteria from context if exist as they provide the scope for name uniqueness, e.g. tenant scope
if criterion.Type == query.LabelQuery {
countCriteria = append(countCriteria, criterion)
}
}
instanceCount, err := c.Repository.Count(ctx, types.ServiceInstanceType, countCriteria...)
if err != nil {
return fmt.Errorf("could not get count of service instances %s", err)
}
if instanceCount > 0 {
return &util.HTTPError{
ErrorType: "Conflict",
Description: "instance with same name exists for the current tenant",
StatusCode: http.StatusConflict,
}
}
return nil
}