Skip to content

Commit c2a7d4c

Browse files
feat(server): int64 host id fields, deprecate int fields (#501)
1 parent a983a52 commit c2a7d4c

12 files changed

Lines changed: 255 additions & 42 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ See updating [Changelog example here](https://keepachangelog.com/en/1.0.0/)
55

66
## [Unreleased]
77

8+
### Added
9+
10+
- server, host: add `int64` host identifier fields for safe JSON handling on 32-bit builds
11+
12+
### Deprecated
13+
14+
- server, host: `int` host identifier fields in favor of the `int64` alternatives
15+
816
## [8.35.0]
917

1018
### Added

upcloud/hosts.go

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@ type Hosts struct {
1313
// UnmarshalJSON is a custom unmarshaller that deals with
1414
// deeply embedded values.
1515
func (n *Hosts) UnmarshalJSON(b []byte) error {
16-
type localHost Host
1716
type hostWrapper struct {
18-
Hosts []localHost `json:"host"`
17+
Hosts []Host `json:"host"`
1918
}
2019

2120
v := struct {
@@ -26,9 +25,7 @@ func (n *Hosts) UnmarshalJSON(b []byte) error {
2625
return err
2726
}
2827

29-
for _, ln := range v.Hosts.Hosts {
30-
n.Hosts = append(n.Hosts, Host(ln))
31-
}
28+
n.Hosts = append(n.Hosts, v.Hosts.Hosts...)
3229

3330
return nil
3431
}
@@ -55,7 +52,9 @@ func (t *StatSlice) UnmarshalJSON(b []byte) error {
5552

5653
// Host represents an individual Host in a response
5754
type Host struct {
55+
// Deprecated: Use HostID instead.
5856
ID int `json:"id"`
57+
HostID int64 `json:"-"`
5958
Description string `json:"description"`
6059
Zone string `json:"zone"`
6160
WindowsEnabled Boolean `json:"windows_enabled"`
@@ -66,20 +65,44 @@ type Host struct {
6665
// deeply embedded values.
6766
func (s *Host) UnmarshalJSON(b []byte) error {
6867
type localHost Host
68+
type hostWrapper struct {
69+
localHost
70+
71+
ID int64 `json:"id"`
72+
}
6973

7074
v := struct {
71-
Host localHost `json:"host"`
75+
Host *hostWrapper `json:"host"`
7276
}{}
7377
err := json.Unmarshal(b, &v)
7478
if err != nil {
7579
return err
7680
}
7781

78-
(*s) = Host(v.Host)
82+
host := hostWrapper{}
83+
if v.Host != nil {
84+
host = *v.Host
85+
} else {
86+
err = json.Unmarshal(b, &host)
87+
if err != nil {
88+
return err
89+
}
90+
}
91+
92+
*s = Host(host.localHost)
93+
s.setHostID(host.ID)
7994

8095
return nil
8196
}
8297

98+
func (s *Host) setHostID(hostID int64) {
99+
s.HostID = hostID
100+
s.ID = 0
101+
if int64FitsInt(hostID) {
102+
s.ID = int(hostID)
103+
}
104+
}
105+
83106
// Stat represents Host stats in a response
84107
type Stat struct {
85108
Name string `json:"name"`

upcloud/hosts_test.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package upcloud
22

33
import (
44
"encoding/json"
5+
"strconv"
56
"testing"
67
"time"
78

@@ -13,14 +14,22 @@ func timeParse(s string) time.Time {
1314
return t
1415
}
1516

17+
func legacyHostID(id int64) int {
18+
if strconv.IntSize == 64 {
19+
return int(id)
20+
}
21+
22+
return 0
23+
}
24+
1625
// TestUnmarshalHosts tests that multiple Hosts and Stats are unmarshalled correctly.
1726
func TestUnmarshalHosts(t *testing.T) {
1827
originalJSON := `
1928
{
2029
"hosts": {
2130
"host": [
2231
{
23-
"id": 7653311107,
32+
"id": 9223372036854775807,
2433
"description": "My Host #1",
2534
"zone": "private-zone-id",
2635
"windows_enabled": "no",
@@ -70,7 +79,8 @@ func TestUnmarshalHosts(t *testing.T) {
7079

7180
testsHosts := []Host{
7281
{
73-
ID: 7653311107,
82+
ID: legacyHostID(9223372036854775807),
83+
HostID: 9223372036854775807,
7484
Description: "My Host #1",
7585
Zone: "private-zone-id",
7686
WindowsEnabled: False,
@@ -88,7 +98,8 @@ func TestUnmarshalHosts(t *testing.T) {
8898
},
8999
},
90100
{
91-
ID: 8055964291,
101+
ID: legacyHostID(8055964291),
102+
HostID: 8055964291,
92103
Description: "My Host #2",
93104
Zone: "private-zone-id",
94105
WindowsEnabled: False,
@@ -117,7 +128,7 @@ func TestUnmarshalHost(t *testing.T) {
117128
originalJSON := `
118129
{
119130
"host": {
120-
"id": 7653311107,
131+
"id": 9223372036854775807,
121132
"description": "My Host #1",
122133
"zone": "private-zone-id",
123134
"windows_enabled": "no",
@@ -144,7 +155,8 @@ func TestUnmarshalHost(t *testing.T) {
144155
assert.NoError(t, err)
145156

146157
testHost := Host{
147-
ID: 7653311107,
158+
ID: legacyHostID(9223372036854775807),
159+
HostID: 9223372036854775807,
148160
Description: "My Host #1",
149161
Zone: "private-zone-id",
150162
WindowsEnabled: False,

upcloud/request/hosts.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,27 @@ import (
88
// GetHostDetailsRequest represents the request for the details of a
99
// single private host
1010
type GetHostDetailsRequest struct {
11-
ID int
11+
// Deprecated: Use HostID instead.
12+
ID int
13+
HostID int64
1214
}
1315

1416
// RequestURL implements the Request interface
1517
func (r *GetHostDetailsRequest) RequestURL() string {
16-
return fmt.Sprintf("/host/%d", r.ID)
18+
return fmt.Sprintf("/host/%d", hostIDValue(r.ID, r.HostID))
1719
}
1820

1921
// ModifyHostRequest represents the request to modify a private host
2022
type ModifyHostRequest struct {
23+
// Deprecated: Use HostID instead.
2124
ID int `json:"-"`
25+
HostID int64 `json:"-"`
2226
Description string `json:"description"`
2327
}
2428

2529
// RequestURL implements the Request interface
2630
func (r *ModifyHostRequest) RequestURL() string {
27-
return fmt.Sprintf("/host/%d", r.ID)
31+
return fmt.Sprintf("/host/%d", hostIDValue(r.ID, r.HostID))
2832
}
2933

3034
// MarshalJSON is a custom marshaller that deals with

upcloud/request/hosts_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package request
22

33
import (
44
"encoding/json"
5+
"math"
56
"testing"
67

78
"github.com/stretchr/testify/assert"
@@ -16,6 +17,14 @@ func TestMarshalGetHostDetailsRequest(t *testing.T) {
1617
assert.Equal(t, "/host/1234", request.RequestURL())
1718
}
1819

20+
func TestMarshalGetHostDetailsRequest_WithHostID(t *testing.T) {
21+
request := GetHostDetailsRequest{
22+
HostID: math.MaxInt64,
23+
}
24+
25+
assert.Equal(t, "/host/9223372036854775807", request.RequestURL())
26+
}
27+
1928
// TestMarshalModifyHostRequest tests that ModifyHostRequest behaves correctly
2029
func TestMarshalModifyHostRequest(t *testing.T) {
2130
request := ModifyHostRequest{
@@ -37,3 +46,24 @@ func TestMarshalModifyHostRequest(t *testing.T) {
3746

3847
assert.Equal(t, "/host/1234", request.RequestURL())
3948
}
49+
50+
func TestMarshalModifyHostRequest_WithHostID(t *testing.T) {
51+
request := ModifyHostRequest{
52+
HostID: math.MaxInt64,
53+
Description: "My New Host",
54+
}
55+
56+
expectedJSON := `
57+
{
58+
"host" : {
59+
"description": "My New Host"
60+
}
61+
}
62+
`
63+
64+
actualJSON, err := json.Marshal(&request)
65+
assert.NoError(t, err)
66+
assert.JSONEq(t, expectedJSON, string(actualJSON))
67+
68+
assert.Equal(t, "/host/9223372036854775807", request.RequestURL())
69+
}

upcloud/request/server.go

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,12 @@ type CreateServerNetworking struct {
135135

136136
// CreateServerRequest represents a request for creating a new server
137137
type CreateServerRequest struct {
138-
AvoidHost int `json:"avoid_host,omitempty"`
139-
Host int `json:"host,omitempty"`
138+
// Deprecated: Use AvoidHostID instead.
139+
AvoidHost int `json:"-"`
140+
AvoidHostID int64 `json:"-"`
141+
// Deprecated: Use HostID instead.
142+
Host int `json:"-"`
143+
HostID int64 `json:"-"`
140144
BootOrder string `json:"boot_order,omitempty"`
141145
CoreNumber int `json:"core_number,omitempty"`
142146
// TODO: Convert to boolean
@@ -168,9 +172,16 @@ type CreateServerRequest struct {
168172
func (r CreateServerRequest) MarshalJSON() ([]byte, error) {
169173
type localCreateServerRequest CreateServerRequest
170174
v := struct {
171-
Server localCreateServerRequest `json:"server"`
175+
Server struct {
176+
localCreateServerRequest
177+
178+
AvoidHost *int64 `json:"avoid_host,omitempty"`
179+
Host *int64 `json:"host,omitempty"`
180+
} `json:"server"`
172181
}{}
173-
v.Server = localCreateServerRequest(r)
182+
v.Server.localCreateServerRequest = localCreateServerRequest(r)
183+
v.Server.AvoidHost = hostIDPtr(r.AvoidHost, r.AvoidHostID)
184+
v.Server.Host = hostIDPtr(r.Host, r.HostID)
174185

175186
return json.Marshal(&v)
176187
}
@@ -234,9 +245,13 @@ type WaitForServerStateRequest struct {
234245

235246
// StartServerRequest represents a request to start a server
236247
type StartServerRequest struct {
237-
UUID string `json:"-"`
238-
AvoidHost int `json:"avoid_host,omitempty"`
239-
Host int `json:"host,omitempty"`
248+
UUID string `json:"-"`
249+
// Deprecated: Use AvoidHostID instead.
250+
AvoidHost int `json:"-"`
251+
AvoidHostID int64 `json:"-"`
252+
// Deprecated: Use HostID instead.
253+
Host int `json:"-"`
254+
HostID int64 `json:"-"`
240255
}
241256

242257
// RequestURL implements the Request interface
@@ -249,9 +264,16 @@ func (r *StartServerRequest) RequestURL() string {
249264
func (r StartServerRequest) MarshalJSON() ([]byte, error) {
250265
type localStartServerRequest StartServerRequest
251266
v := struct {
252-
Server localStartServerRequest `json:"server"`
267+
Server struct {
268+
localStartServerRequest
269+
270+
AvoidHost *int64 `json:"avoid_host,omitempty"`
271+
Host *int64 `json:"host,omitempty"`
272+
} `json:"server"`
253273
}{}
254-
v.Server = localStartServerRequest(r)
274+
v.Server.localStartServerRequest = localStartServerRequest(r)
275+
v.Server.AvoidHost = hostIDPtr(r.AvoidHost, r.AvoidHostID)
276+
v.Server.Host = hostIDPtr(r.Host, r.HostID)
255277

256278
return json.Marshal(&v)
257279
}
@@ -289,7 +311,9 @@ type RestartServerRequest struct {
289311
StopType string `json:"stop_type,omitempty"`
290312
Timeout time.Duration `json:"timeout,omitempty,string"`
291313
TimeoutAction string `json:"timeout_action,omitempty"`
292-
Host int `json:"host,omitempty"`
314+
// Deprecated: Use HostID instead.
315+
Host int `json:"-"`
316+
HostID int64 `json:"-"`
293317
}
294318

295319
// RequestURL implements the Request interface
@@ -302,14 +326,36 @@ func (r *RestartServerRequest) RequestURL() string {
302326
func (r RestartServerRequest) MarshalJSON() ([]byte, error) {
303327
type localRestartServerRequest RestartServerRequest
304328
v := struct {
305-
RestartServerRequest localRestartServerRequest `json:"restart_server"`
329+
RestartServerRequest struct {
330+
localRestartServerRequest
331+
332+
Host *int64 `json:"host,omitempty"`
333+
} `json:"restart_server"`
306334
}{}
307-
v.RestartServerRequest = localRestartServerRequest(r)
335+
v.RestartServerRequest.localRestartServerRequest = localRestartServerRequest(r)
308336
v.RestartServerRequest.Timeout = v.RestartServerRequest.Timeout / 1e9
337+
v.RestartServerRequest.Host = hostIDPtr(r.Host, r.HostID)
309338

310339
return json.Marshal(&v)
311340
}
312341

342+
func hostIDValue(id int, hostID int64) int64 {
343+
if hostID != 0 {
344+
return hostID
345+
}
346+
347+
return int64(id)
348+
}
349+
350+
func hostIDPtr(id int, hostID int64) *int64 {
351+
resolved := hostIDValue(id, hostID)
352+
if resolved == 0 {
353+
return nil
354+
}
355+
356+
return &resolved
357+
}
358+
313359
// ModifyServerRequest represents a request to modify a server
314360
type ModifyServerRequest struct {
315361
UUID string `json:"-"`

0 commit comments

Comments
 (0)