Skip to content

Commit e162a5f

Browse files
committed
Support advanced load balancer member settings
Add labels, admin state, backup, and monitor override fields to the load balancer member create/edit flow.
1 parent cd71693 commit e162a5f

5 files changed

Lines changed: 414 additions & 51 deletions

File tree

src/internal/app/actions_resource.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,19 @@ func (m Model) openLBMemberEdit() (Model, tea.Cmd) {
517517
if mem == nil || poolID == "" {
518518
return m, nil
519519
}
520-
m.lbMemberCreate = lbmembercreate.NewEdit(m.client.LoadBalancer, poolID, mem.ID, mem.Name, mem.Weight, poolName)
520+
m.lbMemberCreate = lbmembercreate.NewEdit(
521+
m.client.LoadBalancer,
522+
poolID,
523+
mem.ID,
524+
mem.Name,
525+
mem.Weight,
526+
mem.AdminStateUp,
527+
mem.Backup,
528+
mem.MonitorAddress,
529+
mem.MonitorPort,
530+
mem.Tags,
531+
poolName,
532+
)
521533
m.lbMemberCreate.SetSize(m.width, m.height)
522534
return m, m.lbMemberCreate.Init()
523535
}

src/internal/loadbalancer/lb.go

Lines changed: 115 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,38 @@ type Member struct {
5151
Address string
5252
ProtocolPort int
5353
Weight int
54+
AdminStateUp bool
5455
OperatingStatus string
56+
Backup bool
57+
MonitorAddress string
58+
MonitorPort int
59+
Tags []string
60+
}
61+
62+
// MemberCreateOpts contains editable fields for creating a pool member.
63+
type MemberCreateOpts struct {
64+
Name string
65+
Address string
66+
ProtocolPort int
67+
Weight int
68+
AdminStateUp bool
69+
Backup bool
70+
MonitorAddress string
71+
MonitorPort *int
72+
Tags []string
73+
}
74+
75+
// MemberUpdateOpts contains editable fields for updating a pool member.
76+
type MemberUpdateOpts struct {
77+
Name *string
78+
Weight *int
79+
AdminStateUp *bool
80+
Backup *bool
81+
MonitorAddress *string
82+
MonitorAddressSet bool
83+
MonitorPort *int
84+
MonitorPortSet bool
85+
Tags *[]string
5586
}
5687

5788
// HealthMonitor is a simplified health monitor.
@@ -347,25 +378,24 @@ func UpdatePool(ctx context.Context, client *gophercloud.ServiceClient, id strin
347378
}
348379

349380
// CreateMember adds a member to a pool.
350-
func CreateMember(ctx context.Context, client *gophercloud.ServiceClient, poolID, name, address string, port, weight int) (*Member, error) {
351-
opts := pools.CreateMemberOpts{
352-
Name: name,
353-
Address: address,
354-
ProtocolPort: port,
355-
Weight: &weight,
356-
}
357-
m, err := pools.CreateMember(ctx, client, poolID, opts).Extract()
381+
func CreateMember(ctx context.Context, client *gophercloud.ServiceClient, poolID string, opts MemberCreateOpts) (*Member, error) {
382+
createOpts := pools.CreateMemberOpts{
383+
Name: opts.Name,
384+
Address: opts.Address,
385+
ProtocolPort: opts.ProtocolPort,
386+
Weight: &opts.Weight,
387+
AdminStateUp: &opts.AdminStateUp,
388+
Backup: &opts.Backup,
389+
MonitorAddress: opts.MonitorAddress,
390+
MonitorPort: opts.MonitorPort,
391+
Tags: cloneStringSlice(opts.Tags),
392+
}
393+
m, err := pools.CreateMember(ctx, client, poolID, createOpts).Extract()
358394
if err != nil {
359395
return nil, fmt.Errorf("creating member: %w", err)
360396
}
361-
return &Member{
362-
ID: m.ID,
363-
Name: m.Name,
364-
Address: m.Address,
365-
ProtocolPort: m.ProtocolPort,
366-
Weight: m.Weight,
367-
OperatingStatus: m.OperatingStatus,
368-
}, nil
397+
member := simplifyMember(m)
398+
return &member, nil
369399
}
370400

371401
// DeleteMember removes a member from a pool.
@@ -377,13 +407,9 @@ func DeleteMember(ctx context.Context, client *gophercloud.ServiceClient, poolID
377407
return nil
378408
}
379409

380-
// UpdateMember updates a member's name and/or weight.
381-
func UpdateMember(ctx context.Context, client *gophercloud.ServiceClient, poolID, memberID string, name *string, weight *int) error {
382-
opts := pools.UpdateMemberOpts{
383-
Name: name,
384-
Weight: weight,
385-
}
386-
_, err := pools.UpdateMember(ctx, client, poolID, memberID, opts).Extract()
410+
// UpdateMember updates an existing member.
411+
func UpdateMember(ctx context.Context, client *gophercloud.ServiceClient, poolID, memberID string, opts MemberUpdateOpts) error {
412+
_, err := pools.UpdateMember(ctx, client, poolID, memberID, memberUpdateRequest(opts)).Extract()
387413
if err != nil {
388414
return fmt.Errorf("updating member %s: %w", memberID, err)
389415
}
@@ -399,14 +425,7 @@ func ListMembers(ctx context.Context, client *gophercloud.ServiceClient, poolID
399425
return false, err
400426
}
401427
for _, m := range extracted {
402-
result = append(result, Member{
403-
ID: m.ID,
404-
Name: m.Name,
405-
Address: m.Address,
406-
ProtocolPort: m.ProtocolPort,
407-
Weight: m.Weight,
408-
OperatingStatus: m.OperatingStatus,
409-
})
428+
result = append(result, simplifyMember(&m))
410429
}
411430
return true, nil
412431
})
@@ -415,3 +434,68 @@ func ListMembers(ctx context.Context, client *gophercloud.ServiceClient, poolID
415434
}
416435
return result, nil
417436
}
437+
438+
type memberUpdateRequest MemberUpdateOpts
439+
440+
func (opts memberUpdateRequest) ToMemberUpdateMap() (map[string]any, error) {
441+
body := map[string]any{}
442+
if opts.Name != nil {
443+
body["name"] = *opts.Name
444+
}
445+
if opts.Weight != nil {
446+
body["weight"] = *opts.Weight
447+
}
448+
if opts.AdminStateUp != nil {
449+
body["admin_state_up"] = *opts.AdminStateUp
450+
}
451+
if opts.Backup != nil {
452+
body["backup"] = *opts.Backup
453+
}
454+
if opts.MonitorAddressSet {
455+
if opts.MonitorAddress == nil {
456+
body["monitor_address"] = nil
457+
} else {
458+
body["monitor_address"] = *opts.MonitorAddress
459+
}
460+
}
461+
if opts.MonitorPortSet {
462+
if opts.MonitorPort == nil {
463+
body["monitor_port"] = nil
464+
} else {
465+
body["monitor_port"] = *opts.MonitorPort
466+
}
467+
}
468+
if opts.Tags != nil {
469+
if len(*opts.Tags) == 0 {
470+
body["tags"] = []string{}
471+
} else {
472+
body["tags"] = cloneStringSlice(*opts.Tags)
473+
}
474+
}
475+
return map[string]any{"member": body}, nil
476+
}
477+
478+
func simplifyMember(m *pools.Member) Member {
479+
return Member{
480+
ID: m.ID,
481+
Name: m.Name,
482+
Address: m.Address,
483+
ProtocolPort: m.ProtocolPort,
484+
Weight: m.Weight,
485+
AdminStateUp: m.AdminStateUp,
486+
OperatingStatus: m.OperatingStatus,
487+
Backup: m.Backup,
488+
MonitorAddress: m.MonitorAddress,
489+
MonitorPort: m.MonitorPort,
490+
Tags: cloneStringSlice(m.Tags),
491+
}
492+
}
493+
494+
func cloneStringSlice(values []string) []string {
495+
if len(values) == 0 {
496+
return nil
497+
}
498+
out := make([]string, len(values))
499+
copy(out, values)
500+
return out
501+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package loadbalancer
2+
3+
import "testing"
4+
5+
func TestMemberUpdateRequestIncludesClearsAndEmptyTags(t *testing.T) {
6+
name := "web-01"
7+
weight := 2
8+
adminStateUp := false
9+
backup := true
10+
tags := []string{}
11+
12+
body, err := memberUpdateRequest{
13+
Name: &name,
14+
Weight: &weight,
15+
AdminStateUp: &adminStateUp,
16+
Backup: &backup,
17+
MonitorAddressSet: true,
18+
MonitorPortSet: true,
19+
Tags: &tags,
20+
}.ToMemberUpdateMap()
21+
if err != nil {
22+
t.Fatalf("ToMemberUpdateMap error = %v", err)
23+
}
24+
25+
memberBody, ok := body["member"].(map[string]any)
26+
if !ok {
27+
t.Fatalf("member body type = %T, want map[string]any", body["member"])
28+
}
29+
if memberBody["name"] != "web-01" {
30+
t.Fatalf("name = %#v, want web-01", memberBody["name"])
31+
}
32+
if memberBody["weight"] != 2 {
33+
t.Fatalf("weight = %#v, want 2", memberBody["weight"])
34+
}
35+
if memberBody["admin_state_up"] != false {
36+
t.Fatalf("admin_state_up = %#v, want false", memberBody["admin_state_up"])
37+
}
38+
if memberBody["backup"] != true {
39+
t.Fatalf("backup = %#v, want true", memberBody["backup"])
40+
}
41+
if _, ok := memberBody["monitor_address"]; !ok {
42+
t.Fatal("expected monitor_address key")
43+
}
44+
if memberBody["monitor_address"] != nil {
45+
t.Fatalf("monitor_address = %#v, want nil", memberBody["monitor_address"])
46+
}
47+
if _, ok := memberBody["monitor_port"]; !ok {
48+
t.Fatal("expected monitor_port key")
49+
}
50+
if memberBody["monitor_port"] != nil {
51+
t.Fatalf("monitor_port = %#v, want nil", memberBody["monitor_port"])
52+
}
53+
tagValues, ok := memberBody["tags"].([]string)
54+
if !ok {
55+
t.Fatalf("tags type = %T, want []string", memberBody["tags"])
56+
}
57+
if len(tagValues) != 0 {
58+
t.Fatalf("len(tags) = %d, want 0", len(tagValues))
59+
}
60+
}

0 commit comments

Comments
 (0)