diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cc03487..126272a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ See updating [Changelog example here](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] +### Added +- managed load balancer: add `WaitForLoadBalancerState` and `WaitForLoadBalancerDeletion` helpers for waiting load-balancer state and deletion + ## [8.18.0] ### Added diff --git a/go.mod b/go.mod index c78ba181..2177acd0 100644 --- a/go.mod +++ b/go.mod @@ -4,14 +4,13 @@ go 1.22 require ( github.com/davecgh/go-spew v1.1.1 - github.com/dnaeon/go-vcr v1.2.0 github.com/stretchr/testify v1.9.0 + gopkg.in/dnaeon/go-vcr.v4 v4.0.2 ) require ( github.com/kr/pretty v0.3.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 1a4623a0..d408a257 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= -github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -10,7 +8,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -21,8 +18,7 @@ github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/dnaeon/go-vcr.v4 v4.0.2 h1:7T5VYf2ifyK01ETHbJPl5A6XTpUljD4Trw3GEDcdedk= +gopkg.in/dnaeon/go-vcr.v4 v4.0.2/go.mod h1:65yxh9goQVrudqofKtHA4JNFWd6XZRkWfKN4YpMx7KI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/upcloud/client/client.go b/upcloud/client/client.go index b58091f3..e1cdd3f0 100644 --- a/upcloud/client/client.go +++ b/upcloud/client/client.go @@ -97,13 +97,32 @@ func (c *Client) Do(r *http.Request) ([]byte, error) { return c.handleResponse(response) } +type clientContextKey string + +const fragmentContextKey clientContextKey = "fragment" + +func WithFragment(ctx context.Context, fragment string) context.Context { + return context.WithValue(ctx, fragmentContextKey, fragment) +} + +func GetFragment(ctx context.Context) string { + if hash, ok := ctx.Value(fragmentContextKey).(string); ok { + return hash + } + return "" +} + func (c *Client) createRequest(ctx context.Context, method, path string, body []byte) (*http.Request, error) { var bodyReader io.Reader if body != nil { bodyReader = bytes.NewBuffer(body) } - req, err := http.NewRequestWithContext(ctx, method, c.createRequestURL(path), bodyReader) + url := c.createRequestURL(path) + if fragment := GetFragment(ctx); fragment != "" { + url = fmt.Sprintf("%s#%s", url, fragment) + } + req, err := http.NewRequestWithContext(ctx, method, url, bodyReader) if err != nil { return nil, err } diff --git a/upcloud/request/load_balancer.go b/upcloud/request/load_balancer.go index 45afd89d..8068019e 100644 --- a/upcloud/request/load_balancer.go +++ b/upcloud/request/load_balancer.go @@ -737,3 +737,12 @@ type GetLoadBalancerDNSChallengeDomainRequest struct{} func (r *GetLoadBalancerDNSChallengeDomainRequest) RequestURL() string { return "/load-balancer/certificate-bundles/dns-challenge-domain" } + +type WaitForLoadBalancerStateRequest struct { + UUID string + DesiredState upcloud.LoadBalancerOperationalState +} + +type WaitForLoadBalancerDeletionRequest struct { + UUID string `json:"-"` +} diff --git a/upcloud/service/account_test.go b/upcloud/service/account_test.go index 1def0c6a..8d2cddc6 100644 --- a/upcloud/service/account_test.go +++ b/upcloud/service/account_test.go @@ -10,10 +10,10 @@ import ( "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/client" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/request" - "github.com/dnaeon/go-vcr/cassette" - "github.com/dnaeon/go-vcr/recorder" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/dnaeon/go-vcr.v4/pkg/cassette" + "gopkg.in/dnaeon/go-vcr.v4/pkg/recorder" ) // TestGetAccount tests that the GetAccount() method returns proper data @@ -49,16 +49,18 @@ func TestGetAccount(t *testing.T) { // - Get account list and check that subaccount and main account is listed // - Delete tag and subaccount func TestListDetailsCreateModifyDeleteSubaccount(t *testing.T) { + mainAccount := "testuser" + + // Mask username from recording just in case if developer forgets to review it before commit + hook := func(i *cassette.Interaction) error { + testuser, _ := getCredentials() + i.Request.Body = strings.Replace(i.Request.Body, testuser, mainAccount, -1) + i.Response.Body = strings.Replace(i.Response.Body, testuser, mainAccount, -1) + return nil + } + record(t, "createmodifydeletesubaccount", func(ctx context.Context, t *testing.T, rec *recorder.Recorder, svc *Service) { var err error - mainAccount := "testuser" - rec.AddFilter(func(i *cassette.Interaction) error { - // try to mask username from recording just in case if developer forgets to review it before commit - testuser, _ := getCredentials() - i.Request.Body = strings.Replace(i.Request.Body, testuser, mainAccount, -1) - i.Response.Body = strings.Replace(i.Response.Body, testuser, mainAccount, -1) - return nil - }) username := "sdk_test_subaccount" tagName := fmt.Sprintf("sdk_test_tag_%s", username) @@ -235,5 +237,5 @@ func TestListDetailsCreateModifyDeleteSubaccount(t *testing.T) { } assert.False(t, subAccountNotFound, "subaccount not found from list of accounts") assert.False(t, mainAccountNotFound, "main account not found from list of accounts") - }) + }, recorder.WithHook(hook, recorder.BeforeSaveHook)) } diff --git a/upcloud/service/cloud_test.go b/upcloud/service/cloud_test.go index 4c583046..fc87d4ce 100644 --- a/upcloud/service/cloud_test.go +++ b/upcloud/service/cloud_test.go @@ -4,9 +4,9 @@ import ( "context" "testing" - "github.com/dnaeon/go-vcr/recorder" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/dnaeon/go-vcr.v4/pkg/recorder" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud" ) diff --git a/upcloud/service/firewall_test.go b/upcloud/service/firewall_test.go index 1b36f527..0aa06d79 100644 --- a/upcloud/service/firewall_test.go +++ b/upcloud/service/firewall_test.go @@ -7,9 +7,9 @@ import ( "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/request" - "github.com/dnaeon/go-vcr/recorder" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/dnaeon/go-vcr.v4/pkg/recorder" ) // TestFirewallRules performs the following actions with context: diff --git a/upcloud/service/fixtures/createrestartserver.yaml b/upcloud/service/fixtures/createrestartserver.yaml index 49bccee3..5f9716f4 100644 --- a/upcloud/service/fixtures/createrestartserver.yaml +++ b/upcloud/service/fixtures/createrestartserver.yaml @@ -1,800 +1,238 @@ --- -version: 1 +version: 2 interactions: -- request: - body: '{"server":{"hostname":"uploud-go-sdk-integration-test-createrestartserver.example.com","labels":{"label":[{"key":"managedBy","value":"upcloud-sdk-integration-test"},{"key":"testName","value":"createrestartserver"}]},"metadata":"no","networking":{"interfaces":{"interface":[{"ip_addresses":{"ip_address":[{"family":"IPv4"}]},"type":"utility"},{"ip_addresses":{"ip_address":[{"family":"IPv4"}]},"type":"public"},{"ip_addresses":{"ip_address":[{"family":"IPv6"}]},"type":"public"}]}},"password_delivery":"none","storage_devices":{"storage_device":[{"action":"clone","storage":"01000000-0000-4000-8000-000020060100","title":"disk1","size":10,"tier":"maxiops"}]},"title":"uploud-go-sdk-integration-test-createrestartserver","remote_access_enabled":"no","zone":"fi-hel2"}}' - form: {} - headers: - Accept: - - application/json - Content-Type: - - application/json - User-Agent: - - upcloud-go-api/4.9.0 - url: https://api.upcloud.com/1.3/server - method: POST - response: - body: | - { - "server" : { - "boot_order" : "disk", - "core_number" : "1", - "created" : 1666609570, - "firewall" : "off", - "hostname" : "uploud-go-sdk-integration-test-createrestartserver.example.com", - "ip_addresses" : { - "ip_address" : [ - { - "access" : "utility", - "address" : "10.6.6.192", - "family" : "IPv4" - }, - { - "access" : "public", - "address" : "2a04:3545:1000:720:d845:c4ff:fe5c:28f4", - "family" : "IPv6" - }, - { - "access" : "public", - "address" : "94.237.9.224", - "family" : "IPv4" - } - ] - }, - "labels" : { - "label" : [ - { - "key" : "managedBy", - "value" : "upcloud-sdk-integration-test" - }, - { - "key" : "testName", - "value" : "createrestartserver" - } - ] - }, - "license" : 0, - "memory_amount" : "1024", - "metadata" : "no", - "networking" : { - "interfaces" : { - "interface" : [ - { - "bootable" : "no", - "index" : 1, - "ip_addresses" : { - "ip_address" : [ - { - "address" : "10.6.6.192", - "family" : "IPv4", - "floating" : "no" - } - ] - }, - "mac" : "da:45:c4:5c:83:7c", - "network" : "0343ede2-9890-4470-b680-0dcda6d9f7e3", - "source_ip_filtering" : "yes", - "type" : "utility" - }, - { - "bootable" : "no", - "index" : 2, - "ip_addresses" : { - "ip_address" : [ - { - "address" : "94.237.9.224", - "family" : "IPv4", - "floating" : "no" - } - ] - }, - "mac" : "da:45:c4:5c:38:d4", - "network" : "03000000-0000-4000-8044-000000000000", - "source_ip_filtering" : "yes", - "type" : "public" - }, - { - "bootable" : "no", - "index" : 3, - "ip_addresses" : { - "ip_address" : [ - { - "address" : "2a04:3545:1000:720:d845:c4ff:fe5c:28f4", - "family" : "IPv6", - "floating" : "no" - } - ] - }, - "mac" : "da:45:c4:5c:28:f4", - "network" : "03000000-0000-4000-8046-000000000000", - "source_ip_filtering" : "yes", - "type" : "public" - } - ] - } - }, - "nic_model" : "virtio", - "password" : "q6373z2538769g7b", - "plan" : "custom", - "plan_ipv4_bytes" : "0", - "plan_ipv6_bytes" : "0", - "progress" : "0", - "remote_access_enabled" : "no", - "remote_access_password" : "4qw5yU8Q", - "remote_access_type" : "vnc", - "simple_backup" : "no", - "state" : "maintenance", - "storage_devices" : { - "storage_device" : [ - { - "address" : "virtio:0", - "boot_disk" : "0", - "storage" : "01ef765b-add6-4ed3-927e-f4bc7d2a013d", - "storage_size" : 10, - "storage_tier" : "maxiops", - "storage_title" : "disk1", - "type" : "disk" - } - ] - }, - "tags" : { - "tag" : [] - }, - "timezone" : "UTC", - "title" : "uploud-go-sdk-integration-test-createrestartserver", - "username" : "root", - "uuid" : "005ceb18-6e0f-4520-add7-4e4a8b34ea1e", - "video_model" : "cirrus", - "zone" : "fi-hel2" - } - } - headers: - Content-Length: - - "4149" - Content-Type: - - application/json; charset=UTF-8 - Date: - - Mon, 24 Oct 2022 11:06:08 GMT - Server: - - Apache - Strict-Transport-Security: - - max-age=63072000 - status: 202 Accepted - code: 202 - duration: "" -- request: - body: "" - form: {} - headers: - Accept: - - application/json - Content-Type: - - application/json - User-Agent: - - upcloud-go-api/4.9.0 - url: https://api.upcloud.com/1.3/server/005ceb18-6e0f-4520-add7-4e4a8b34ea1e - method: GET - response: - body: | - { - "server" : { - "boot_order" : "disk", - "core_number" : "1", - "created" : 1666609570, - "firewall" : "off", - "host" : 6946046595, - "hostname" : "uploud-go-sdk-integration-test-createrestartserver.example.com", - "ip_addresses" : { - "ip_address" : [ - { - "access" : "utility", - "address" : "10.6.6.192", - "family" : "IPv4" - }, - { - "access" : "public", - "address" : "2a04:3545:1000:720:d845:c4ff:fe5c:28f4", - "family" : "IPv6" - }, - { - "access" : "public", - "address" : "94.237.9.224", - "family" : "IPv4" - } - ] - }, - "labels" : { - "label" : [ - { - "key" : "managedBy", - "value" : "upcloud-sdk-integration-test" - }, - { - "key" : "testName", - "value" : "createrestartserver" - } - ] - }, - "license" : 0, - "memory_amount" : "1024", - "metadata" : "no", - "networking" : { - "interfaces" : { - "interface" : [ - { - "bootable" : "no", - "index" : 1, - "ip_addresses" : { - "ip_address" : [ - { - "address" : "10.6.6.192", - "family" : "IPv4", - "floating" : "no" - } - ] - }, - "mac" : "da:45:c4:5c:83:7c", - "network" : "0343ede2-9890-4470-b680-0dcda6d9f7e3", - "source_ip_filtering" : "yes", - "type" : "utility" - }, - { - "bootable" : "no", - "index" : 2, - "ip_addresses" : { - "ip_address" : [ - { - "address" : "94.237.9.224", - "family" : "IPv4", - "floating" : "no" - } - ] - }, - "mac" : "da:45:c4:5c:38:d4", - "network" : "03000000-0000-4000-8044-000000000000", - "source_ip_filtering" : "yes", - "type" : "public" - }, - { - "bootable" : "no", - "index" : 3, - "ip_addresses" : { - "ip_address" : [ - { - "address" : "2a04:3545:1000:720:d845:c4ff:fe5c:28f4", - "family" : "IPv6", - "floating" : "no" - } - ] - }, - "mac" : "da:45:c4:5c:28:f4", - "network" : "03000000-0000-4000-8046-000000000000", - "source_ip_filtering" : "yes", - "type" : "public" - } - ] - } - }, - "nic_model" : "virtio", - "plan" : "custom", - "plan_ipv4_bytes" : "0", - "plan_ipv6_bytes" : "0", - "remote_access_enabled" : "no", - "remote_access_password" : "4qw5yU8Q", - "remote_access_type" : "vnc", - "simple_backup" : "no", - "state" : "started", - "storage_devices" : { - "storage_device" : [ - { - "address" : "virtio:0", - "boot_disk" : "0", - "storage" : "01ef765b-add6-4ed3-927e-f4bc7d2a013d", - "storage_size" : 10, - "storage_tier" : "maxiops", - "storage_title" : "disk1", - "type" : "disk" - } - ] - }, - "tags" : { - "tag" : [] - }, - "timezone" : "UTC", - "title" : "uploud-go-sdk-integration-test-createrestartserver", - "uuid" : "005ceb18-6e0f-4520-add7-4e4a8b34ea1e", - "video_model" : "cirrus", - "zone" : "fi-hel2" - } - } - headers: - Content-Length: - - "4082" - Content-Type: - - application/json; charset=UTF-8 - Date: - - Mon, 24 Oct 2022 11:06:47 GMT - Server: - - Apache - Strict-Transport-Security: - - max-age=63072000 - status: 200 OK - code: 200 - duration: "" -- request: - body: '{"restart_server":{"stop_type":"hard","timeout":"900","timeout_action":"ignore"}}' - form: {} - headers: - Accept: - - application/json - Content-Type: - - application/json - User-Agent: - - upcloud-go-api/4.9.0 - url: https://api.upcloud.com/1.3/server/005ceb18-6e0f-4520-add7-4e4a8b34ea1e/restart - method: POST - response: - body: | - { - "server" : { - "boot_order" : "disk", - "core_number" : "1", - "created" : 1666609570, - "firewall" : "off", - "host" : 6946046595, - "hostname" : "uploud-go-sdk-integration-test-createrestartserver.example.com", - "ip_addresses" : { - "ip_address" : [ - { - "access" : "utility", - "address" : "10.6.6.192", - "family" : "IPv4" - }, - { - "access" : "public", - "address" : "2a04:3545:1000:720:d845:c4ff:fe5c:28f4", - "family" : "IPv6" - }, - { - "access" : "public", - "address" : "94.237.9.224", - "family" : "IPv4" - } - ] - }, - "labels" : { - "label" : [ - { - "key" : "managedBy", - "value" : "upcloud-sdk-integration-test" - }, - { - "key" : "testName", - "value" : "createrestartserver" - } - ] - }, - "license" : 0, - "memory_amount" : "1024", - "metadata" : "no", - "networking" : { - "interfaces" : { - "interface" : [ - { - "bootable" : "no", - "index" : 1, - "ip_addresses" : { - "ip_address" : [ - { - "address" : "10.6.6.192", - "family" : "IPv4", - "floating" : "no" - } - ] - }, - "mac" : "da:45:c4:5c:83:7c", - "network" : "0343ede2-9890-4470-b680-0dcda6d9f7e3", - "source_ip_filtering" : "yes", - "type" : "utility" - }, - { - "bootable" : "no", - "index" : 2, - "ip_addresses" : { - "ip_address" : [ - { - "address" : "94.237.9.224", - "family" : "IPv4", - "floating" : "no" - } - ] - }, - "mac" : "da:45:c4:5c:38:d4", - "network" : "03000000-0000-4000-8044-000000000000", - "source_ip_filtering" : "yes", - "type" : "public" - }, - { - "bootable" : "no", - "index" : 3, - "ip_addresses" : { - "ip_address" : [ - { - "address" : "2a04:3545:1000:720:d845:c4ff:fe5c:28f4", - "family" : "IPv6", - "floating" : "no" - } - ] - }, - "mac" : "da:45:c4:5c:28:f4", - "network" : "03000000-0000-4000-8046-000000000000", - "source_ip_filtering" : "yes", - "type" : "public" - } - ] - } - }, - "nic_model" : "virtio", - "plan" : "custom", - "plan_ipv4_bytes" : "0", - "plan_ipv6_bytes" : "0", - "remote_access_enabled" : "no", - "remote_access_password" : "4qw5yU8Q", - "remote_access_type" : "vnc", - "simple_backup" : "no", - "state" : "started", - "storage_devices" : { - "storage_device" : [ - { - "address" : "virtio:0", - "boot_disk" : "0", - "storage" : "01ef765b-add6-4ed3-927e-f4bc7d2a013d", - "storage_size" : 10, - "storage_tier" : "maxiops", - "storage_title" : "disk1", - "type" : "disk" - } - ] - }, - "tags" : { - "tag" : [] - }, - "timezone" : "UTC", - "title" : "uploud-go-sdk-integration-test-createrestartserver", - "uuid" : "005ceb18-6e0f-4520-add7-4e4a8b34ea1e", - "video_model" : "cirrus", - "zone" : "fi-hel2" - } - } - headers: - Content-Length: - - "4082" - Content-Type: - - application/json; charset=UTF-8 - Date: - - Mon, 24 Oct 2022 11:06:48 GMT - Server: - - Apache - Strict-Transport-Security: - - max-age=63072000 - status: 200 OK - code: 200 - duration: "" -- request: - body: "" - form: {} - headers: - Accept: - - application/json - Content-Type: - - application/json - User-Agent: - - upcloud-go-api/4.9.0 - url: https://api.upcloud.com/1.3/server/005ceb18-6e0f-4520-add7-4e4a8b34ea1e - method: GET - response: - body: | - { - "server" : { - "boot_order" : "disk", - "core_number" : "1", - "created" : 1666609570, - "firewall" : "off", - "host" : 6946046595, - "hostname" : "uploud-go-sdk-integration-test-createrestartserver.example.com", - "ip_addresses" : { - "ip_address" : [ - { - "access" : "utility", - "address" : "10.6.6.192", - "family" : "IPv4" - }, - { - "access" : "public", - "address" : "2a04:3545:1000:720:d845:c4ff:fe5c:28f4", - "family" : "IPv6" - }, - { - "access" : "public", - "address" : "94.237.9.224", - "family" : "IPv4" - } - ] - }, - "labels" : { - "label" : [ - { - "key" : "managedBy", - "value" : "upcloud-sdk-integration-test" - }, - { - "key" : "testName", - "value" : "createrestartserver" - } - ] - }, - "license" : 0, - "memory_amount" : "1024", - "metadata" : "no", - "networking" : { - "interfaces" : { - "interface" : [ - { - "bootable" : "no", - "index" : 1, - "ip_addresses" : { - "ip_address" : [ - { - "address" : "10.6.6.192", - "family" : "IPv4", - "floating" : "no" - } - ] - }, - "mac" : "da:45:c4:5c:83:7c", - "network" : "0343ede2-9890-4470-b680-0dcda6d9f7e3", - "source_ip_filtering" : "yes", - "type" : "utility" - }, - { - "bootable" : "no", - "index" : 2, - "ip_addresses" : { - "ip_address" : [ - { - "address" : "94.237.9.224", - "family" : "IPv4", - "floating" : "no" - } - ] - }, - "mac" : "da:45:c4:5c:38:d4", - "network" : "03000000-0000-4000-8044-000000000000", - "source_ip_filtering" : "yes", - "type" : "public" - }, - { - "bootable" : "no", - "index" : 3, - "ip_addresses" : { - "ip_address" : [ - { - "address" : "2a04:3545:1000:720:d845:c4ff:fe5c:28f4", - "family" : "IPv6", - "floating" : "no" - } - ] - }, - "mac" : "da:45:c4:5c:28:f4", - "network" : "03000000-0000-4000-8046-000000000000", - "source_ip_filtering" : "yes", - "type" : "public" - } - ] - } - }, - "nic_model" : "virtio", - "plan" : "custom", - "plan_ipv4_bytes" : "0", - "plan_ipv6_bytes" : "0", - "remote_access_enabled" : "no", - "remote_access_password" : "4qw5yU8Q", - "remote_access_type" : "vnc", - "simple_backup" : "no", - "state" : "maintenance", - "storage_devices" : { - "storage_device" : [ - { - "address" : "virtio:0", - "boot_disk" : "0", - "storage" : "01ef765b-add6-4ed3-927e-f4bc7d2a013d", - "storage_size" : 10, - "storage_tier" : "maxiops", - "storage_title" : "disk1", - "type" : "disk" - } - ] - }, - "tags" : { - "tag" : [] - }, - "timezone" : "UTC", - "title" : "uploud-go-sdk-integration-test-createrestartserver", - "uuid" : "005ceb18-6e0f-4520-add7-4e4a8b34ea1e", - "video_model" : "cirrus", - "zone" : "fi-hel2" - } - } - headers: - Content-Length: - - "4086" - Content-Type: - - application/json; charset=UTF-8 - Date: - - Mon, 24 Oct 2022 11:06:54 GMT - Server: - - Apache - Strict-Transport-Security: - - max-age=63072000 - status: 200 OK - code: 200 - duration: "" -- request: - body: "" - form: {} - headers: - Accept: - - application/json - Content-Type: - - application/json - User-Agent: - - upcloud-go-api/4.9.0 - url: https://api.upcloud.com/1.3/server/005ceb18-6e0f-4520-add7-4e4a8b34ea1e - method: GET - response: - body: | - { - "server" : { - "boot_order" : "disk", - "core_number" : "1", - "created" : 1666609570, - "firewall" : "off", - "host" : 6946046595, - "hostname" : "uploud-go-sdk-integration-test-createrestartserver.example.com", - "ip_addresses" : { - "ip_address" : [ - { - "access" : "utility", - "address" : "10.6.6.192", - "family" : "IPv4" - }, - { - "access" : "public", - "address" : "2a04:3545:1000:720:d845:c4ff:fe5c:28f4", - "family" : "IPv6" - }, - { - "access" : "public", - "address" : "94.237.9.224", - "family" : "IPv4" - } - ] - }, - "labels" : { - "label" : [ - { - "key" : "managedBy", - "value" : "upcloud-sdk-integration-test" - }, - { - "key" : "testName", - "value" : "createrestartserver" - } - ] - }, - "license" : 0, - "memory_amount" : "1024", - "metadata" : "no", - "networking" : { - "interfaces" : { - "interface" : [ - { - "bootable" : "no", - "index" : 1, - "ip_addresses" : { - "ip_address" : [ - { - "address" : "10.6.6.192", - "family" : "IPv4", - "floating" : "no" - } - ] - }, - "mac" : "da:45:c4:5c:83:7c", - "network" : "0343ede2-9890-4470-b680-0dcda6d9f7e3", - "source_ip_filtering" : "yes", - "type" : "utility" - }, - { - "bootable" : "no", - "index" : 2, - "ip_addresses" : { - "ip_address" : [ - { - "address" : "94.237.9.224", - "family" : "IPv4", - "floating" : "no" - } - ] - }, - "mac" : "da:45:c4:5c:38:d4", - "network" : "03000000-0000-4000-8044-000000000000", - "source_ip_filtering" : "yes", - "type" : "public" - }, - { - "bootable" : "no", - "index" : 3, - "ip_addresses" : { - "ip_address" : [ - { - "address" : "2a04:3545:1000:720:d845:c4ff:fe5c:28f4", - "family" : "IPv6", - "floating" : "no" - } - ] - }, - "mac" : "da:45:c4:5c:28:f4", - "network" : "03000000-0000-4000-8046-000000000000", - "source_ip_filtering" : "yes", - "type" : "public" - } - ] - } - }, - "nic_model" : "virtio", - "plan" : "custom", - "plan_ipv4_bytes" : "0", - "plan_ipv6_bytes" : "0", - "remote_access_enabled" : "no", - "remote_access_password" : "4qw5yU8Q", - "remote_access_type" : "vnc", - "simple_backup" : "no", - "state" : "started", - "storage_devices" : { - "storage_device" : [ - { - "address" : "virtio:0", - "boot_disk" : "0", - "storage" : "01ef765b-add6-4ed3-927e-f4bc7d2a013d", - "storage_size" : 10, - "storage_tier" : "maxiops", - "storage_title" : "disk1", - "type" : "disk" - } - ] - }, - "tags" : { - "tag" : [] - }, - "timezone" : "UTC", - "title" : "uploud-go-sdk-integration-test-createrestartserver", - "uuid" : "005ceb18-6e0f-4520-add7-4e4a8b34ea1e", - "video_model" : "cirrus", - "zone" : "fi-hel2" - } - } - headers: - Content-Length: - - "4082" - Content-Type: - - application/json; charset=UTF-8 - Date: - - Mon, 24 Oct 2022 11:07:10 GMT - Server: - - Apache - Strict-Transport-Security: - - max-age=63072000 - status: 200 OK - code: 200 - duration: "" + - id: 0 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 766 + transfer_encoding: [] + trailer: {} + host: api.upcloud.com + remote_addr: "" + request_uri: "" + body: '{"server":{"hostname":"uploud-go-sdk-integration-test-createrestartserver.example.com","labels":{"label":[{"key":"managedBy","value":"upcloud-sdk-integration-test"},{"key":"testName","value":"createrestartserver"}]},"metadata":"no","networking":{"interfaces":{"interface":[{"ip_addresses":{"ip_address":[{"family":"IPv4"}]},"type":"utility"},{"ip_addresses":{"ip_address":[{"family":"IPv4"}]},"type":"public"},{"ip_addresses":{"ip_address":[{"family":"IPv6"}]},"type":"public"}]}},"password_delivery":"none","storage_devices":{"storage_device":[{"action":"clone","storage":"01000000-0000-4000-8000-000020060100","title":"disk1","size":10,"tier":"maxiops"}]},"title":"uploud-go-sdk-integration-test-createrestartserver","remote_access_enabled":"no","zone":"fi-hel2"}}' + form: {} + headers: + Accept: + - application/json + Authorization: + - Basic [REDACTED] + Content-Type: + - application/json + User-Agent: + - upcloud-go-api/8.18.0 + url: https://api.upcloud.com/1.3/server + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 5415 + uncompressed: false + body: '{"server":{"boot_order":"disk","core_number":"1","created":1747917414,"firewall":"off","host":8556659331,"hostname":"uploud-go-sdk-integration-test-createrestartserver.example.com","ip_addresses":{"ip_address":[{"access":"utility","address":"10.6.12.219","family":"IPv4"},{"access":"public","address":"2a04:3545:1000:720:e0a4:b3ff:fe19:6dc7","family":"IPv6"},{"access":"public","address":"94.237.106.79","family":"IPv4"}]},"labels":{"label":[{"key":"managedBy","value":"upcloud-sdk-integration-test"},{"key":"testName","value":"createrestartserver"}]},"license":0,"memory_amount":"1024","metadata":"no","networking":{"interfaces":{"interface":[{"bootable":"no","index":1,"ip_addresses":{"ip_address":[{"address":"10.6.12.219","dhcp_provided":"yes","family":"IPv4","floating":"no","release_policy":"release"}]},"mac":"e2:a4:b3:19:bc:e8","network":"033e8ae3-3125-4506-aecb-d5bfc6c698d6","source_ip_filtering":"yes","type":"utility"},{"bootable":"no","index":2,"ip_addresses":{"ip_address":[{"address":"94.237.106.79","dhcp_provided":"yes","family":"IPv4","floating":"no","release_policy":"release"}]},"mac":"e2:a4:b3:19:7a:67","network":"031457f4-0f8c-483c-96f2-eccede02909c","source_ip_filtering":"yes","type":"public"},{"bootable":"no","index":3,"ip_addresses":{"ip_address":[{"address":"2a04:3545:1000:720:e0a4:b3ff:fe19:6dc7","dhcp_provided":"yes","family":"IPv6","floating":"no","release_policy":"release"}]},"mac":"e2:a4:b3:19:6d:c7","network":"03000000-0000-4000-8046-000000000000","source_ip_filtering":"yes","type":"public"}]}},"nic_model":"virtio","password":"rqjcvhtk5r2y97ub","plan":"custom","plan_ipv4_bytes":"0","plan_ipv6_bytes":"0","progress":"0","remote_access_enabled":"no","remote_access_password":"m6Ufm7GV","remote_access_type":"vnc","server_group":null,"simple_backup":"no","state":"maintenance","storage_devices":{"storage_device":[{"address":"virtio:0","boot_disk":"0","labels":[{"key":"_os_architecture","value":"64"},{"key":"_os_brand_name","value":"Debian"},{"key":"_os_brand_version","value":"11"},{"key":"_os_main_category","value":"Linux"},{"key":"_os_type","value":"debian"},{"key":"_template_uuid","value":"01000000-0000-4000-8000-000020060100"}],"storage":"016dc265-1fdd-48f8-878b-363852c9e30c","storage_encrypted":"no","storage_size":10,"storage_tier":"maxiops","storage_title":"disk1","type":"disk"}]},"tags":{"tag":[]},"timezone":"UTC","title":"uploud-go-sdk-integration-test-createrestartserver","username":"root","uuid":"00c51c63-a1fb-4bc3-9028-2238210b50bf","video_model":"cirrus","zone":"fi-hel2"}}' + headers: + Content-Length: + - "5415" + Content-Type: + - application/json; charset=UTF-8 + Date: + - Thu, 22 May 2025 12:36:54 GMT + Server: + - Apache + Strict-Transport-Security: + - max-age=63072000 + status: 202 Accepted + code: 202 + duration: 3.053835875s + - id: 1 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.upcloud.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + Authorization: + - Basic [REDACTED] + Content-Type: + - application/json + User-Agent: + - upcloud-go-api/8.18.0 + url: https://api.upcloud.com/1.3/server/00c51c63-a1fb-4bc3-9028-2238210b50bf + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 5321 + uncompressed: false + body: '{"server":{"boot_order":"disk","core_number":"1","created":1747917414,"firewall":"off","host":8556659331,"hostname":"uploud-go-sdk-integration-test-createrestartserver.example.com","ip_addresses":{"ip_address":[{"access":"utility","address":"10.6.12.219","family":"IPv4"},{"access":"public","address":"2a04:3545:1000:720:e0a4:b3ff:fe19:6dc7","family":"IPv6"},{"access":"public","address":"94.237.106.79","family":"IPv4"}]},"labels":{"label":[{"key":"managedBy","value":"upcloud-sdk-integration-test"},{"key":"testName","value":"createrestartserver"}]},"license":0,"memory_amount":"1024","metadata":"no","networking":{"interfaces":{"interface":[{"bootable":"no","index":1,"ip_addresses":{"ip_address":[{"address":"10.6.12.219","dhcp_provided":"yes","family":"IPv4","floating":"no","release_policy":"release"}]},"mac":"e2:a4:b3:19:bc:e8","network":"033e8ae3-3125-4506-aecb-d5bfc6c698d6","source_ip_filtering":"yes","type":"utility"},{"bootable":"no","index":2,"ip_addresses":{"ip_address":[{"address":"94.237.106.79","dhcp_provided":"yes","family":"IPv4","floating":"no","release_policy":"release"}]},"mac":"e2:a4:b3:19:7a:67","network":"031457f4-0f8c-483c-96f2-eccede02909c","source_ip_filtering":"yes","type":"public"},{"bootable":"no","index":3,"ip_addresses":{"ip_address":[{"address":"2a04:3545:1000:720:e0a4:b3ff:fe19:6dc7","dhcp_provided":"yes","family":"IPv6","floating":"no","release_policy":"release"}]},"mac":"e2:a4:b3:19:6d:c7","network":"03000000-0000-4000-8046-000000000000","source_ip_filtering":"yes","type":"public"}]}},"nic_model":"virtio","plan":"custom","plan_ipv4_bytes":"0","plan_ipv6_bytes":"0","remote_access_enabled":"no","remote_access_password":"m6Ufm7GV","remote_access_type":"vnc","server_group":null,"simple_backup":"no","state":"started","storage_devices":{"storage_device":[{"address":"virtio:0","boot_disk":"0","labels":[{"key":"_os_architecture","value":"64"},{"key":"_os_brand_name","value":"Debian"},{"key":"_os_brand_version","value":"11"},{"key":"_os_main_category","value":"Linux"},{"key":"_os_type","value":"debian"},{"key":"_template_uuid","value":"01000000-0000-4000-8000-000020060100"}],"storage":"016dc265-1fdd-48f8-878b-363852c9e30c","storage_encrypted":"no","storage_size":10,"storage_tier":"maxiops","storage_title":"disk1","type":"disk"}]},"tags":{"tag":[]},"timezone":"UTC","title":"uploud-go-sdk-integration-test-createrestartserver","uuid":"00c51c63-a1fb-4bc3-9028-2238210b50bf","video_model":"cirrus","zone":"fi-hel2"}}' + headers: + Content-Length: + - "5321" + Content-Type: + - application/json; charset=UTF-8 + Date: + - Thu, 22 May 2025 12:38:30 GMT + Server: + - Apache + Strict-Transport-Security: + - max-age=63072000 + status: 200 OK + code: 200 + duration: 58.808042ms + - id: 2 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 81 + transfer_encoding: [] + trailer: {} + host: api.upcloud.com + remote_addr: "" + request_uri: "" + body: '{"restart_server":{"stop_type":"hard","timeout":"900","timeout_action":"ignore"}}' + form: {} + headers: + Accept: + - application/json + Authorization: + - Basic [REDACTED] + Content-Type: + - application/json + User-Agent: + - upcloud-go-api/8.18.0 + url: https://api.upcloud.com/1.3/server/00c51c63-a1fb-4bc3-9028-2238210b50bf/restart + method: POST + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 5321 + uncompressed: false + body: '{"server":{"boot_order":"disk","core_number":"1","created":1747917414,"firewall":"off","host":8556659331,"hostname":"uploud-go-sdk-integration-test-createrestartserver.example.com","ip_addresses":{"ip_address":[{"access":"utility","address":"10.6.12.219","family":"IPv4"},{"access":"public","address":"2a04:3545:1000:720:e0a4:b3ff:fe19:6dc7","family":"IPv6"},{"access":"public","address":"94.237.106.79","family":"IPv4"}]},"labels":{"label":[{"key":"managedBy","value":"upcloud-sdk-integration-test"},{"key":"testName","value":"createrestartserver"}]},"license":0,"memory_amount":"1024","metadata":"no","networking":{"interfaces":{"interface":[{"bootable":"no","index":1,"ip_addresses":{"ip_address":[{"address":"10.6.12.219","dhcp_provided":"yes","family":"IPv4","floating":"no","release_policy":"release"}]},"mac":"e2:a4:b3:19:bc:e8","network":"033e8ae3-3125-4506-aecb-d5bfc6c698d6","source_ip_filtering":"yes","type":"utility"},{"bootable":"no","index":2,"ip_addresses":{"ip_address":[{"address":"94.237.106.79","dhcp_provided":"yes","family":"IPv4","floating":"no","release_policy":"release"}]},"mac":"e2:a4:b3:19:7a:67","network":"031457f4-0f8c-483c-96f2-eccede02909c","source_ip_filtering":"yes","type":"public"},{"bootable":"no","index":3,"ip_addresses":{"ip_address":[{"address":"2a04:3545:1000:720:e0a4:b3ff:fe19:6dc7","dhcp_provided":"yes","family":"IPv6","floating":"no","release_policy":"release"}]},"mac":"e2:a4:b3:19:6d:c7","network":"03000000-0000-4000-8046-000000000000","source_ip_filtering":"yes","type":"public"}]}},"nic_model":"virtio","plan":"custom","plan_ipv4_bytes":"0","plan_ipv6_bytes":"0","remote_access_enabled":"no","remote_access_password":"m6Ufm7GV","remote_access_type":"vnc","server_group":null,"simple_backup":"no","state":"started","storage_devices":{"storage_device":[{"address":"virtio:0","boot_disk":"0","labels":[{"key":"_os_architecture","value":"64"},{"key":"_os_brand_name","value":"Debian"},{"key":"_os_brand_version","value":"11"},{"key":"_os_main_category","value":"Linux"},{"key":"_os_type","value":"debian"},{"key":"_template_uuid","value":"01000000-0000-4000-8000-000020060100"}],"storage":"016dc265-1fdd-48f8-878b-363852c9e30c","storage_encrypted":"no","storage_size":10,"storage_tier":"maxiops","storage_title":"disk1","type":"disk"}]},"tags":{"tag":[]},"timezone":"UTC","title":"uploud-go-sdk-integration-test-createrestartserver","uuid":"00c51c63-a1fb-4bc3-9028-2238210b50bf","video_model":"cirrus","zone":"fi-hel2"}}' + headers: + Content-Length: + - "5321" + Content-Type: + - application/json; charset=UTF-8 + Date: + - Thu, 22 May 2025 12:38:31 GMT + Server: + - Apache + Strict-Transport-Security: + - max-age=63072000 + status: 200 OK + code: 200 + duration: 69.462792ms + - id: 3 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.upcloud.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + Authorization: + - Basic [REDACTED] + Content-Type: + - application/json + User-Agent: + - upcloud-go-api/8.18.0 + url: https://api.upcloud.com/1.3/server/00c51c63-a1fb-4bc3-9028-2238210b50bf + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 5325 + uncompressed: false + body: '{"server":{"boot_order":"disk","core_number":"1","created":1747917414,"firewall":"off","host":8556659331,"hostname":"uploud-go-sdk-integration-test-createrestartserver.example.com","ip_addresses":{"ip_address":[{"access":"utility","address":"10.6.12.219","family":"IPv4"},{"access":"public","address":"2a04:3545:1000:720:e0a4:b3ff:fe19:6dc7","family":"IPv6"},{"access":"public","address":"94.237.106.79","family":"IPv4"}]},"labels":{"label":[{"key":"managedBy","value":"upcloud-sdk-integration-test"},{"key":"testName","value":"createrestartserver"}]},"license":0,"memory_amount":"1024","metadata":"no","networking":{"interfaces":{"interface":[{"bootable":"no","index":1,"ip_addresses":{"ip_address":[{"address":"10.6.12.219","dhcp_provided":"yes","family":"IPv4","floating":"no","release_policy":"release"}]},"mac":"e2:a4:b3:19:bc:e8","network":"033e8ae3-3125-4506-aecb-d5bfc6c698d6","source_ip_filtering":"yes","type":"utility"},{"bootable":"no","index":2,"ip_addresses":{"ip_address":[{"address":"94.237.106.79","dhcp_provided":"yes","family":"IPv4","floating":"no","release_policy":"release"}]},"mac":"e2:a4:b3:19:7a:67","network":"031457f4-0f8c-483c-96f2-eccede02909c","source_ip_filtering":"yes","type":"public"},{"bootable":"no","index":3,"ip_addresses":{"ip_address":[{"address":"2a04:3545:1000:720:e0a4:b3ff:fe19:6dc7","dhcp_provided":"yes","family":"IPv6","floating":"no","release_policy":"release"}]},"mac":"e2:a4:b3:19:6d:c7","network":"03000000-0000-4000-8046-000000000000","source_ip_filtering":"yes","type":"public"}]}},"nic_model":"virtio","plan":"custom","plan_ipv4_bytes":"0","plan_ipv6_bytes":"0","remote_access_enabled":"no","remote_access_password":"m6Ufm7GV","remote_access_type":"vnc","server_group":null,"simple_backup":"no","state":"maintenance","storage_devices":{"storage_device":[{"address":"virtio:0","boot_disk":"0","labels":[{"key":"_os_architecture","value":"64"},{"key":"_os_brand_name","value":"Debian"},{"key":"_os_brand_version","value":"11"},{"key":"_os_main_category","value":"Linux"},{"key":"_os_type","value":"debian"},{"key":"_template_uuid","value":"01000000-0000-4000-8000-000020060100"}],"storage":"016dc265-1fdd-48f8-878b-363852c9e30c","storage_encrypted":"no","storage_size":10,"storage_tier":"maxiops","storage_title":"disk1","type":"disk"}]},"tags":{"tag":[]},"timezone":"UTC","title":"uploud-go-sdk-integration-test-createrestartserver","uuid":"00c51c63-a1fb-4bc3-9028-2238210b50bf","video_model":"cirrus","zone":"fi-hel2"}}' + headers: + Content-Length: + - "5325" + Content-Type: + - application/json; charset=UTF-8 + Date: + - Thu, 22 May 2025 12:38:36 GMT + Server: + - Apache + Strict-Transport-Security: + - max-age=63072000 + status: 200 OK + code: 200 + duration: 52.469042ms + - id: 4 + request: + proto: HTTP/1.1 + proto_major: 1 + proto_minor: 1 + content_length: 0 + transfer_encoding: [] + trailer: {} + host: api.upcloud.com + remote_addr: "" + request_uri: "" + body: "" + form: {} + headers: + Accept: + - application/json + Authorization: + - Basic [REDACTED] + Content-Type: + - application/json + User-Agent: + - upcloud-go-api/8.18.0 + url: https://api.upcloud.com/1.3/server/00c51c63-a1fb-4bc3-9028-2238210b50bf + method: GET + response: + proto: HTTP/2.0 + proto_major: 2 + proto_minor: 0 + transfer_encoding: [] + trailer: {} + content_length: 5321 + uncompressed: false + body: '{"server":{"boot_order":"disk","core_number":"1","created":1747917414,"firewall":"off","host":8556659331,"hostname":"uploud-go-sdk-integration-test-createrestartserver.example.com","ip_addresses":{"ip_address":[{"access":"utility","address":"10.6.12.219","family":"IPv4"},{"access":"public","address":"2a04:3545:1000:720:e0a4:b3ff:fe19:6dc7","family":"IPv6"},{"access":"public","address":"94.237.106.79","family":"IPv4"}]},"labels":{"label":[{"key":"managedBy","value":"upcloud-sdk-integration-test"},{"key":"testName","value":"createrestartserver"}]},"license":0,"memory_amount":"1024","metadata":"no","networking":{"interfaces":{"interface":[{"bootable":"no","index":1,"ip_addresses":{"ip_address":[{"address":"10.6.12.219","dhcp_provided":"yes","family":"IPv4","floating":"no","release_policy":"release"}]},"mac":"e2:a4:b3:19:bc:e8","network":"033e8ae3-3125-4506-aecb-d5bfc6c698d6","source_ip_filtering":"yes","type":"utility"},{"bootable":"no","index":2,"ip_addresses":{"ip_address":[{"address":"94.237.106.79","dhcp_provided":"yes","family":"IPv4","floating":"no","release_policy":"release"}]},"mac":"e2:a4:b3:19:7a:67","network":"031457f4-0f8c-483c-96f2-eccede02909c","source_ip_filtering":"yes","type":"public"},{"bootable":"no","index":3,"ip_addresses":{"ip_address":[{"address":"2a04:3545:1000:720:e0a4:b3ff:fe19:6dc7","dhcp_provided":"yes","family":"IPv6","floating":"no","release_policy":"release"}]},"mac":"e2:a4:b3:19:6d:c7","network":"03000000-0000-4000-8046-000000000000","source_ip_filtering":"yes","type":"public"}]}},"nic_model":"virtio","plan":"custom","plan_ipv4_bytes":"0","plan_ipv6_bytes":"0","remote_access_enabled":"no","remote_access_password":"m6Ufm7GV","remote_access_type":"vnc","server_group":null,"simple_backup":"no","state":"started","storage_devices":{"storage_device":[{"address":"virtio:0","boot_disk":"0","labels":[{"key":"_os_architecture","value":"64"},{"key":"_os_brand_name","value":"Debian"},{"key":"_os_brand_version","value":"11"},{"key":"_os_main_category","value":"Linux"},{"key":"_os_type","value":"debian"},{"key":"_template_uuid","value":"01000000-0000-4000-8000-000020060100"}],"storage":"016dc265-1fdd-48f8-878b-363852c9e30c","storage_encrypted":"no","storage_size":10,"storage_tier":"maxiops","storage_title":"disk1","type":"disk"}]},"tags":{"tag":[]},"timezone":"UTC","title":"uploud-go-sdk-integration-test-createrestartserver","uuid":"00c51c63-a1fb-4bc3-9028-2238210b50bf","video_model":"cirrus","zone":"fi-hel2"}}' + headers: + Content-Length: + - "5321" + Content-Type: + - application/json; charset=UTF-8 + Date: + - Thu, 22 May 2025 12:39:51 GMT + Server: + - Apache + Strict-Transport-Security: + - max-age=63072000 + status: 200 OK + code: 200 + duration: 50.21075ms diff --git a/upcloud/service/gateway_test.go b/upcloud/service/gateway_test.go index c166ecce..04a2fba9 100644 --- a/upcloud/service/gateway_test.go +++ b/upcloud/service/gateway_test.go @@ -12,8 +12,8 @@ import ( "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/client" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/request" - "github.com/dnaeon/go-vcr/recorder" "github.com/stretchr/testify/assert" + "gopkg.in/dnaeon/go-vcr.v4/pkg/recorder" ) func TestGatewayPlans(t *testing.T) { @@ -653,19 +653,12 @@ func TestVPNGatewayConnectionTunnels(t *testing.T) { } func waitGatewayToStart(ctx context.Context, rec *recorder.Recorder, svc *Service, UUID string) error { - if rec.Mode() != recorder.ModeRecording { + if !rec.IsRecording() { return nil } const timeout = 10 * time.Minute - rec.AddPassthrough(func(h *http.Request) bool { - return true - }) - defer func() { - rec.Passthroughs = nil - }() - waitUntil := time.Now().Add(timeout) for { gw, err := svc.GetGateway(ctx, &request.GetGatewayRequest{UUID: UUID}) @@ -683,19 +676,12 @@ func waitGatewayToStart(ctx context.Context, rec *recorder.Recorder, svc *Servic } func waitGatewayToDelete(ctx context.Context, rec *recorder.Recorder, svc *Service, UUID string) error { - if rec.Mode() != recorder.ModeRecording { + if !rec.IsRecording() { return nil } const timeout = 10 * time.Minute - rec.AddPassthrough(func(h *http.Request) bool { - return true - }) - defer func() { - rec.Passthroughs = nil - }() - waitUntil := time.Now().Add(timeout) for { _, err := svc.GetGateway(ctx, &request.GetGatewayRequest{UUID: UUID}) diff --git a/upcloud/service/hosts_test.go b/upcloud/service/hosts_test.go index 7ece2e9e..eed35faa 100644 --- a/upcloud/service/hosts_test.go +++ b/upcloud/service/hosts_test.go @@ -4,9 +4,9 @@ import ( "context" "testing" - "github.com/dnaeon/go-vcr/recorder" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/dnaeon/go-vcr.v4/pkg/recorder" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/request" ) diff --git a/upcloud/service/ip_address_test.go b/upcloud/service/ip_address_test.go index d2db5f40..3305f98c 100644 --- a/upcloud/service/ip_address_test.go +++ b/upcloud/service/ip_address_test.go @@ -6,9 +6,9 @@ import ( "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/request" - "github.com/dnaeon/go-vcr/recorder" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/dnaeon/go-vcr.v4/pkg/recorder" ) // TestGetIPAddresses performs the following actions: diff --git a/upcloud/service/kubernetes.go b/upcloud/service/kubernetes.go index ea214671..75ad3995 100644 --- a/upcloud/service/kubernetes.go +++ b/upcloud/service/kubernetes.go @@ -90,7 +90,7 @@ func (s *Service) DeleteKubernetesCluster(ctx context.Context, r *request.Delete // specified state. If the state changes favorably, cluster details are returned. The method will give up // after the specified timeout func (s *Service) WaitForKubernetesClusterState(ctx context.Context, r *request.WaitForKubernetesClusterStateRequest) (*upcloud.KubernetesCluster, error) { - return retry(ctx, func(i int, c context.Context) (*upcloud.KubernetesCluster, error) { + return wait(ctx, func(i int, c context.Context) (*upcloud.KubernetesCluster, error) { details, err := s.GetKubernetesCluster(c, &request.GetKubernetesClusterRequest{ UUID: r.UUID, }) @@ -115,7 +115,7 @@ func (s *Service) WaitForKubernetesClusterState(ctx context.Context, r *request. // specified state. If the state changes favorably, node group is returned. The method will give up // after the specified timeout func (s *Service) WaitForKubernetesNodeGroupState(ctx context.Context, r *request.WaitForKubernetesNodeGroupStateRequest) (*upcloud.KubernetesNodeGroup, error) { - return retry(ctx, func(i int, c context.Context) (*upcloud.KubernetesNodeGroup, error) { + return wait(ctx, func(i int, c context.Context) (*upcloud.KubernetesNodeGroup, error) { ng, err := s.GetKubernetesNodeGroup(c, &request.GetKubernetesNodeGroupRequest{ ClusterUUID: r.ClusterUUID, Name: r.Name, diff --git a/upcloud/service/load_balancer.go b/upcloud/service/load_balancer.go index 4c87ad5d..f1f06645 100644 --- a/upcloud/service/load_balancer.go +++ b/upcloud/service/load_balancer.go @@ -2,6 +2,8 @@ package service import ( "context" + "errors" + "net/http" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/request" @@ -13,6 +15,8 @@ type LoadBalancer interface { CreateLoadBalancer(ctx context.Context, r *request.CreateLoadBalancerRequest) (*upcloud.LoadBalancer, error) ModifyLoadBalancer(ctx context.Context, r *request.ModifyLoadBalancerRequest) (*upcloud.LoadBalancer, error) DeleteLoadBalancer(ctx context.Context, r *request.DeleteLoadBalancerRequest) error + WaitForLoadBalancerState(ctx context.Context, r *request.WaitForLoadBalancerStateRequest) (*upcloud.LoadBalancer, error) + WaitForLoadBalancerDeletion(ctx context.Context, r *request.WaitForLoadBalancerDeletionRequest) error // Backends GetLoadBalancerBackends(ctx context.Context, r *request.GetLoadBalancerBackendsRequest) ([]upcloud.LoadBalancerBackend, error) GetLoadBalancerBackend(ctx context.Context, r *request.GetLoadBalancerBackendRequest) (*upcloud.LoadBalancerBackend, error) @@ -428,3 +432,41 @@ func (s *Service) GetLoadBalancerDNSChallengeDomain(ctx context.Context, r *requ var domain upcloud.LoadBalancerDNSChallengeDomain return &domain, s.get(ctx, r.RequestURL(), &domain) } + +func (s *Service) WaitForLoadBalancerState(ctx context.Context, r *request.WaitForLoadBalancerStateRequest) (*upcloud.LoadBalancer, error) { + return wait(ctx, func(i int, c context.Context) (*upcloud.LoadBalancer, error) { + details, err := s.GetLoadBalancer(c, &request.GetLoadBalancerRequest{ + UUID: r.UUID, + }) + if err != nil { + return nil, err + } + + // Either wait for the server to enter the desired state or wait for it to leave the undesired state + if r.DesiredState != "" && details.OperationalState == r.DesiredState { + return details, nil + } + + return nil, nil + }, nil) +} + +// WaitForManagedObjectStorageDeletion blocks execution until the specified Managed Object Storage service has been deleted. +func (s *Service) WaitForLoadBalancerDeletion(ctx context.Context, r *request.WaitForLoadBalancerDeletionRequest) error { + _, err := wait(ctx, func(_ int, c context.Context) (*upcloud.LoadBalancer, error) { + details, err := s.GetLoadBalancer(c, &request.GetLoadBalancerRequest{ + UUID: r.UUID, + }) + if err != nil { + var ucErr *upcloud.Problem + if errors.As(err, &ucErr) && ucErr.Status == http.StatusNotFound { + return nil, nil + } + + return nil, err + } + + return details, err + }, &waitConfig{inverse: true}) + return err +} diff --git a/upcloud/service/load_balancer_test.go b/upcloud/service/load_balancer_test.go index 4dd0f665..2d08aa63 100644 --- a/upcloud/service/load_balancer_test.go +++ b/upcloud/service/load_balancer_test.go @@ -2,17 +2,15 @@ package service import ( "context" - "errors" "fmt" - "net/http" "testing" "time" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/request" - "github.com/dnaeon/go-vcr/recorder" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/dnaeon/go-vcr.v4/pkg/recorder" ) func TestLoadBalancer(t *testing.T) { @@ -1043,7 +1041,7 @@ func TestLoadBalancerPage(t *testing.T) { lb, err := createLoadBalancer(ctx, svc, net.UUID, zone) require.NoError(t, err) lbs = append(lbs, lb) - if rec.Mode() != recorder.ModeReplaying { + if rec.IsRecording() { time.Sleep(1 * time.Second) } } @@ -1080,9 +1078,13 @@ func TestLoadBalancerPage(t *testing.T) { } } for _, lb := range lbs { - if err := waitForLoadBalancerToShutdown(ctx, rec, svc, lb); err != nil { - t.Log(err) - continue + if rec.IsRecording() { + if err := svc.WaitForLoadBalancerDeletion(ctx, &request.WaitForLoadBalancerDeletionRequest{ + UUID: lb.UUID, + }); err != nil { + t.Log(err) + continue + } } } if err := svc.DeleteNetwork(ctx, &request.DeleteNetworkRequest{UUID: net.UUID}); err != nil { @@ -1174,7 +1176,11 @@ func TestLoadBalancerNetwork(t *testing.T) { assert.Len(t, fe.Networks, 1) t.Logf("Waiting LB %s to become online", lb.Name) - if err = waitLoadBalancerOnline(ctx, t, rec, svc, lb.UUID); err != nil { + _, err = svc.WaitForLoadBalancerState(ctx, &request.WaitForLoadBalancerStateRequest{ + UUID: lb.UUID, + DesiredState: upcloud.LoadBalancerOperationalStateRunning, + }) + if err != nil { t.Error(err) return } @@ -1289,29 +1295,6 @@ func TestDNSChallengeDomain(t *testing.T) { }) } -func waitLoadBalancerOnline(ctx context.Context, t *testing.T, rec *recorder.Recorder, svc *Service, UUID string) error { - t.Helper() - if rec.Mode() == recorder.ModeRecording { - rec.AddPassthrough(func(h *http.Request) bool { - return true - }) - defer func() { - rec.Passthroughs = nil - }() - for s := time.Now(); time.Since(s) < time.Minute*20; { - lb, err := svc.GetLoadBalancer(ctx, &request.GetLoadBalancerRequest{UUID: UUID}) - if err != nil { - return err - } - if lb.OperationalState == upcloud.LoadBalancerOperationalStateRunning { - return nil - } - time.Sleep(time.Second * 2) - } - } - return nil -} - func createLoadBalancerBackend(ctx context.Context, svc *Service, lbUUID string) (*upcloud.LoadBalancerBackend, error) { req := request.CreateLoadBalancerBackendRequest{ ServiceUUID: lbUUID, @@ -1342,11 +1325,39 @@ func createLoadBalancerBackend(ctx context.Context, svc *Service, lbUUID string) } func cleanupLoadBalancer(ctx context.Context, rec *recorder.Recorder, svc *Service, lb *upcloud.LoadBalancer) error { - if rec.Mode() != recorder.ModeRecording { + if !rec.IsRecording() { return nil } - err := deleteLoadBalancer(ctx, svc, lb) - return err + + if err := svc.DeleteLoadBalancer(ctx, &request.DeleteLoadBalancerRequest{UUID: lb.UUID}); err != nil { + return err + } + + if err := svc.WaitForLoadBalancerDeletion(ctx, &request.WaitForLoadBalancerDeletionRequest{ + UUID: lb.UUID, + }); err != nil { + return fmt.Errorf("unable to shutdown LB '%s' (%s) (check dangling networks)", lb.UUID, lb.Name) + } + + var errs []error + if lb.NetworkUUID != "" { + if err := svc.DeleteNetwork(ctx, &request.DeleteNetworkRequest{UUID: lb.NetworkUUID}); err != nil { + errs = append(errs, err) + } + } + if len(lb.Networks) > 0 { + for _, n := range lb.Networks { + if n.Type == upcloud.LoadBalancerNetworkTypePrivate && n.UUID != "" && lb.NetworkUUID != n.UUID { + if err := svc.DeleteNetwork(ctx, &request.DeleteNetworkRequest{UUID: n.UUID}); err != nil { + errs = append(errs, err) + } + } + } + } + if len(errs) > 0 { + return fmt.Errorf("%s", errs) + } + return nil } func createLoadBalancer(ctx context.Context, svc *Service, networkUUID, zone string, label ...upcloud.Label) (*upcloud.LoadBalancer, error) { @@ -1395,70 +1406,6 @@ func createLoadBalancerAndPrivateNetwork(ctx context.Context, svc *Service, zone }) } -func waitForLoadBalancerToShutdown(ctx context.Context, rec *recorder.Recorder, svc *Service, lb *upcloud.LoadBalancer) error { - if rec.Mode() != recorder.ModeRecording { - return nil - } - - const maxRetries int = 100 - // wait delete request - for i := 0; i <= maxRetries; i++ { - _, err := svc.GetLoadBalancer(ctx, &request.GetLoadBalancerRequest{UUID: lb.UUID}) - if err != nil { - if svcErr, ok := err.(*upcloud.Problem); ok && svcErr.Status == http.StatusNotFound { - return nil - } - } - time.Sleep(2 * time.Second) - } - return errors.New("max retries reached while waiting for load balancer instance to shutdown") -} - -func waitLoadBalancerToShutdown(ctx context.Context, svc *Service, lb *upcloud.LoadBalancer) error { - const maxRetries int = 100 - // wait delete request - for i := 0; i <= maxRetries; i++ { - _, err := svc.GetLoadBalancer(ctx, &request.GetLoadBalancerRequest{UUID: lb.UUID}) - if err != nil { - if svcErr, ok := err.(*upcloud.Problem); ok && svcErr.Status == http.StatusNotFound { - return nil - } - } - time.Sleep(2 * time.Second) - } - return errors.New("max retries reached while waiting for load balancer instance to shutdown") -} - -func deleteLoadBalancer(ctx context.Context, svc *Service, lb *upcloud.LoadBalancer) error { - if err := svc.DeleteLoadBalancer(ctx, &request.DeleteLoadBalancerRequest{UUID: lb.UUID}); err != nil { - return err - } - - if err := waitLoadBalancerToShutdown(ctx, svc, lb); err != nil { - return fmt.Errorf("unable to shutdown LB '%s' (%s) (check dangling networks)", lb.UUID, lb.Name) - } - - var errs []error - if lb.NetworkUUID != "" { - if err := svc.DeleteNetwork(ctx, &request.DeleteNetworkRequest{UUID: lb.NetworkUUID}); err != nil { - errs = append(errs, err) - } - } - if len(lb.Networks) > 0 { - for _, n := range lb.Networks { - if n.Type == upcloud.LoadBalancerNetworkTypePrivate && n.UUID != "" && lb.NetworkUUID != n.UUID { - if err := svc.DeleteNetwork(ctx, &request.DeleteNetworkRequest{UUID: n.UUID}); err != nil { - errs = append(errs, err) - } - } - } - } - if len(errs) > 0 { - return fmt.Errorf("%s", errs) - } - return nil -} - func createLoadBalancerPrivateNetwork(ctx context.Context, svc *Service, zone, addr string) (*upcloud.Network, error) { return svc.CreateNetwork(ctx, &request.CreateNetworkRequest{ Name: fmt.Sprintf("go-api-lb-%d", time.Now().Unix()), diff --git a/upcloud/service/managed_database.go b/upcloud/service/managed_database.go index 8f1c0a82..d07cd3c0 100644 --- a/upcloud/service/managed_database.go +++ b/upcloud/service/managed_database.go @@ -169,7 +169,7 @@ func (s *Service) GetManagedDatabaseVersions(ctx context.Context, r *request.Get // specified state. If the state changes favorably, the new managed database details is returned. The method will give up // after the specified timeout func (s *Service) WaitForManagedDatabaseState(ctx context.Context, r *request.WaitForManagedDatabaseStateRequest) (*upcloud.ManagedDatabase, error) { - return retry(ctx, func(_ int, c context.Context) (*upcloud.ManagedDatabase, error) { + return wait(ctx, func(_ int, c context.Context) (*upcloud.ManagedDatabase, error) { details, err := s.GetManagedDatabase(c, &request.GetManagedDatabaseRequest{ UUID: r.UUID, }) diff --git a/upcloud/service/managed_database_test.go b/upcloud/service/managed_database_test.go index 97916a52..dfecfbde 100644 --- a/upcloud/service/managed_database_test.go +++ b/upcloud/service/managed_database_test.go @@ -3,13 +3,12 @@ package service import ( "context" "fmt" - "net/http" "testing" "time" - "github.com/dnaeon/go-vcr/recorder" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/dnaeon/go-vcr.v4/pkg/recorder" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/request" @@ -220,7 +219,7 @@ func TestService_GetManagedDatabaseLogs(t *testing.T) { err = waitForManagedDatabaseRunningState(ctx, rec, svc, details.UUID) require.NoError(t, err) - if rec.Mode() == recorder.ModeRecording { + if rec.IsRecording() { t.Logf("waiting for %s for the logs to be available", waitFor) time.Sleep(waitFor) } @@ -332,7 +331,7 @@ func TestService_GetManagedDatabaseMetrics(t *testing.T) { err = waitForManagedDatabaseRunningState(ctx, rec, svc, details.UUID) require.NoError(t, err) - if rec.Mode() == recorder.ModeRecording { + if rec.IsRecording() { t.Logf("waiting for %s to gather up some data", waitFor) time.Sleep(waitFor) } @@ -975,20 +974,12 @@ func TestService_GetManagedDatabaseIndices(t *testing.T) { } func waitForManagedDatabaseInitialBackup(ctx context.Context, rec *recorder.Recorder, svc *Service, dbUUID string) error { - if rec.Mode() != recorder.ModeRecording { + if !rec.IsRecording() { return nil } const timeout = 10 * time.Minute - rec.AddPassthrough(func(h *http.Request) bool { - return true - }) - - defer func() { - rec.Passthroughs = nil - }() - waitUntil := time.Now().Add(timeout) for { waitForBackupDetails, err := svc.GetManagedDatabase(ctx, &request.GetManagedDatabaseRequest{UUID: dbUUID}) @@ -1009,17 +1000,10 @@ func waitForManagedDatabaseInitialBackup(ctx context.Context, rec *recorder.Reco } func waitForManagedDatabaseRunningState(ctx context.Context, rec *recorder.Recorder, svc *Service, dbUUID string) error { - if rec.Mode() != recorder.ModeRecording { + if !rec.IsRecording() { return nil } - rec.AddPassthrough(func(h *http.Request) bool { - return true - }) - defer func() { - rec.Passthroughs = nil - }() - _, err := svc.WaitForManagedDatabaseState(ctx, &request.WaitForManagedDatabaseStateRequest{ UUID: dbUUID, DesiredState: upcloud.ManagedDatabaseStateRunning, diff --git a/upcloud/service/managed_object_storage.go b/upcloud/service/managed_object_storage.go index d3bb5aca..0e297037 100644 --- a/upcloud/service/managed_object_storage.go +++ b/upcloud/service/managed_object_storage.go @@ -231,7 +231,7 @@ func (s *Service) DeleteManagedObjectStorageCustomDomain(ctx context.Context, r // has entered the specified state. If the state changes favorably, service details is returned. The method will give up // after the specified timeout func (s *Service) WaitForManagedObjectStorageOperationalState(ctx context.Context, r *request.WaitForManagedObjectStorageOperationalStateRequest) (*upcloud.ManagedObjectStorage, error) { - return retry(ctx, func(_ int, c context.Context) (*upcloud.ManagedObjectStorage, error) { + return wait(ctx, func(_ int, c context.Context) (*upcloud.ManagedObjectStorage, error) { details, err := s.GetManagedObjectStorage(c, &request.GetManagedObjectStorageRequest{ UUID: r.UUID, }) @@ -248,7 +248,7 @@ func (s *Service) WaitForManagedObjectStorageOperationalState(ctx context.Contex // WaitForManagedObjectStorageDeletion blocks execution until the specified Managed Object Storage service has been deleted. func (s *Service) WaitForManagedObjectStorageDeletion(ctx context.Context, r *request.WaitForManagedObjectStorageDeletionRequest) error { - _, err := retry(ctx, func(_ int, c context.Context) (*upcloud.ManagedObjectStorage, error) { + _, err := wait(ctx, func(_ int, c context.Context) (*upcloud.ManagedObjectStorage, error) { details, err := s.GetManagedObjectStorage(c, &request.GetManagedObjectStorageRequest{ UUID: r.UUID, }) @@ -262,13 +262,13 @@ func (s *Service) WaitForManagedObjectStorageDeletion(ctx context.Context, r *re } return details, err - }, &retryConfig{inverse: true}) + }, &waitConfig{inverse: true}) return err } // WaitForManagedObjectStorageBucketDeletion blocks execution until the specified Managed Object Storage bucket has been deleted. func (s *Service) WaitForManagedObjectStorageBucketDeletion(ctx context.Context, r *request.WaitForManagedObjectStorageBucketDeletionRequest) error { - _, err := retry(ctx, func(_ int, c context.Context) (*upcloud.ManagedObjectStorageBucketMetrics, error) { + _, err := wait(ctx, func(_ int, c context.Context) (*upcloud.ManagedObjectStorageBucketMetrics, error) { buckets, err := s.GetManagedObjectStorageBucketMetrics(c, &request.GetManagedObjectStorageBucketMetricsRequest{ ServiceUUID: r.ServiceUUID, }) @@ -287,6 +287,6 @@ func (s *Service) WaitForManagedObjectStorageBucketDeletion(ctx context.Context, } } return nil, err - }, &retryConfig{inverse: true}) + }, &waitConfig{inverse: true}) return err } diff --git a/upcloud/service/managed_object_storage_test.go b/upcloud/service/managed_object_storage_test.go index 49518c42..41e4cc92 100644 --- a/upcloud/service/managed_object_storage_test.go +++ b/upcloud/service/managed_object_storage_test.go @@ -6,9 +6,9 @@ import ( "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/request" - "github.com/dnaeon/go-vcr/recorder" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/dnaeon/go-vcr.v4/pkg/recorder" ) func TestGetManagedObjectStorageRegions(t *testing.T) { diff --git a/upcloud/service/network_peering.go b/upcloud/service/network_peering.go index aac18bf7..fbf8cd2c 100644 --- a/upcloud/service/network_peering.go +++ b/upcloud/service/network_peering.go @@ -47,7 +47,7 @@ func (s *Service) DeleteNetworkPeering(ctx context.Context, r *request.DeleteNet } func (s *Service) WaitForNetworkPeeringState(ctx context.Context, r *request.WaitForNetworkPeeringStateRequest) (*upcloud.NetworkPeering, error) { - return retry(ctx, func(_ int, c context.Context) (*upcloud.NetworkPeering, error) { + return wait(ctx, func(_ int, c context.Context) (*upcloud.NetworkPeering, error) { details, err := s.GetNetworkPeering(c, &request.GetNetworkPeeringRequest{ UUID: r.UUID, }) diff --git a/upcloud/service/network_test.go b/upcloud/service/network_test.go index 971cd2a0..ebd81682 100644 --- a/upcloud/service/network_test.go +++ b/upcloud/service/network_test.go @@ -8,9 +8,9 @@ import ( "time" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/client" - "github.com/dnaeon/go-vcr/recorder" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/dnaeon/go-vcr.v4/pkg/recorder" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/request" @@ -656,21 +656,14 @@ func TestCreateTwoNetworksTwoServersAndARouter(t *testing.T) { err = svc.DetachNetworkRouter(ctx, &request.DetachNetworkRouterRequest{NetworkUUID: network1.UUID}) require.NoError(t, err) - if rec.Mode() == recorder.ModeRecording { - rec.AddPassthrough(func(h *http.Request) bool { - return true + assert.Eventually(t, func() bool { + details, err := svc.GetNetworkDetails(ctx, &request.GetNetworkDetailsRequest{ + UUID: network1.UUID, }) + require.NoError(t, err) + return err == nil && details.Router == "" + }, 15*time.Second, time.Second) - assert.Eventually(t, func() bool { - details, err := svc.GetNetworkDetails(ctx, &request.GetNetworkDetailsRequest{ - UUID: network1.UUID, - }) - require.NoError(t, err) - return err == nil && details.Router == "" - }, 15*time.Second, time.Second) - - rec.Passthroughs = nil - } details, err := svc.GetNetworkDetails(ctx, &request.GetNetworkDetailsRequest{ UUID: network1.UUID, }) diff --git a/upcloud/service/object_storage_test.go b/upcloud/service/object_storage_test.go index 7525c201..e9fa2ad4 100644 --- a/upcloud/service/object_storage_test.go +++ b/upcloud/service/object_storage_test.go @@ -5,9 +5,9 @@ import ( "strings" "testing" - "github.com/dnaeon/go-vcr/recorder" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/dnaeon/go-vcr.v4/pkg/recorder" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/request" diff --git a/upcloud/service/partner_test.go b/upcloud/service/partner_test.go index 7f81c20e..22b1485e 100644 --- a/upcloud/service/partner_test.go +++ b/upcloud/service/partner_test.go @@ -6,9 +6,9 @@ import ( "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/request" - "github.com/dnaeon/go-vcr/recorder" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/dnaeon/go-vcr.v4/pkg/recorder" ) func TestCreatePartnerAccount(t *testing.T) { diff --git a/upcloud/service/permission_test.go b/upcloud/service/permission_test.go index f88d3a07..3aadd707 100644 --- a/upcloud/service/permission_test.go +++ b/upcloud/service/permission_test.go @@ -6,8 +6,8 @@ import ( "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/request" - "github.com/dnaeon/go-vcr/recorder" "github.com/stretchr/testify/assert" + "gopkg.in/dnaeon/go-vcr.v4/pkg/recorder" ) func TestPermissions(t *testing.T) { diff --git a/upcloud/service/retry.go b/upcloud/service/retry.go index 385f36e5..cd2f7541 100644 --- a/upcloud/service/retry.go +++ b/upcloud/service/retry.go @@ -2,18 +2,21 @@ package service import ( "context" + "fmt" "time" + + "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/client" ) -type retryConfig struct { +type waitConfig struct { interval time.Duration // Inverse the should retry logic. By default, operation is retried until operation returns a value. If inverse is set to true, operation is retried while operation returns a value. This should be used, for example, for waiting until resource is deleted. inverse bool } -func fillDefaults(c *retryConfig) *retryConfig { +func fillDefaults(c *waitConfig) *waitConfig { if c == nil { - c = &retryConfig{} + c = &waitConfig{} } if c.interval.Milliseconds() == 0 { @@ -23,7 +26,11 @@ func fillDefaults(c *retryConfig) *retryConfig { return c } -func retry[T any](ctx context.Context, operation func(int, context.Context) (*T, error), config *retryConfig) (*T, error) { +func withWaitFragment(ctx context.Context, i int) context.Context { + return client.WithFragment(ctx, fmt.Sprintf("wait:i=%d", i)) +} + +func wait[T any](ctx context.Context, operation func(int, context.Context) (*T, error), config *waitConfig) (*T, error) { config = fillDefaults(config) ticker := time.NewTicker(config.interval) @@ -32,7 +39,8 @@ func retry[T any](ctx context.Context, operation func(int, context.Context) (*T, for i := 0; ; i++ { select { case <-ticker.C: - value, err := operation(i, ctx) + c := withWaitFragment(ctx, i) + value, err := operation(i, c) if err != nil { return value, err } diff --git a/upcloud/service/retry_test.go b/upcloud/service/retry_test.go index dd61c5cb..9519d1a4 100644 --- a/upcloud/service/retry_test.go +++ b/upcloud/service/retry_test.go @@ -12,14 +12,14 @@ func TestRetry_noInverse(t *testing.T) { t.Parallel() ctx := context.TODO() - value, err := retry(ctx, func(i int, _ context.Context) (*string, error) { + value, err := wait(ctx, func(i int, _ context.Context) (*string, error) { if i < 3 { return nil, nil } value := "ready" return &value, nil - }, &retryConfig{interval: time.Millisecond * 125}) + }, &waitConfig{interval: time.Millisecond * 125}) assert.NoError(t, err) assert.Equal(t, "ready", *value) @@ -31,9 +31,9 @@ func TestRetry_timeout(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) defer cancel() - value, err := retry(ctx, func(i int, _ context.Context) (*string, error) { + value, err := wait(ctx, func(i int, _ context.Context) (*string, error) { return nil, nil - }, &retryConfig{interval: time.Millisecond * 125}) + }, &waitConfig{interval: time.Millisecond * 125}) assert.Error(t, err) assert.Nil(t, value) @@ -43,12 +43,12 @@ func TestRetry_inverse(t *testing.T) { t.Parallel() ctx := context.TODO() - value, err := retry(ctx, func(i int, _ context.Context) (*int, error) { + value, err := wait(ctx, func(i int, _ context.Context) (*int, error) { if i < 3 { return &i, nil } return nil, nil - }, &retryConfig{interval: time.Millisecond * 125, inverse: true}) + }, &waitConfig{interval: time.Millisecond * 125, inverse: true}) assert.NoError(t, err) assert.Nil(t, value) @@ -59,9 +59,9 @@ func TestRetry_noInterval(t *testing.T) { tests := []struct { name string - config *retryConfig + config *waitConfig }{ - {name: "no interval", config: &retryConfig{inverse: false}}, + {name: "no interval", config: &waitConfig{inverse: false}}, {name: "nil config", config: nil}, } @@ -74,7 +74,7 @@ func TestRetry_noInterval(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 125*time.Millisecond) defer cancel() - value, err := retry(ctx, func(i int, _ context.Context) (*int, error) { + value, err := wait(ctx, func(i int, _ context.Context) (*int, error) { return &i, nil }, test.config) diff --git a/upcloud/service/server.go b/upcloud/service/server.go index 13cc386e..5ba7052d 100644 --- a/upcloud/service/server.go +++ b/upcloud/service/server.go @@ -55,7 +55,7 @@ func (s *Service) CreateServer(ctx context.Context, r *request.CreateServerReque // WaitForServerState blocks execution until the specified server has entered the specified state. If the state changes // favorably, the new server details are returned. The method will give up after the specified timeout func (s *Service) WaitForServerState(ctx context.Context, r *request.WaitForServerStateRequest) (*upcloud.ServerDetails, error) { - return retry(ctx, func(i int, c context.Context) (*upcloud.ServerDetails, error) { + return wait(ctx, func(i int, c context.Context) (*upcloud.ServerDetails, error) { details, err := s.GetServerDetails(c, &request.GetServerDetailsRequest{ UUID: r.UUID, }) diff --git a/upcloud/service/server_group_test.go b/upcloud/service/server_group_test.go index d242a128..025dc2fb 100644 --- a/upcloud/service/server_group_test.go +++ b/upcloud/service/server_group_test.go @@ -6,9 +6,9 @@ import ( "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/request" - "github.com/dnaeon/go-vcr/recorder" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/dnaeon/go-vcr.v4/pkg/recorder" ) func TestServerGroups(t *testing.T) { @@ -116,7 +116,7 @@ func TestServerGroups(t *testing.T) { assert.NoError(t, err) // skip server cleanup if recorder is replaying to save some time - if rec.Mode() == recorder.ModeReplaying { + if !rec.IsRecording() { return } diff --git a/upcloud/service/server_test.go b/upcloud/service/server_test.go index 01e17e63..64025c80 100644 --- a/upcloud/service/server_test.go +++ b/upcloud/service/server_test.go @@ -3,7 +3,6 @@ package service import ( "context" "fmt" - "net/http" "reflect" "strings" "testing" @@ -11,9 +10,9 @@ import ( "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/request" - "github.com/dnaeon/go-vcr/recorder" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/dnaeon/go-vcr.v4/pkg/recorder" ) // TestGetServerConfigurations ensures that the GetServerConfigurations() function returns proper data. @@ -585,17 +584,10 @@ func stopServer(ctx context.Context, rec *recorder.Recorder, svc *Service, uuid // Waits for the server to achieve the desired state. func waitForServerState(ctx context.Context, rec *recorder.Recorder, svc *Service, serverUUID string, desiredState string) error { - if rec.Mode() != recorder.ModeRecording { + if !rec.IsRecording() { return nil } - rec.AddPassthrough(func(h *http.Request) bool { - return true - }) - defer func() { - rec.Passthroughs = nil - }() - // Wait for the server to start _, err := svc.WaitForServerState(ctx, &request.WaitForServerStateRequest{ UUID: serverUUID, diff --git a/upcloud/service/storage.go b/upcloud/service/storage.go index 7da51632..a0ae6327 100644 --- a/upcloud/service/storage.go +++ b/upcloud/service/storage.go @@ -89,7 +89,7 @@ func (s *Service) TemplatizeStorage(ctx context.Context, r *request.TemplatizeSt // WaitForStorageState blocks execution until the specified storage device has entered the specified state. If the // state changes favorably, the new storage details is returned. The method will give up after the specified timeout func (s *Service) WaitForStorageState(ctx context.Context, r *request.WaitForStorageStateRequest) (*upcloud.StorageDetails, error) { - return retry(ctx, func(i int, c context.Context) (*upcloud.StorageDetails, error) { + return wait(ctx, func(i int, c context.Context) (*upcloud.StorageDetails, error) { details, err := s.GetStorageDetails(c, &request.GetStorageDetailsRequest{ UUID: r.UUID, }) @@ -214,7 +214,7 @@ func (s *Service) GetStorageImportDetails(ctx context.Context, r *request.GetSto // WaitForStorageImportCompletion waits for the importing storage to complete. func (s *Service) WaitForStorageImportCompletion(ctx context.Context, r *request.WaitForStorageImportCompletionRequest) (*upcloud.StorageImportDetails, error) { - return retry(ctx, func(i int, c context.Context) (*upcloud.StorageImportDetails, error) { + return wait(ctx, func(i int, c context.Context) (*upcloud.StorageImportDetails, error) { details, err := s.GetStorageImportDetails(c, &request.GetStorageImportDetailsRequest{ UUID: r.StorageUUID, }) diff --git a/upcloud/service/storage_test.go b/upcloud/service/storage_test.go index 00906138..a40ee929 100644 --- a/upcloud/service/storage_test.go +++ b/upcloud/service/storage_test.go @@ -12,9 +12,9 @@ import ( "testing" "time" - "github.com/dnaeon/go-vcr/recorder" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/dnaeon/go-vcr.v4/pkg/recorder" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/request" @@ -649,17 +649,10 @@ func deleteStorage(ctx context.Context, svc *Service, uuid string) error { // Waits for the specified storage to come online. func waitForStorageImportCompletion(ctx context.Context, rec *recorder.Recorder, svc *Service, storageUUID string) error { - if rec.Mode() != recorder.ModeRecording { + if !rec.IsRecording() { return nil } - rec.AddPassthrough(func(h *http.Request) bool { - return true - }) - defer func() { - rec.Passthroughs = nil - }() - _, err := svc.WaitForStorageImportCompletion(ctx, &request.WaitForStorageImportCompletionRequest{ StorageUUID: storageUUID, }) @@ -669,17 +662,10 @@ func waitForStorageImportCompletion(ctx context.Context, rec *recorder.Recorder, // Waits for the specified storage to come online. func waitForStorageOnlineState(ctx context.Context, rec *recorder.Recorder, svc *Service, storageUUID string) error { - if rec.Mode() != recorder.ModeRecording { + if !rec.IsRecording() { return nil } - rec.AddPassthrough(func(h *http.Request) bool { - return true - }) - defer func() { - rec.Passthroughs = nil - }() - _, err := svc.WaitForStorageState(ctx, &request.WaitForStorageStateRequest{ UUID: storageUUID, DesiredState: upcloud.StorageStateOnline, diff --git a/upcloud/service/tag_test.go b/upcloud/service/tag_test.go index 6f67f26f..0707ea61 100644 --- a/upcloud/service/tag_test.go +++ b/upcloud/service/tag_test.go @@ -6,9 +6,9 @@ import ( "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/request" - "github.com/dnaeon/go-vcr/recorder" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/dnaeon/go-vcr.v4/pkg/recorder" ) // TestCreateTag tests the creation of a single tag diff --git a/upcloud/service/token_test.go b/upcloud/service/token_test.go index 4c281f89..c510090d 100644 --- a/upcloud/service/token_test.go +++ b/upcloud/service/token_test.go @@ -9,8 +9,8 @@ import ( "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/client" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/request" - "github.com/dnaeon/go-vcr/recorder" "github.com/stretchr/testify/assert" + "gopkg.in/dnaeon/go-vcr.v4/pkg/recorder" "github.com/stretchr/testify/require" ) diff --git a/upcloud/service/utils_test.go b/upcloud/service/utils_test.go index a3904636..3a55c0e2 100644 --- a/upcloud/service/utils_test.go +++ b/upcloud/service/utils_test.go @@ -7,6 +7,7 @@ import ( "net/http" "net/http/httptest" "os" + "regexp" "strings" "testing" "time" @@ -14,9 +15,9 @@ import ( "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/client" "github.com/UpCloudLtd/upcloud-go-api/v8/upcloud/request" - "github.com/dnaeon/go-vcr/cassette" - "github.com/dnaeon/go-vcr/recorder" "github.com/stretchr/testify/require" + "gopkg.in/dnaeon/go-vcr.v4/pkg/cassette" + "gopkg.in/dnaeon/go-vcr.v4/pkg/recorder" ) const waitTimeout = time.Minute * 15 @@ -63,16 +64,22 @@ func handleError(err error) { } // records the API interactions of the test. Function provides both services to test cases so that old utility functions can be used to initialize environment. -func record(t *testing.T, fixture string, f func(context.Context, *testing.T, *recorder.Recorder, *Service)) { +func record(t *testing.T, fixture string, f func(context.Context, *testing.T, *recorder.Recorder, *Service), opts ...recorder.Option) { if testing.Short() { t.Skip("Skipping recorded test in short mode") } - r, err := recorder.New("fixtures/" + fixture) - require.NoError(t, err) + testMode := os.Getenv("UPCLOUD_GO_SDK_TEST_MODE") + recorderMode := recorder.ModeRecordOnce + switch testMode { + case "integration": + recorderMode = recorder.ModeReplayOnly + case "end-to-end": + recorderMode = recorder.ModePassthrough + } // Redact sensitive information from Authorization field - r.AddFilter(func(i *cassette.Interaction) error { + hook := (func(i *cassette.Interaction) error { if authHeader, ok := i.Request.Headers["Authorization"]; ok { var redactedAuthHeader []string for _, value := range authHeader { @@ -90,6 +97,10 @@ func record(t *testing.T, fixture string, f func(context.Context, *testing.T, *r } } + // Redact DB passwords + avns := regexp.MustCompile(`AVNS_[a-zA-Z0-9-_]+`) + i.Response.Body = avns.ReplaceAllString(i.Response.Body, `AVNS_[REDACTED]`) + // Redact sensitive information from response body if i.Response.Body != "" { var responseData map[string]interface{} @@ -114,10 +125,13 @@ func record(t *testing.T, fixture string, f func(context.Context, *testing.T, *r return nil }) - defer func() { - err := r.Stop() - require.NoError(t, err) - }() + passthrough := func(req *http.Request) bool { + if strings.HasPrefix(req.URL.Fragment, "wait:i=") { + // Do not store wait requests in fixtures + return true + } + return false + } // Read token credentials from the environment, if it does not exists try to read user and password var user, password string @@ -128,20 +142,39 @@ func record(t *testing.T, fixture string, f func(context.Context, *testing.T, *r httpClient := client.NewDefaultHTTPClient() origTransport := httpClient.Transport - r.SetTransport(origTransport) - httpClient.Transport = r + transport := origTransport customAPI := os.Getenv("UPCLOUD_GO_SDK_API_HOST") if customAPI != "" { // Override api host after the go-vcr to maintain consistent test fixtures - r.SetTransport(&customRoundTripper{fn: func(r *http.Request) (*http.Response, error) { + transport = &customRoundTripper{fn: func(r *http.Request) (*http.Response, error) { clone := r.Clone(r.Context()) clone.URL.Host = customAPI clone.Host = customAPI return origTransport.RoundTrip(clone) - }}) + }} } + opts = append(opts, + recorder.WithHook(hook, recorder.BeforeSaveHook), + recorder.WithMode(recorderMode), + recorder.WithPassthrough(passthrough), + recorder.WithRealTransport(transport), + ) + + r, err := recorder.New( + "fixtures/"+fixture, + opts..., + ) + require.NoError(t, err) + + httpClient.Transport = r + + defer func() { + err := r.Stop() + require.NoError(t, err) + }() + // just some random timeout value. High enough that it won't be reached during normal test. ctx, cancel := context.WithTimeout(context.Background(), waitTimeout*4) defer cancel()