Skip to content

Commit 0360da3

Browse files
Merge pull request #8337 from mjturek/fix-complexity
no-jira: Power VS: Refactor `InfraReady` to use metadata client
2 parents a6d9a1a + 4b5500b commit 0360da3

File tree

2 files changed

+151
-143
lines changed

2 files changed

+151
-143
lines changed

pkg/asset/installconfig/powervs/metadata.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@ package powervs
33
import (
44
"context"
55
"fmt"
6+
"math"
67
"sync"
8+
"time"
79

810
"github.com/IBM-Cloud/bluemix-go/crn"
911
"github.com/IBM/vpc-go-sdk/vpcv1"
12+
"k8s.io/apimachinery/pkg/util/wait"
1013

1114
"github.com/openshift/installer/pkg/types"
1215
)
@@ -282,3 +285,82 @@ func (m *Metadata) GetDNSServerIP(ctx context.Context, vpcName string) (string,
282285
}
283286
return dnsServerIP, nil
284287
}
288+
289+
// CreateDNSRecord creates a CNAME record for the specified hostname and destination hostname.
290+
func (m *Metadata) CreateDNSRecord(ctx context.Context, hostname string, destHostname string) error {
291+
instanceCRN, err := m.client.GetInstanceCRNByName(ctx, m.BaseDomain, m.PublishStrategy)
292+
if err != nil {
293+
return fmt.Errorf("failed to get InstanceCRN (%s) by name: %w", m.PublishStrategy, err)
294+
}
295+
296+
backoff := wait.Backoff{
297+
Duration: 15 * time.Second,
298+
Factor: 1.1,
299+
Cap: leftInContext(ctx),
300+
Steps: math.MaxInt32}
301+
302+
var lastErr error
303+
err = wait.ExponentialBackoffWithContext(ctx, backoff, func(context.Context) (bool, error) {
304+
lastErr = m.client.CreateDNSRecord(ctx, m.PublishStrategy, instanceCRN, m.BaseDomain, hostname, destHostname)
305+
if lastErr == nil {
306+
return true, nil
307+
}
308+
return false, nil
309+
})
310+
311+
if err != nil {
312+
if lastErr != nil {
313+
err = lastErr
314+
}
315+
return fmt.Errorf("failed to create a DNS CNAME record (%s, %s): %w",
316+
hostname,
317+
destHostname,
318+
err)
319+
}
320+
return err
321+
}
322+
323+
// ListSecurityGroupRules lists the rules created in the specified VPC.
324+
func (m *Metadata) ListSecurityGroupRules(ctx context.Context, vpcID string) (*vpcv1.SecurityGroupRuleCollection, error) {
325+
return m.client.ListSecurityGroupRules(ctx, vpcID)
326+
}
327+
328+
// SetVPCServiceURLForRegion sets the URL for the VPC based on the specified region.
329+
func (m *Metadata) SetVPCServiceURLForRegion(ctx context.Context, vpcRegion string) error {
330+
return m.client.SetVPCServiceURLForRegion(ctx, vpcRegion)
331+
}
332+
333+
// AddSecurityGroupRule adds a security group rule to the specified VPC.
334+
func (m *Metadata) AddSecurityGroupRule(ctx context.Context, rule *vpcv1.SecurityGroupRulePrototype, vpcID string) error {
335+
backoff := wait.Backoff{
336+
Duration: 15 * time.Second,
337+
Factor: 1.1,
338+
Cap: leftInContext(ctx),
339+
Steps: math.MaxInt32}
340+
341+
var lastErr error
342+
err := wait.ExponentialBackoffWithContext(ctx, backoff, func(context.Context) (bool, error) {
343+
lastErr = m.client.AddSecurityGroupRule(ctx, vpcID, rule)
344+
if lastErr == nil {
345+
return true, nil
346+
}
347+
return false, nil
348+
})
349+
350+
if err != nil {
351+
if lastErr != nil {
352+
err = lastErr
353+
}
354+
return fmt.Errorf("failed to add security group rule: %w", err)
355+
}
356+
return err
357+
}
358+
359+
func leftInContext(ctx context.Context) time.Duration {
360+
deadline, ok := ctx.Deadline()
361+
if !ok {
362+
return math.MaxInt64
363+
}
364+
365+
return time.Until(deadline)
366+
}

pkg/infrastructure/powervs/clusterapi/powervs.go

Lines changed: 69 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"math"
77
"reflect"
88
"regexp"
9-
"strings"
109
"time"
1110

1211
"github.com/IBM/vpc-go-sdk/vpcv1"
@@ -56,17 +55,10 @@ const publicPrefix = "api."
5655
// InfraReady is called once cluster.Status.InfrastructureReady
5756
// is true, typically after load balancers have been provisioned. It can be used
5857
// to create DNS records.
59-
// nolint:gocyclo
6058
func (p Provider) InfraReady(ctx context.Context, in clusterapi.InfraReadyInput) error {
6159
var (
62-
client *powervsconfig.Client
63-
vpcRegion string
64-
instanceCRN string
65-
rules *vpcv1.SecurityGroupRuleCollection
66-
rule *vpcv1.SecurityGroupRulePrototype
67-
wantedPorts = sets.New[int64](22, 10258, 22623)
68-
foundPorts = sets.Set[int64]{}
69-
err error
60+
err error
61+
rule *vpcv1.SecurityGroupRulePrototype
7062
)
7163

7264
logrus.Debugf("InfraReady: in = %+v", in)
@@ -103,129 +95,115 @@ func (p Provider) InfraReady(ctx context.Context, in clusterapi.InfraReadyInput)
10395
}
10496
logrus.Debugf("InfraReady: image = %+v", powerVSImage)
10597

106-
// SAD: client in the Metadata struct is lowercase and therefore private
107-
// client = in.InstallConfig.PowerVS.client
108-
client, err = powervsconfig.NewClient()
109-
if err != nil {
110-
return fmt.Errorf("failed to get NewClient in InfraReady: %w", err)
111-
}
112-
logrus.Debugf("InfraReady: NewClient returns %+v", client)
113-
11498
// We need to set the region we will eventually query inside
115-
vpcRegion = in.InstallConfig.Config.Platform.PowerVS.VPCRegion
99+
vpcRegion := in.InstallConfig.Config.Platform.PowerVS.VPCRegion
116100
if vpcRegion == "" {
117101
vpcRegion, err = powervstypes.VPCRegionForPowerVSRegion(in.InstallConfig.Config.Platform.PowerVS.Region)
118102
if err != nil {
119103
return fmt.Errorf("failed to get VPC region (%s) in InfraReady: %w", vpcRegion, err)
120104
}
121105
}
122106
logrus.Debugf("InfraReady: vpcRegion = %s", vpcRegion)
123-
if err = client.SetVPCServiceURLForRegion(ctx, vpcRegion); err != nil {
107+
if err = in.InstallConfig.PowerVS.SetVPCServiceURLForRegion(ctx, vpcRegion); err != nil {
124108
return fmt.Errorf("failed to set the VPC service region (%s) in InfraReady: %w", vpcRegion, err)
125109
}
126110

127-
// Step 1.
128-
// Create DNS records for the two load balancers
129-
// map[string]VPCLoadBalancerStatus
130-
instanceCRN, err = client.GetInstanceCRNByName(ctx,
131-
in.InstallConfig.PowerVS.BaseDomain,
132-
in.InstallConfig.Config.Publish)
111+
// Step 1: Create DNS records for the two load balancers
112+
if err = createLoadBalancerDNSRecords(ctx, in, powerVSCluster.Status.LoadBalancers); err != nil {
113+
return fmt.Errorf("failed to create DNS records for loadbalancers: %w", err)
114+
}
115+
116+
// Step 2: See which ports are already allowed.
117+
missingPorts, err := findMissingSecurityGroupRules(ctx, in, *powerVSCluster.Status.VPC.ID)
133118
if err != nil {
134-
return fmt.Errorf("failed to get InstanceCRN (%s) by name in InfraReady: %w",
135-
in.InstallConfig.Config.Publish,
136-
err)
119+
return fmt.Errorf("failed to find missing security group rules: %w", err)
137120
}
138-
logrus.Debugf("InfraReady: instanceCRN = %s", instanceCRN)
139121

122+
// Step 3: Add to security group rules
123+
for port := range missingPorts {
124+
port := port // TODO: remove when using golang 1.22+
125+
rule = &vpcv1.SecurityGroupRulePrototype{
126+
Direction: ptr.To("inbound"),
127+
Protocol: ptr.To("tcp"),
128+
PortMin: ptr.To(port),
129+
PortMax: ptr.To(port),
130+
}
131+
132+
logrus.Debugf("InfraReady: Adding port %d to security group rule to %v", port, *powerVSCluster.Status.VPC.ID)
133+
err := in.InstallConfig.PowerVS.AddSecurityGroupRule(ctx, rule, *powerVSCluster.Status.VPC.ID)
134+
if err != nil {
135+
return fmt.Errorf("failed to add security group rule for port %d: %w", port, err)
136+
}
137+
}
138+
139+
// Also allow ping so we can debug
140+
rule = &vpcv1.SecurityGroupRulePrototype{
141+
Direction: ptr.To("inbound"),
142+
Protocol: ptr.To("icmp"),
143+
}
144+
145+
err = in.InstallConfig.PowerVS.AddSecurityGroupRule(ctx, rule, *powerVSCluster.Status.VPC.ID)
146+
if err != nil {
147+
return fmt.Errorf("failed to add ping security group rule: %w", err)
148+
}
149+
return nil
150+
}
151+
152+
func createLoadBalancerDNSRecords(ctx context.Context, in clusterapi.InfraReadyInput, loadBalancers map[string]capibm.VPCLoadBalancerStatus) error {
140153
lbExtExp := regexp.MustCompile(`\b-loadbalancer\b$`)
141154
lbIntExp := regexp.MustCompile(`\b-loadbalancer-int\b$`)
155+
for lbKey, loadBalancerStatus := range loadBalancers {
156+
var hostnames []string
142157

143-
for lbKey, loadBalancerStatus := range powerVSCluster.Status.LoadBalancers {
144-
var (
145-
idx int
146-
substr string
147-
infraID string
148-
hostnames []string
149-
prefix string
150-
)
151-
152-
// The infra id is "rdr-hamzy-test-dal10-846vd" and we need "rdr-hamzy-test-dal10"
153-
logrus.Debugf("in.InfraID = %s", in.InfraID)
154-
idx = strings.LastIndex(in.InfraID, "-")
155-
logrus.Debugf("idx = %d", idx)
156-
substr = in.InfraID[idx:]
157-
logrus.Debugf("substr = %s", substr)
158-
infraID = strings.ReplaceAll(in.InfraID, substr, "")
159-
logrus.Debugf("infraID = %s", infraID)
158+
clusterName := in.InstallConfig.Config.ObjectMeta.Name
160159

161160
// Is it external (public) or internal (private)?
162161
logrus.Debugf("lbKey = %s", lbKey)
163162
switch {
164163
case lbExtExp.MatchString(lbKey):
165164
if in.InstallConfig.Config.Publish == types.ExternalPublishingStrategy {
166-
hostnames = append(hostnames, fmt.Sprintf("%s%s", publicPrefix, infraID))
165+
hostnames = append(hostnames, fmt.Sprintf("%s%s", publicPrefix, clusterName))
167166
}
168167
case lbIntExp.MatchString(lbKey):
169-
hostnames = append(hostnames, fmt.Sprintf("%s%s", privatePrefix, infraID))
168+
hostnames = append(hostnames, fmt.Sprintf("%s%s", privatePrefix, clusterName))
170169
// In the private cluster scenario, also point api.* to internal LB
171170
if in.InstallConfig.Config.Publish == types.InternalPublishingStrategy {
172-
hostnames = append(hostnames, fmt.Sprintf("%s%s", publicPrefix, infraID))
171+
hostnames = append(hostnames, fmt.Sprintf("%s%s", publicPrefix, clusterName))
173172
}
174173
}
175-
logrus.Debugf("prefix = %s", prefix)
176174

177175
for _, hostname := range hostnames {
178-
logrus.Debugf("InfraReady: crn = %s, base domain = %s, hostname = %s, cname = %s",
179-
instanceCRN,
180-
in.InstallConfig.PowerVS.BaseDomain,
176+
logrus.Debugf("InfraReady: hostname = %s, cname = %s",
181177
hostname,
182178
*loadBalancerStatus.Hostname)
183179

184-
backoff := wait.Backoff{
185-
Duration: 15 * time.Second,
186-
Factor: 1.1,
187-
Cap: leftInContext(ctx),
188-
Steps: math.MaxInt32}
189-
var lastErr error
190-
err = wait.ExponentialBackoffWithContext(ctx, backoff, func(context.Context) (bool, error) {
191-
lastErr = client.CreateDNSRecord(ctx,
192-
in.InstallConfig.Config.Publish,
193-
instanceCRN,
194-
in.InstallConfig.PowerVS.BaseDomain,
195-
hostname,
196-
*loadBalancerStatus.Hostname)
197-
if lastErr == nil {
198-
return true, nil
199-
}
200-
return false, nil
201-
})
202-
180+
err := in.InstallConfig.PowerVS.CreateDNSRecord(ctx,
181+
hostname,
182+
*loadBalancerStatus.Hostname)
203183
if err != nil {
204-
if lastErr != nil {
205-
err = lastErr
206-
}
207-
return fmt.Errorf("failed to create a DNS CNAME record (%s, %s): %w",
208-
hostname,
209-
*loadBalancerStatus.Hostname,
210-
err)
184+
return fmt.Errorf("InfraReady: Failed to create DNS record: %w", err)
211185
}
212186
}
213187
}
188+
return nil
189+
}
214190

215-
// Step 2.
216-
// See which ports are already allowed.
217-
rules, err = client.ListSecurityGroupRules(ctx, *powerVSCluster.Status.VPC.ID)
191+
func findMissingSecurityGroupRules(ctx context.Context, in clusterapi.InfraReadyInput, vpcID string) (sets.Set[int64], error) {
192+
foundPorts := sets.Set[int64]{}
193+
wantedPorts := sets.New[int64](22, 10258, 22623)
194+
195+
existingRules, err := in.InstallConfig.PowerVS.ListSecurityGroupRules(ctx, vpcID)
218196
if err != nil {
219-
return fmt.Errorf("failed to list security group rules: %w", err)
197+
return nil, fmt.Errorf("failed to list security group rules: %w", err)
220198
}
221199

222-
for _, existingRule := range rules.Rules {
200+
for _, existingRule := range existingRules.Rules {
223201
switch reflect.TypeOf(existingRule).String() {
224202
case "*vpcv1.SecurityGroupRuleSecurityGroupRuleProtocolAll":
225203
case "*vpcv1.SecurityGroupRuleSecurityGroupRuleProtocolTcpudp":
226204
securityGroupRule, ok := existingRule.(*vpcv1.SecurityGroupRuleSecurityGroupRuleProtocolTcpudp)
227205
if !ok {
228-
return fmt.Errorf("could not convert to ProtocolTcpudp")
206+
return nil, fmt.Errorf("could not convert to ProtocolTcpudp")
229207
}
230208
logrus.Debugf("InfraReady: VPC has rule: direction = %s, proto = %s, min = %d, max = %d",
231209
*securityGroupRule.Direction,
@@ -239,66 +217,14 @@ func (p Provider) InfraReady(ctx context.Context, in clusterapi.InfraReadyInput)
239217
case "*vpcv1.SecurityGroupRuleSecurityGroupRuleProtocolIcmp":
240218
}
241219
}
242-
logrus.Debugf("InfraReady: foundPorts = %+v", foundPorts)
243-
logrus.Debugf("InfraReady: wantedPorts = %+v", wantedPorts)
244-
logrus.Debugf("InfraReady: wantedPorts.Difference(foundPorts) = %+v", wantedPorts.Difference(foundPorts))
245220

246-
// Step 3.
247-
// Add to security group rules
248-
for port := range wantedPorts.Difference(foundPorts) {
249-
rule = &vpcv1.SecurityGroupRulePrototype{
250-
Direction: ptr.To("inbound"),
251-
Protocol: ptr.To("tcp"),
252-
PortMin: ptr.To(port),
253-
PortMax: ptr.To(port),
254-
}
255-
256-
backoff := wait.Backoff{
257-
Duration: 15 * time.Second,
258-
Factor: 1.1,
259-
Cap: leftInContext(ctx),
260-
Steps: math.MaxInt32}
261-
err = wait.ExponentialBackoffWithContext(ctx, backoff, func(context.Context) (bool, error) {
262-
logrus.Debugf("InfraReady: Adding port %d to security group rule to %v",
263-
port,
264-
*powerVSCluster.Status.VPC.ID)
265-
err2 := client.AddSecurityGroupRule(ctx, *powerVSCluster.Status.VPC.ID, rule)
266-
if err2 == nil {
267-
return true, nil
268-
}
269-
return false, err2
270-
})
271-
if err != nil {
272-
return fmt.Errorf("failed to add security group rule for port %d: %w", port, err)
273-
}
274-
}
221+
missingPorts := wantedPorts.Difference(foundPorts)
275222

276-
// Allow ping so we can debug
277-
rule = &vpcv1.SecurityGroupRulePrototype{
278-
Direction: ptr.To("inbound"),
279-
Protocol: ptr.To("icmp"),
280-
}
281-
backoff := wait.Backoff{
282-
Duration: 15 * time.Second,
283-
Factor: 1.1,
284-
Cap: leftInContext(ctx),
285-
Steps: math.MaxInt32}
286-
var lastErr error
287-
err = wait.ExponentialBackoffWithContext(ctx, backoff, func(context.Context) (bool, error) {
288-
lastErr = client.AddSecurityGroupRule(ctx, *powerVSCluster.Status.VPC.ID, rule)
289-
if lastErr == nil {
290-
return true, nil
291-
}
292-
return false, nil
293-
})
294-
if err != nil {
295-
if lastErr != nil {
296-
err = lastErr
297-
}
298-
return fmt.Errorf("failed to add security group rule for icmp: %w", err)
299-
}
223+
logrus.Debugf("InfraReady: foundPorts = %+v", foundPorts)
224+
logrus.Debugf("InfraReady: wantedPorts = %+v", wantedPorts)
225+
logrus.Debugf("InfraReady: wantedPorts.Difference(foundPorts) = %+v", missingPorts)
300226

301-
return nil
227+
return missingPorts, nil
302228
}
303229

304230
func findMachineAddress(ctx context.Context, in clusterapi.PostProvisionInput, key crclient.ObjectKey) (string, error) {

0 commit comments

Comments
 (0)