@@ -11,13 +11,15 @@ import (
11
11
"sigs.k8s.io/controller-runtime/pkg/client"
12
12
)
13
13
14
+ // LBManager Responsible for the creation and deletion of externally accessible Services to access the Postgresql clusters managed by the Postgreslet.
14
15
type LBManager struct {
15
- client.Client // todo: service cluster
16
- LBIP string // todo: via configmap
17
- PortRangeStart int32 // todo: via configmap
16
+ client.Client
17
+ LBIP string
18
+ PortRangeStart int32
18
19
PortRangeSize int32
19
20
}
20
21
22
+ // New Creates a new LBManager with the given configuration
21
23
func New (client client.Client , lbIP string , portRangeStart , portRangeSize int32 ) * LBManager {
22
24
return & LBManager {
23
25
Client : client ,
@@ -27,6 +29,7 @@ func New(client client.Client, lbIP string, portRangeStart, portRangeSize int32)
27
29
}
28
30
}
29
31
32
+ // CreateSvcLBIfNone Creates a new Service of type LoadBalancer for the given Postgres resource if neccessary
30
33
func (m * LBManager ) CreateSvcLBIfNone (ctx context.Context , in * api.Postgres ) error {
31
34
if err := m .Get (ctx , client.ObjectKey {
32
35
Namespace : in .ToPeripheralResourceNamespace (),
@@ -36,30 +39,20 @@ func (m *LBManager) CreateSvcLBIfNone(ctx context.Context, in *api.Postgres) err
36
39
return fmt .Errorf ("failed to fetch Service of type LoadBalancer: %w" , err )
37
40
}
38
41
39
- existingLBIP , nextFreePort , err := m .nextFreeSocket (ctx )
42
+ nextFreePort , err := m .nextFreePort (ctx )
40
43
if err != nil {
41
- return fmt .Errorf ("failed to get a free port for creating Service of type LoadBalancer: %w" , err )
42
- }
43
- var lbIPToUse string
44
- if m .LBIP != "" {
45
- // a specific IP was configured in the config, so use that one
46
- lbIPToUse = m .LBIP
47
- } else if existingLBIP != "" {
48
- // no ip was configured, but one is already in use, so use the existing one
49
- lbIPToUse = existingLBIP
50
- } else {
51
- // nothing was configured, nothing exists yet, so use an empty address so a new loadbalancer will be created and assigned
52
- lbIPToUse = ""
44
+ return fmt .Errorf ("failed to get the next free port: %w" , err )
53
45
}
54
46
55
- if err := m .Create (ctx , in .ToSvcLB (lbIPToUse , nextFreePort )); err != nil {
47
+ if err := m .Create (ctx , in .ToSvcLB (m . LBIP , nextFreePort )); err != nil {
56
48
return fmt .Errorf ("failed to create Service of type LoadBalancer: %w" , err )
57
49
}
58
50
return nil
59
51
}
60
52
return nil
61
53
}
62
54
55
+ // DeleteSvcLB Deletes the corresponding Service of type LoadBalancer of the given Postgres resource.
63
56
func (m * LBManager ) DeleteSvcLB (ctx context.Context , in * api.Postgres ) error {
64
57
lb := & corev1.Service {}
65
58
lb .Namespace = in .ToPeripheralResourceNamespace ()
@@ -70,37 +63,51 @@ func (m *LBManager) DeleteSvcLB(ctx context.Context, in *api.Postgres) error {
70
63
return nil
71
64
}
72
65
73
- func (m * LBManager ) nextFreeSocket (ctx context.Context ) (string , int32 , error ) {
66
+ // nextFreeSocket finds any existing LoadBalancerIP and the next free port out of the configure port range.
67
+ func (m * LBManager ) nextFreePort (ctx context.Context ) (int32 , error ) {
74
68
// TODO prevent concurrency issues when calculating port / ip.
75
69
76
- existingLBIP := ""
77
-
70
+ // Fetch all services managed by this postgreslet
78
71
lbs := & corev1.ServiceList {}
79
72
if err := m .List (ctx , lbs , client .MatchingLabels (api .SvcLoadBalancerLabel )); err != nil {
80
- return existingLBIP , 0 , fmt .Errorf ("failed to fetch the list of services of type LoadBalancer: %w" , err )
73
+ return 0 , fmt .Errorf ("failed to fetch the list of services of type LoadBalancer: %w" , err )
81
74
}
82
75
76
+ // If there are none, this will be the first (managed) service we create, so start with PortRangeStart and return
83
77
if len (lbs .Items ) == 0 {
84
- return existingLBIP , m .PortRangeStart , nil
78
+ return m .PortRangeStart , nil
85
79
}
86
80
87
- // Record weather any port is occupied
88
- isOccupied := make ([] bool , int ( m . PortRangeSize ))
81
+ // If there are already any managed services, store all the used ports in a slice.
82
+ portsInUse := [] int32 {}
89
83
for i := range lbs .Items {
90
84
svc := lbs .Items [i ]
91
85
if len (svc .Spec .Ports ) > 0 {
92
- isOccupied [svc .Spec .Ports [0 ].Port - m .PortRangeStart ] = true
93
- }
94
- if svc .Spec .LoadBalancerIP != "" {
95
- existingLBIP = svc .Spec .LoadBalancerIP
86
+ portsInUse = append (portsInUse , svc .Spec .Ports [0 ].Port )
96
87
}
97
88
}
98
89
99
- for i := range isOccupied {
100
- if ! isOccupied [i ] {
101
- return existingLBIP , m .PortRangeStart + int32 (i ), nil
90
+ // Now try all ports in the configured port range to find a free one.
91
+ // While not as effective as other implementations, this allows us to freely change PortRangeStart and PortRangeSize
92
+ // retroactively without breaking the implementation.
93
+ for port := m .PortRangeStart ; port < m .PortRangeStart + m .PortRangeSize ; port ++ {
94
+ if containsElem (portsInUse , port ) {
95
+ // Port already in use, try the next one
96
+ continue
102
97
}
98
+ // The postgreslet hasn't assigned this port yet, so use it.
99
+ return port , nil
103
100
}
104
101
105
- return existingLBIP , 0 , errors .New ("no free port" )
102
+ // If we made it this far, no free port could be found.
103
+ return 0 , errors .New ("no free port in the configured port range found" )
104
+ }
105
+
106
+ func containsElem (s []int32 , v int32 ) bool {
107
+ for _ , elem := range s {
108
+ if elem == v {
109
+ return true
110
+ }
111
+ }
112
+ return false
106
113
}
0 commit comments