Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add support for Hetzner Cloud Arm Server Types #5677

Merged
merged 3 commits into from
Apr 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 43 additions & 10 deletions cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,24 @@ func (c *ActionClient) AllWithOpts(ctx context.Context, opts ActionListOpts) ([]
return allActions, nil
}

// WatchOverallProgress watches several actions' progress until they complete with success or error.
// WatchOverallProgress watches several actions' progress until they complete
// with success or error. This watching happens in a goroutine and updates are
// provided through the two returned channels:
//
// - The first channel receives percentage updates of the progress, based on
// the number of completed versus total watched actions. The return value
// is an int between 0 and 100.
// - The second channel returned receives errors for actions that did not
// complete successfully, as well as any errors that happened while
// querying the API.
//
// By default the method keeps watching until all actions have finished
// processing. If you want to be able to cancel the method or configure a
// timeout, use the [context.Context]. Once the method has stopped watching,
// both returned channels are closed.
//
// WatchOverallProgress uses the [WithPollBackoffFunc] of the [Client] to wait
// until sending the next request.
func (c *ActionClient) WatchOverallProgress(ctx context.Context, actions []*Action) (<-chan int, <-chan error) {
errCh := make(chan error, len(actions))
progressCh := make(chan int)
Expand All @@ -212,15 +229,15 @@ func (c *ActionClient) WatchOverallProgress(ctx context.Context, actions []*Acti
watchIDs[action.ID] = struct{}{}
}

ticker := time.NewTicker(c.client.pollInterval)
defer ticker.Stop()
retries := 0

for {
select {
case <-ctx.Done():
errCh <- ctx.Err()
return
case <-ticker.C:
break
case <-time.After(c.client.pollBackoffFunc(retries)):
retries++
}

opts := ActionListOpts{}
Expand Down Expand Up @@ -257,7 +274,24 @@ func (c *ActionClient) WatchOverallProgress(ctx context.Context, actions []*Acti
return progressCh, errCh
}

// WatchProgress watches one action's progress until it completes with success or error.
// WatchProgress watches one action's progress until it completes with success
// or error. This watching happens in a goroutine and updates are provided
// through the two returned channels:
//
// - The first channel receives percentage updates of the progress, based on
// the progress percentage indicated by the API. The return value is an int
// between 0 and 100.
// - The second channel receives any errors that happened while querying the
// API, as well as the error of the action if it did not complete
// successfully, or nil if it did.
//
// By default the method keeps watching until the action has finished
// processing. If you want to be able to cancel the method or configure a
// timeout, use the [context.Context]. Once the method has stopped watching,
// both returned channels are closed.
//
// WatchProgress uses the [WithPollBackoffFunc] of the [Client] to wait until
// sending the next request.
func (c *ActionClient) WatchProgress(ctx context.Context, action *Action) (<-chan int, <-chan error) {
errCh := make(chan error, 1)
progressCh := make(chan int)
Expand All @@ -266,16 +300,15 @@ func (c *ActionClient) WatchProgress(ctx context.Context, action *Action) (<-cha
defer close(errCh)
defer close(progressCh)

ticker := time.NewTicker(c.client.pollInterval)
defer ticker.Stop()
retries := 0

for {
select {
case <-ctx.Done():
errCh <- ctx.Err()
return
case <-ticker.C:
break
case <-time.After(c.client.pollBackoffFunc(retries)):
retries++
}

a, _, err := c.GetByID(ctx, action.ID)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

package instrumentation
package hcloud

import "testing"
// Architecture specifies the architecture of the CPU.
type Architecture string

func Test_preparePath(t *testing.T) {
tests := []struct {
name string
path string
want string
}{
{
"simple test",
"/v1/volumes/123456",
"/volumes/",
},
{
"simple test",
"/v1/volumes/123456/actions/attach",
"/volumes/actions/attach",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := preparePathForLabel(tt.path); got != tt.want {
t.Errorf("preparePathForLabel() = %v, want %v", got, tt.want)
}
})
}
}
const (
// ArchitectureX86 is the architecture for Intel/AMD x86 CPUs.
ArchitectureX86 Architecture = "x86"

// ArchitectureARM is the architecture for ARM CPUs.
ArchitectureARM Architecture = "arm"
)
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ const (
CertificateStatusTypePending CertificateStatusType = "pending"
CertificateStatusTypeFailed CertificateStatusType = "failed"

// only in issuance
// only in issuance.
CertificateStatusTypeCompleted CertificateStatusType = "completed"

// only in renewal
// only in renewal.
CertificateStatusTypeScheduled CertificateStatusType = "scheduled"
CertificateStatusTypeUnavailable CertificateStatusType = "unavailable"
)
Expand Down
55 changes: 37 additions & 18 deletions cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"math"
"net/http"
"net/http/httputil"
Expand All @@ -34,6 +33,7 @@ import (

"github.com/prometheus/client_golang/prometheus"
"golang.org/x/net/http/httpguts"

"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/internal/instrumentation"
"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema"
)
Expand All @@ -59,7 +59,10 @@ func ConstantBackoff(d time.Duration) BackoffFunc {
}

// ExponentialBackoff returns a BackoffFunc which implements an exponential
// backoff using the formula: b^retries * d
// backoff.
// It uses the formula:
//
// b^retries * d
func ExponentialBackoff(b float64, d time.Duration) BackoffFunc {
return func(retries int) time.Duration {
return time.Duration(math.Pow(b, float64(retries))) * d
Expand All @@ -71,8 +74,8 @@ type Client struct {
endpoint string
token string
tokenValid bool
pollInterval time.Duration
backoffFunc BackoffFunc
pollBackoffFunc BackoffFunc
httpClient *http.Client
applicationName string
applicationVersion string
Expand Down Expand Up @@ -119,15 +122,31 @@ func WithToken(token string) ClientOption {
}
}

// WithPollInterval configures a Client to use the specified interval when polling
// from the API.
// WithPollInterval configures a Client to use the specified interval when
// polling from the API.
//
// Deprecated: Setting the poll interval is deprecated, you can now configure
// [WithPollBackoffFunc] with a [ConstantBackoff] to get the same results. To
// migrate your code, replace your usage like this:
//
// // before
// hcloud.WithPollInterval(2 * time.Second)
// // now
// hcloud.WithPollBackoffFunc(hcloud.ConstantBackoff(2 * time.Second))
func WithPollInterval(pollInterval time.Duration) ClientOption {
return WithPollBackoffFunc(ConstantBackoff(pollInterval))
}

// WithPollBackoffFunc configures a Client to use the specified backoff
// function when polling from the API.
func WithPollBackoffFunc(f BackoffFunc) ClientOption {
return func(client *Client) {
client.pollInterval = pollInterval
client.backoffFunc = f
}
}

// WithBackoffFunc configures a Client to use the specified backoff function.
// The backoff function is used for retrying HTTP requests.
func WithBackoffFunc(f BackoffFunc) ClientOption {
return func(client *Client) {
client.backoffFunc = f
Expand Down Expand Up @@ -169,11 +188,11 @@ func WithInstrumentation(registry *prometheus.Registry) ClientOption {
// NewClient creates a new client.
func NewClient(options ...ClientOption) *Client {
client := &Client{
endpoint: Endpoint,
tokenValid: true,
httpClient: &http.Client{},
backoffFunc: ExponentialBackoff(2, 500*time.Millisecond),
pollInterval: 500 * time.Millisecond,
endpoint: Endpoint,
tokenValid: true,
httpClient: &http.Client{},
backoffFunc: ExponentialBackoff(2, 500*time.Millisecond),
pollBackoffFunc: ConstantBackoff(500 * time.Millisecond),
}

for _, option := range options {
Expand Down Expand Up @@ -238,7 +257,7 @@ func (c *Client) Do(r *http.Request, v interface{}) (*Response, error) {
var body []byte
var err error
if r.ContentLength > 0 {
body, err = ioutil.ReadAll(r.Body)
body, err = io.ReadAll(r.Body)
if err != nil {
r.Body.Close()
return nil, err
Expand All @@ -247,7 +266,7 @@ func (c *Client) Do(r *http.Request, v interface{}) (*Response, error) {
}
for {
if r.ContentLength > 0 {
r.Body = ioutil.NopCloser(bytes.NewReader(body))
r.Body = io.NopCloser(bytes.NewReader(body))
}

if c.debugWriter != nil {
Expand All @@ -263,13 +282,13 @@ func (c *Client) Do(r *http.Request, v interface{}) (*Response, error) {
return nil, err
}
response := &Response{Response: resp}
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
resp.Body.Close()
return response, err
}
resp.Body.Close()
resp.Body = ioutil.NopCloser(bytes.NewReader(body))
resp.Body = io.NopCloser(bytes.NewReader(body))

if c.debugWriter != nil {
dumpResp, err := httputil.DumpResponse(resp, true)
Expand All @@ -287,7 +306,7 @@ func (c *Client) Do(r *http.Request, v interface{}) (*Response, error) {
err = errorFromResponse(resp, body)
if err == nil {
err = fmt.Errorf("hcloud: server responded with status code %d", resp.StatusCode)
} else if isRetryable(err) {
} else if isConflict(err) {
c.backoff(retries)
retries++
continue
Expand All @@ -306,12 +325,12 @@ func (c *Client) Do(r *http.Request, v interface{}) (*Response, error) {
}
}

func isRetryable(error error) bool {
func isConflict(error error) bool {
err, ok := error.(Error)
if !ok {
return false
}
return err.Code == ErrorCodeRateLimitExceeded || err.Code == ErrorCodeConflict
return err.Code == ErrorCodeConflict
}

func (c *Client) backoff(retries int) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ const (
ErrorCodeResourceLocked ErrorCode = "resource_locked" // The resource is locked. The caller should contact support
ErrorUnsupportedError ErrorCode = "unsupported_error" // The given resource does not support this

// Server related error codes
// Server related error codes.
ErrorCodeInvalidServerType ErrorCode = "invalid_server_type" // The server type does not fit for the given server or is deprecated
ErrorCodeServerNotStopped ErrorCode = "server_not_stopped" // The action requires a stopped server
ErrorCodeNetworksOverlap ErrorCode = "networks_overlap" // The network IP range overlaps with one of the server networks
ErrorCodePlacementError ErrorCode = "placement_error" // An error during the placement occurred
ErrorCodeServerAlreadyAttached ErrorCode = "server_already_attached" // The server is already attached to the resource

// Load Balancer related error codes
// Load Balancer related error codes.
ErrorCodeIPNotOwned ErrorCode = "ip_not_owned" // The IP you are trying to add as a target is not owned by the Project owner
ErrorCodeSourcePortAlreadyUsed ErrorCode = "source_port_already_used" // The source port you are trying to add is already in use
ErrorCodeCloudResourceIPNotAllowed ErrorCode = "cloud_resource_ip_not_allowed" // The IP you are trying to add as a target belongs to a Hetzner Cloud resource
Expand All @@ -62,24 +62,24 @@ const (
ErrorCodeTargetsWithoutUsePrivateIP ErrorCode = "targets_without_use_private_ip" // The Load Balancer has targets that use the public IP instead of the private IP
ErrorCodeLoadBalancerNotAttachedToNetwork ErrorCode = "load_balancer_not_attached_to_network" // The Load Balancer is not attached to a network

// Network related error codes
// Network related error codes.
ErrorCodeIPNotAvailable ErrorCode = "ip_not_available" // The provided Network IP is not available
ErrorCodeNoSubnetAvailable ErrorCode = "no_subnet_available" // No Subnet or IP is available for the Load Balancer/Server within the network
ErrorCodeVSwitchAlreadyUsed ErrorCode = "vswitch_id_already_used" // The given Robot vSwitch ID is already registered in another network

// Volume related error codes
// Volume related error codes.
ErrorCodeNoSpaceLeftInLocation ErrorCode = "no_space_left_in_location" // There is no volume space left in the given location
ErrorCodeVolumeAlreadyAttached ErrorCode = "volume_already_attached" // Volume is already attached to a server, detach first

// Firewall related error codes
// Firewall related error codes.
ErrorCodeFirewallAlreadyApplied ErrorCode = "firewall_already_applied" // Firewall was already applied on resource
ErrorCodeFirewallAlreadyRemoved ErrorCode = "firewall_already_removed" // Firewall was already removed from the resource
ErrorCodeIncompatibleNetworkType ErrorCode = "incompatible_network_type" // The Network type is incompatible for the given resource
ErrorCodeResourceInUse ErrorCode = "resource_in_use" // Firewall must not be in use to be deleted
ErrorCodeServerAlreadyAdded ErrorCode = "server_already_added" // Server added more than one time to resource
ErrorCodeFirewallResourceNotFound ErrorCode = "firewall_resource_not_found" // Resource a firewall should be attached to / detached from not found

// Certificate related error codes
// Certificate related error codes.
ErrorCodeCAARecordDoesNotAllowCA ErrorCode = "caa_record_does_not_allow_ca" // CAA record does not allow certificate authority
ErrorCodeCADNSValidationFailed ErrorCode = "ca_dns_validation_failed" // Certificate Authority: DNS validation failed
ErrorCodeCATooManyAuthorizationsFailedRecently ErrorCode = "ca_too_many_authorizations_failed_recently" // Certificate Authority: Too many authorizations failed recently
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ type FloatingIP struct {
}

// DNSPtrForIP returns the reverse DNS pointer of the IP address.
// Deprecated: Use GetDNSPtrForIP instead
// Deprecated: Use GetDNSPtrForIP instead.
func (f *FloatingIP) DNSPtrForIP(ip net.IP) string {
return f.DNSPtr[ip.String()]
}
Expand Down Expand Up @@ -257,10 +257,10 @@ func (c *FloatingIPClient) Create(ctx context.Context, opts FloatingIPCreateOpts
Name: opts.Name,
}
if opts.HomeLocation != nil {
reqBody.HomeLocation = String(opts.HomeLocation.Name)
reqBody.HomeLocation = Ptr(opts.HomeLocation.Name)
}
if opts.Server != nil {
reqBody.Server = Int(opts.Server.ID)
reqBody.Server = Ptr(opts.Server.ID)
}
if opts.Labels != nil {
reqBody.Labels = &opts.Labels
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ limitations under the License.
package hcloud

// Version is the library's version following Semantic Versioning.
const Version = "1.35.0"
const Version = "1.42.0" // x-release-please-version
Loading