Skip to content

Add support for creation of NB with IPv4 Reserved Address #733

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
2 changes: 2 additions & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0Y
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
Expand All @@ -78,6 +79,7 @@ golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
Expand Down
10 changes: 6 additions & 4 deletions nodebalancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,12 @@ type NodeBalancerCreateOptions struct {
Region string `json:"region,omitempty"`
ClientConnThrottle *int `json:"client_conn_throttle,omitempty"`
Configs []*NodeBalancerConfigCreateOptions `json:"configs,omitempty"`
Tags []string `json:"tags"`
FirewallID int `json:"firewall_id,omitempty"`
Type NodeBalancerPlanType `json:"type,omitempty"`
VPCs []NodeBalancerVPCOptions `json:"vpcs,omitempty"`
Tags []string `json:"tags,omitempty"`
// NOTE: IP assignment feature may not currently be available to all users.
IPv4 *string `json:"ipv4,omitempty"`
FirewallID int `json:"firewall_id,omitempty"`
Type NodeBalancerPlanType `json:"type,omitempty"`
VPCs []NodeBalancerVPCOptions `json:"vpcs,omitempty"`
}

// NodeBalancerUpdateOptions are the options permitted for UpdateNodeBalancer
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
---
version: 1
interactions:
- request:
body: '{"region":"us-east"}'
form: {}
headers:
Accept:
- application/json
Content-Type:
- application/json
User-Agent:
- linodego/dev https://github.com/linode/linodego
url: https://api.linode.com/v4beta/networking/reserved/ips
method: POST
response:
body: '{"address": "45.79.134.221", "gateway": "45.79.134.1", "subnet_mask": "255.255.255.0",
"prefix": 24, "type": "ipv4", "public": true, "rdns": "45-79-134-221.ip.linodeusercontent.com",
"linode_id": null, "interface_id": null, "region": "us-east", "vpc_nat_1_1":
null, "reserved": true, "assigned_entity": null}'
headers:
Access-Control-Allow-Credentials:
- "true"
Access-Control-Allow-Headers:
- Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter
Access-Control-Allow-Methods:
- HEAD, GET, OPTIONS, POST, PUT, DELETE
Access-Control-Allow-Origin:
- '*'
Access-Control-Expose-Headers:
- X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status
Akamai-Internal-Account:
- '*'
Cache-Control:
- max-age=0, no-cache, no-store
Connection:
- keep-alive
Content-Length:
- "308"
Content-Security-Policy:
- default-src 'none'
Content-Type:
- application/json
Expires:
- Thu, 08 May 2025 20:48:26 GMT
Pragma:
- no-cache
Strict-Transport-Security:
- max-age=31536000
Vary:
- Authorization, X-Filter
X-Accepted-Oauth-Scopes:
- ips:read_write
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
- DENY
X-Oauth-Scopes:
- '*'
X-Ratelimit-Limit:
- "1600"
X-Xss-Protection:
- 1; mode=block
status: 200 OK
code: 200
duration: ""
- request:
body: '{"label":"go-test-def","region":"us-east","client_conn_throttle":20,"ipv4":"45.79.134.221","firewall_id":2511562}'
form: {}
headers:
Accept:
- application/json
Content-Type:
- application/json
User-Agent:
- linodego/dev https://github.com/linode/linodego
url: https://api.linode.com/v4beta/nodebalancers
method: POST
response:
body: '{"id": 1540254, "label": "go-test-def", "region": "us-east", "type": "common",
"hostname": "45-79-134-221.ip.linodeusercontent.com", "ipv4": "45.79.134.221",
"ipv6": "2600:3c03:1::45a4:dfc0", "created": "2018-01-02T03:04:05", "updated":
"2018-01-02T03:04:05", "client_conn_throttle": 20, "client_udp_sess_throttle":
0, "lke_cluster": null, "tags": [], "transfer": {"in": null, "out": null, "total":
null}}'
headers:
Access-Control-Allow-Credentials:
- "true"
Access-Control-Allow-Headers:
- Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter
Access-Control-Allow-Methods:
- HEAD, GET, OPTIONS, POST, PUT, DELETE
Access-Control-Allow-Origin:
- '*'
Access-Control-Expose-Headers:
- X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status
Akamai-Internal-Account:
- '*'
Cache-Control:
- max-age=0, no-cache, no-store
Connection:
- keep-alive
Content-Length:
- "407"
Content-Security-Policy:
- default-src 'none'
Content-Type:
- application/json
Expires:
- Thu, 08 May 2025 20:48:27 GMT
Pragma:
- no-cache
Strict-Transport-Security:
- max-age=31536000
Vary:
- Authorization, X-Filter
X-Accepted-Oauth-Scopes:
- nodebalancers:read_write
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
- DENY
X-Oauth-Scopes:
- '*'
X-Ratelimit-Limit:
- "400"
X-Xss-Protection:
- 1; mode=block
status: 200 OK
code: 200
duration: ""
- request:
body: ""
form: {}
headers:
Accept:
- application/json
Content-Type:
- application/json
User-Agent:
- linodego/dev https://github.com/linode/linodego
url: https://api.linode.com/v4beta/nodebalancers/1540254
method: DELETE
response:
body: '{}'
headers:
Access-Control-Allow-Credentials:
- "true"
Access-Control-Allow-Headers:
- Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter
Access-Control-Allow-Methods:
- HEAD, GET, OPTIONS, POST, PUT, DELETE
Access-Control-Allow-Origin:
- '*'
Access-Control-Expose-Headers:
- X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status
Akamai-Internal-Account:
- '*'
Cache-Control:
- max-age=0, no-cache, no-store
Connection:
- keep-alive
Content-Length:
- "2"
Content-Security-Policy:
- default-src 'none'
Content-Type:
- application/json
Expires:
- Thu, 08 May 2025 20:48:27 GMT
Pragma:
- no-cache
Strict-Transport-Security:
- max-age=31536000
Vary:
- Authorization, X-Filter
X-Accepted-Oauth-Scopes:
- nodebalancers:read_write
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
- DENY
X-Oauth-Scopes:
- '*'
X-Ratelimit-Limit:
- "1600"
X-Xss-Protection:
- 1; mode=block
status: 200 OK
code: 200
duration: ""
- request:
body: ""
form: {}
headers:
Accept:
- application/json
Content-Type:
- application/json
User-Agent:
- linodego/dev https://github.com/linode/linodego
url: https://api.linode.com/v4beta/networking/reserved/ips/45.79.134.221
method: DELETE
response:
body: '{}'
headers:
Access-Control-Allow-Credentials:
- "true"
Access-Control-Allow-Headers:
- Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter
Access-Control-Allow-Methods:
- HEAD, GET, OPTIONS, POST, PUT, DELETE
Access-Control-Allow-Origin:
- '*'
Access-Control-Expose-Headers:
- X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status
Akamai-Internal-Account:
- '*'
Cache-Control:
- max-age=0, no-cache, no-store
Connection:
- keep-alive
Content-Length:
- "2"
Content-Security-Policy:
- default-src 'none'
Content-Type:
- application/json
Expires:
- Thu, 08 May 2025 20:48:27 GMT
Pragma:
- no-cache
Strict-Transport-Security:
- max-age=31536000
Vary:
- Authorization, X-Filter
X-Accepted-Oauth-Scopes:
- ips:read_write
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
- DENY
X-Oauth-Scopes:
- '*'
X-Ratelimit-Limit:
- "10"
X-Xss-Protection:
- 1; mode=block
status: 200 OK
code: 200
duration: ""
58 changes: 58 additions & 0 deletions test/integration/nodebalancers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,27 @@ func TestNodeBalancer_Create_Type(t *testing.T) {
assertDateSet(t, nodebalancer.Updated)
}

func TestNodeBalancer_Create_with_ReservedIP(t *testing.T) {
_, reserveIP, nodebalancer, teardown, err := setupNodeBalancerWithReservedIP(t, "fixtures/TestNodeBalancer_With_ReservedIP_Create")
defer teardown()

if err != nil {
t.Errorf("Error creating nodebalancer: %v", err)
}

// when comparing fixtures to random value Label will differ, compare the known suffix
if !strings.Contains(*nodebalancer.Label, label) {
t.Errorf("nodebalancer returned does not match nodebalancer create request")
}

if reserveIP.Address != *nodebalancer.IPv4 {
t.Errorf("nodebalancer address: %s does not matched requested reserved IP: %s", *nodebalancer.IPv4, reserveIP.Address)
}

assertDateSet(t, nodebalancer.Created)
assertDateSet(t, nodebalancer.Updated)
}

func TestNodeBalancer_Create_with_vpc(t *testing.T) {
_, nodebalancer, _, _, teardown, err := setupNodeBalancerWithVPC(t, "fixtures/TestNodeBalancer_With_VPC_Create")
defer teardown()
Expand Down Expand Up @@ -150,6 +171,43 @@ func setupNodeBalancer(t *testing.T, fixturesYaml string, nbModifiers []nbModifi
return client, nodebalancer, teardown, err
}

func setupNodeBalancerWithReservedIP(t *testing.T, fixturesYaml string) (*linodego.Client, *linodego.InstanceIP, *linodego.NodeBalancer, func(), error) {
t.Helper()
var fixtureTeardown func()
client, fixtureTeardown := createTestClient(t, fixturesYaml)
reserveIP, err := client.ReserveIPAddress(context.Background(), linodego.ReserveIPOptions{
Region: "us-east",
})
if err != nil {
t.Fatalf("Failed to reserve IP %v", err)
}
t.Logf("Successfully reserved IP: %s", reserveIP.Address)

createOpts := linodego.NodeBalancerCreateOptions{
Label: &label,
Region: "us-east",
ClientConnThrottle: &clientConnThrottle,
FirewallID: GetFirewallID(),
IPv4: &reserveIP.Address,
}

nodebalancer, err := client.CreateNodeBalancer(context.Background(), createOpts)
if err != nil {
t.Fatalf("Error listing nodebalancers, expected struct, got error %v", err)
}

teardown := func() {
if err := client.DeleteNodeBalancer(context.Background(), nodebalancer.ID); err != nil {
t.Errorf("Expected to delete a nodebalancer, but got %v", err)
}
if err := client.DeleteReservedIPAddress(context.Background(), reserveIP.Address); err != nil {
t.Errorf("Expected to delete a reserved IP, but got %v", err)
}
fixtureTeardown()
}
return client, reserveIP, nodebalancer, teardown, err
}

func setupNodeBalancerWithVPC(t *testing.T, fixturesYaml string, vpcModifier ...vpcModifier) (*linodego.Client, *linodego.NodeBalancer, *linodego.VPC, *linodego.VPCSubnet, func(), error) {
t.Helper()
var fixtureTeardown func()
Expand Down
17 changes: 17 additions & 0 deletions test/unit/fixtures/nodebalancer_create_with_ipv4.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"id": 124,
"label": "Test NodeBalancer IPv4",
"region": "us-east",
"hostname": "nb-192-0-2-2.nodebalancer.linode.com",
"ipv4": "192.0.2.2",
"ipv6": null,
"client_conn_throttle": 0,
"transfer": {
"total": 0,
"out": 0,
"in": 0
},
"tags": ["test", "example"],
"created": "2023-01-01T00:00:00",
"updated": "2023-01-01T00:00:00"
}
Loading