/
selector.go
235 lines (212 loc) · 8.26 KB
/
selector.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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
/*
Copyright 2023 The Nephio 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 logicalinterconnectcontroller
import (
"context"
"fmt"
"github.com/go-logr/logr"
invv1alpha1 "github.com/nokia/k8s-ipam/apis/inv/v1alpha1"
topov1alpha1 "github.com/nokia/k8s-ipam/apis/topo/v1alpha1"
"github.com/nokia/k8s-ipam/pkg/objects/endpoint"
"sigs.k8s.io/controller-runtime/pkg/log"
)
type selector struct {
endpoint endpoint.Endpoint
links uint16
//leps []invv1alpha1.LogicalEndpoint
result *result
l logr.Logger
}
type result struct {
// selectedEndpoints keeps track of the selected endpoints
// -> labeled after the selection completes
endpoints []invv1alpha1.Endpoint
// logicalEndpoints keeps track of the selected endpoints
// logical links are per endpoint, the per node/topology
// actuation happens by the logicalendpoint controller
// -> applied after selection
logicalEndpoints []invv1alpha1.LogicalEndpointSpec
// links keeps track of the links
// -> applied after selection
linkSpecs []invv1alpha1.LinkSpec
}
func newSelector(links uint16, endpoint endpoint.Endpoint) *selector {
return &selector{
endpoint: endpoint,
links: links,
result: &result{
endpoints: make([]invv1alpha1.Endpoint, 0, links*2),
logicalEndpoints: make([]invv1alpha1.LogicalEndpointSpec, 2),
linkSpecs: make([]invv1alpha1.LinkSpec, links),
},
}
}
func (r *selector) validateIndices(linkIdx, epIdx int) error {
if linkIdx < 0 || linkIdx >= int(r.links) {
return fmt.Errorf("invalid linkIndex, got: %d, want linkidx > 0 and < %d", linkIdx, r.links)
}
if epIdx < 0 || epIdx >= 2 {
return fmt.Errorf("invalid endpointIndex, got: %d, want endpointIndex > 0 and < 2", epIdx)
}
return nil
}
func (r *selector) addEndpoint(topology string, linkIdx, epIdx int, ep invv1alpha1.Endpoint, lacp *bool, name *string) error {
if err := r.validateIndices(linkIdx, epIdx); err != nil {
return err
}
// add selected endpoint
r.result.endpoints = append(r.result.endpoints, ep)
// add linkSpec
if len(r.result.linkSpecs[linkIdx].Endpoints) == 0 {
r.result.linkSpecs[linkIdx].Endpoints = make([]invv1alpha1.LinkEndpointSpec, 2)
}
r.result.linkSpecs[linkIdx].Endpoints[epIdx] = invv1alpha1.LinkEndpointSpec{
Topology: topology,
EndpointSpec: invv1alpha1.EndpointSpec{
InterfaceName: ep.Spec.InterfaceName,
NodeName: ep.Spec.NodeName,
},
}
// add logical endpoint
r.result.logicalEndpoints[epIdx].Lacp = lacp
r.result.logicalEndpoints[epIdx].Name = name
if len(r.result.logicalEndpoints[epIdx].Endpoints) == 0 {
r.result.logicalEndpoints[epIdx].Endpoints = []invv1alpha1.EndpointSpec{}
}
r.result.logicalEndpoints[epIdx].Endpoints = append(r.result.logicalEndpoints[epIdx].Endpoints,
invv1alpha1.EndpointSpec{
//Topology: topology,
InterfaceName: ep.Spec.InterfaceName,
NodeName: ep.Spec.NodeName,
},
)
return nil
}
func (r *selector) selectEndpoints(ctx context.Context, cr *topov1alpha1.LogicalInterconnect) (*result, error) {
r.l = log.FromContext(ctx)
// initialize the endpoint inventory based on the cr and topology information
if err := r.endpoint.Init(ctx, cr.GetTopologies()); err != nil {
return nil, err
}
// selection is run per link endpoint to ensure we take into account topology and node diversity
for epIdx, ep := range cr.Spec.Endpoints {
// for node diversity we keep track of the nodes per topology
// that have been selected - key = topology
selectedNodes := map[string][]string{}
// retrieve endpoints per topology - key = topology
topoEndpoints := map[string][]invv1alpha1.Endpoint{}
for _, topology := range ep.Topologies {
var err error
topoEndpoints[topology], err = r.endpoint.GetTopologyEndpointsWithSelector(topology, ep.Selector)
if err != nil {
return nil, err
}
selectedNodes[topology] = []string{"", ""}
}
// allocate endpoint per link
for linkIdx := 0; linkIdx < int(cr.Spec.Links); linkIdx++ {
// topoIdx is used to find the topology we should use for the
// endpoint selection
// Current strategy we walk one by one through each topology
topoIdx := linkIdx % len(ep.Topologies)
topology := ep.Topologies[topoIdx]
// selectedNodeIdx is used for node selection when node diversity is used
// we assume max 2 nodes for node diversity
selectedNodeIdx := 0
// for a multi topology environment node selection is not applicable
// (we ignore it)
if len(ep.Topologies) == 1 {
selectedNodeIdx = linkIdx % 2
}
// select an endpoint within a topology
found := false
for idx, tep := range topoEndpoints[topology] {
//r.l.Info("selector topoEndpoints", "gvk", fmt.Sprintf("%s.%s.%s", tep.APIVersion, tep.Kind, tep.Name))
if tep.WasAllocated(cr) {
found = true
if err := r.addEndpoint(topology, linkIdx, epIdx, tep, cr.Spec.Lacp, ep.Name); err != nil {
return nil, err
}
// delete the endpoint from the list to avoid reselection
topoEndpoints[topology] = append(topoEndpoints[topology][:idx], topoEndpoints[topology][idx+1:]...)
break
}
// if the selectedNode was already selected, node diversity was already
// checked so we need to allocate an endpoint on the
selectedNode := selectedNodes[topology][selectedNodeIdx]
if selectedNode != "" {
// node selection was already done, we need to select an endpoint
// on the same node that was not already allocated
if tep.Spec.NodeName == selectedNode && !tep.IsAllocated(cr) {
// the nodeName previously selected node matches
// -> we need to continue searching for an endpoint
// that matches the previous selected node
found = true
if err := r.addEndpoint(topology, linkIdx, epIdx, tep, cr.Spec.Lacp, ep.Name); err != nil {
return nil, err
}
// delete the endpoint from the list to avoid reselection
topoEndpoints[topology] = append(topoEndpoints[topology][:idx], topoEndpoints[topology][idx+1:]...)
break
}
} else {
// node selection is required
if len(ep.Topologies) == 1 && ep.SelectorPolicy != nil && ep.SelectorPolicy.NodeDiversity != nil && *ep.SelectorPolicy.NodeDiversity > 1 {
// we need to select a node - ensure the node we select is
// node diverse from the previous selected nodes and not allocated
if tep.IsNodeDiverse(selectedNodeIdx, selectedNodes[topology]) && !tep.IsAllocated(cr) {
// the ep is node diverse and not allocated -> we allocated this endpoint
found = true
selectedNodes[topology][selectedNodeIdx] = tep.Spec.NodeName
if err := r.addEndpoint(topology, linkIdx, epIdx, tep, cr.Spec.Lacp, ep.Name); err != nil {
return nil, err
}
// delete the endpoint from the list to avoid reselection
topoEndpoints[topology] = append(topoEndpoints[topology][:idx], topoEndpoints[topology][idx+1:]...)
break
}
} else {
if !tep.IsAllocated(cr) {
found = true
selectedNodes[topology][selectedNodeIdx] = tep.Spec.NodeName
if err := r.addEndpoint(topology, linkIdx, epIdx, tep, cr.Spec.Lacp, ep.Name); err != nil {
return nil, err
}
// delete the endpoint from the list to avoid reselection
topoEndpoints[topology] = append(topoEndpoints[topology][:idx], topoEndpoints[topology][idx+1:]...)
break
}
}
}
}
if !found {
return nil, fmt.Errorf("no endpoints available")
}
}
}
return r.result, nil
}
func (r *result) getLinkSpecs() []invv1alpha1.LinkSpec {
return r.linkSpecs
}
func (r *result) getSelectedEndpoints() []invv1alpha1.Endpoint {
return r.endpoints
}
func (r *result) getLogicalEndpoints() []invv1alpha1.LogicalEndpointSpec {
return r.logicalEndpoints
}
// option 1:
// give the endpoints back -> issue is reallocation
// option 2:
// list the allocated endpoints based on links or endpoints
// check if the selection