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

Egoscale v3 rewrite #97

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
79 changes: 52 additions & 27 deletions exoscale/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,29 @@ import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"sync"

egoscale "github.com/exoscale/egoscale/v2"
v3 "github.com/exoscale/egoscale/v3"
"github.com/exoscale/egoscale/v3/credentials"

"gopkg.in/fsnotify.v1"
)

const defaultComputeEnvironment = "api"

type exoscaleClient interface {
CreateNetworkLoadBalancer(context.Context, string, *egoscale.NetworkLoadBalancer) (*egoscale.NetworkLoadBalancer, error)
CreateNetworkLoadBalancerService(context.Context, string, *egoscale.NetworkLoadBalancer, *egoscale.NetworkLoadBalancerService) (*egoscale.NetworkLoadBalancerService, error)
DeleteNetworkLoadBalancer(context.Context, string, *egoscale.NetworkLoadBalancer) error
DeleteNetworkLoadBalancerService(context.Context, string, *egoscale.NetworkLoadBalancer, *egoscale.NetworkLoadBalancerService) error
GetInstance(context.Context, string, string) (*egoscale.Instance, error)
GetInstanceType(context.Context, string, string) (*egoscale.InstanceType, error)
GetNetworkLoadBalancer(context.Context, string, string) (*egoscale.NetworkLoadBalancer, error)
ListInstances(context.Context, string, ...egoscale.ListInstancesOpt) ([]*egoscale.Instance, error)
UpdateNetworkLoadBalancer(context.Context, string, *egoscale.NetworkLoadBalancer) error
UpdateNetworkLoadBalancerService(context.Context, string, *egoscale.NetworkLoadBalancer, *egoscale.NetworkLoadBalancerService) error
CreateLoadBalancer(ctx context.Context, req v3.CreateLoadBalancerRequest) (*v3.Operation, error)
AddServiceToLoadBalancer(ctx context.Context, id v3.UUID, req v3.AddServiceToLoadBalancerRequest) (*v3.Operation, error)
DeleteLoadBalancer(ctx context.Context, id v3.UUID) (*v3.Operation, error)
DeleteLoadBalancerService(ctx context.Context, id v3.UUID, serviceID v3.UUID) (*v3.Operation, error)
GetInstance(ctx context.Context, id v3.UUID) (*v3.Instance, error)
GetInstanceType(ctx context.Context, id v3.UUID) (*v3.InstanceType, error)
GetLoadBalancer(ctx context.Context, id v3.UUID) (*v3.LoadBalancer, error)
ListInstances(ctx context.Context, opts ...v3.ListInstancesOpt) (*v3.ListInstancesResponse, error)
UpdateLoadBalancer(ctx context.Context, id v3.UUID, req v3.UpdateLoadBalancerRequest) (*v3.Operation, error)
UpdateLoadBalancerService(ctx context.Context, id v3.UUID, serviceID v3.UUID, req v3.UpdateLoadBalancerServiceRequest) (*v3.Operation, error)
Wait(ctx context.Context, op *v3.Operation, states ...v3.OperationState) (*v3.Operation, error)
}

type exoscaleAPICredentials struct {
Expand All @@ -37,46 +38,65 @@ type exoscaleAPICredentials struct {
type refreshableExoscaleClient struct {
exo exoscaleClient
apiCredentials exoscaleAPICredentials
apiEnvironment string
apiEndpoint string

*sync.RWMutex
}

func newRefreshableExoscaleClient(ctx context.Context, config *globalConfig) (*refreshableExoscaleClient, error) {
c := &refreshableExoscaleClient{
RWMutex: &sync.RWMutex{},
apiEnvironment: defaultComputeEnvironment,
RWMutex: &sync.RWMutex{},
}

if config.ApiEnvironment != "" {
c.apiEnvironment = config.ApiEnvironment
if config.APIEndpoint != "" {
c.apiEndpoint = config.APIEndpoint
}

if config.ApiKey != "" && config.ApiSecret != "" { //nolint:gocritic
if config.APIKey != "" && config.APISecret != "" { //nolint:gocritic
infof("using Exoscale actual API credentials (key + secret)")

c.apiCredentials = exoscaleAPICredentials{
APIKey: config.ApiKey,
APISecret: config.ApiSecret,
APIKey: config.APIKey,
APISecret: config.APISecret,
}

exo, err := egoscale.NewClient(c.apiCredentials.APIKey, c.apiCredentials.APISecret)
//TODO add chain credentials with env...etc
creds := credentials.NewStaticCredentials(c.apiCredentials.APIKey, c.apiCredentials.APISecret)
exo, err := v3.NewClient(creds, v3.ClientOptWithUserAgent(
fmt.Sprintf("Exoscale-K8s-Cloud-Controller/%s", versionString),
))
if err != nil {
return nil, err
}

if c.apiEndpoint != "" {
exo = exo.WithEndpoint(v3.Endpoint(c.apiEndpoint))
}

c.exo = exo
} else if config.ApiCredentialsFile != "" {
infof("reading (watching) Exoscale API credentials from file %q", config.ApiCredentialsFile)
} else if config.APICredentialsFile != "" {
infof("reading (watching) Exoscale API credentials from file %q", config.APICredentialsFile)

c.refreshCredentialsFromFile(config.ApiCredentialsFile)
go c.watchCredentialsFile(ctx, config.ApiCredentialsFile)
c.refreshCredentialsFromFile(config.APICredentialsFile)
go c.watchCredentialsFile(ctx, config.APICredentialsFile)
} else {
return nil, errors.New("incomplete or missing Exoscale API credentials")
}

return c, nil
}

func (c *refreshableExoscaleClient) Wait(ctx context.Context, op *v3.Operation, states ...v3.OperationState) (*v3.Operation, error) {
c.RLock()
defer c.RUnlock()

return c.exo.Wait(
ctx,
op,
states...,
)
}

func (c *refreshableExoscaleClient) watchCredentialsFile(ctx context.Context, path string) {
watcher, err := fsnotify.NewWatcher()
if err != nil {
Expand Down Expand Up @@ -131,13 +151,18 @@ func (c *refreshableExoscaleClient) refreshCredentialsFromFile(path string) {
return
}

client, err := egoscale.NewClient(apiCredentials.APIKey, apiCredentials.APISecret)
//TODO add chain credentials with env...etc
creds := credentials.NewStaticCredentials(apiCredentials.APIKey, apiCredentials.APISecret)
client, err := v3.NewClient(creds)
if err != nil {
infof("failed to initialize Exoscale client: %v", err)
return
}

c.Lock()
if c.apiEndpoint != "" {
client = client.WithEndpoint(v3.Endpoint(c.apiEndpoint))
}
c.exo = client
c.apiCredentials = apiCredentials
c.Unlock()
Expand Down
116 changes: 58 additions & 58 deletions exoscale/client_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package exoscale
import (
"context"

egoscale "github.com/exoscale/egoscale/v2"
v3 "github.com/exoscale/egoscale/v3"

"github.com/stretchr/testify/mock"
)
Expand All @@ -12,87 +12,87 @@ type exoscaleClientMock struct {
mock.Mock
}

func (m *exoscaleClientMock) CreateNetworkLoadBalancer(
func (m *exoscaleClientMock) CreateLoadBalancer(
ctx context.Context,
zone string,
nlb *egoscale.NetworkLoadBalancer,
) (*egoscale.NetworkLoadBalancer, error) {
args := m.Called(ctx, zone, nlb)
return args.Get(0).(*egoscale.NetworkLoadBalancer), args.Error(1)
req v3.CreateLoadBalancerRequest,
) (*v3.Operation, error) {
args := m.Called(ctx, req)
return args.Get(0).(*v3.Operation), args.Error(1)
}

func (m *exoscaleClientMock) CreateNetworkLoadBalancerService(
func (m *exoscaleClientMock) AddServiceToLoadBalancer(
ctx context.Context,
zone string,
nlb *egoscale.NetworkLoadBalancer,
svc *egoscale.NetworkLoadBalancerService,
) (*egoscale.NetworkLoadBalancerService, error) {
args := m.Called(ctx, zone, nlb, svc)
return args.Get(0).(*egoscale.NetworkLoadBalancerService), args.Error(1)
id v3.UUID,
req v3.AddServiceToLoadBalancerRequest,
) (*v3.Operation, error) {
args := m.Called(ctx, id, req)
return args.Get(0).(*v3.Operation), args.Error(1)
}

func (m *exoscaleClientMock) DeleteNetworkLoadBalancer(
func (m *exoscaleClientMock) DeleteLoadBalancer(
ctx context.Context,
zone string,
nlb *egoscale.NetworkLoadBalancer,
) error {
args := m.Called(ctx, zone, nlb)
return args.Error(0)
id v3.UUID,
) (*v3.Operation, error) {
args := m.Called(ctx, id)
return args.Get(0).(*v3.Operation), args.Error(1)
}

func (m *exoscaleClientMock) DeleteNetworkLoadBalancerService(
func (m *exoscaleClientMock) DeleteLoadBalancerService(
ctx context.Context,
zone string,
nlb *egoscale.NetworkLoadBalancer,
svc *egoscale.NetworkLoadBalancerService,
) error {
args := m.Called(ctx, zone, nlb, svc)
return args.Error(0)
id v3.UUID,
serviceID v3.UUID,
) (*v3.Operation, error) {
args := m.Called(ctx, id, serviceID)
return args.Get(0).(*v3.Operation), args.Error(1)
}

func (m *exoscaleClientMock) GetInstance(ctx context.Context, zone, id string) (*egoscale.Instance, error) {
args := m.Called(ctx, zone, id)
return args.Get(0).(*egoscale.Instance), args.Error(1)
func (m *exoscaleClientMock) GetInstance(ctx context.Context, id v3.UUID) (*v3.Instance, error) {
args := m.Called(ctx, id)
return args.Get(0).(*v3.Instance), args.Error(1)
}

func (m *exoscaleClientMock) GetInstanceType(ctx context.Context, zone, id string) (*egoscale.InstanceType, error) {
args := m.Called(ctx, zone, id)
return args.Get(0).(*egoscale.InstanceType), args.Error(1)
func (m *exoscaleClientMock) GetInstanceType(ctx context.Context, id v3.UUID) (*v3.InstanceType, error) {
args := m.Called(ctx, id)
return args.Get(0).(*v3.InstanceType), args.Error(1)
}

func (m *exoscaleClientMock) GetNetworkLoadBalancer(
ctx context.Context,
zone string,
id string,
) (*egoscale.NetworkLoadBalancer, error) {
args := m.Called(ctx, zone, id)
return args.Get(0).(*egoscale.NetworkLoadBalancer), args.Error(1)
func (m *exoscaleClientMock) GetLoadBalancer(ctx context.Context, id v3.UUID) (*v3.LoadBalancer, error) {
args := m.Called(ctx, id)
return args.Get(0).(*v3.LoadBalancer), args.Error(1)
}

func (m *exoscaleClientMock) ListInstances(
ctx context.Context,
zone string,
opts ...egoscale.ListInstancesOpt,
) ([]*egoscale.Instance, error) {
args := m.Called(ctx, zone, opts)
return args.Get(0).([]*egoscale.Instance), args.Error(1)
opts ...v3.ListInstancesOpt,
) (*v3.ListInstancesResponse, error) {
args := m.Called(ctx, opts)
return args.Get(0).(*v3.ListInstancesResponse), args.Error(1)
}

func (m *exoscaleClientMock) UpdateLoadBalancer(
ctx context.Context,
id v3.UUID,
req v3.UpdateLoadBalancerRequest,
) (*v3.Operation, error) {
args := m.Called(ctx, id, req)
return args.Get(0).(*v3.Operation), args.Error(1)
}

func (m *exoscaleClientMock) UpdateNetworkLoadBalancer(
func (m *exoscaleClientMock) UpdateLoadBalancerService(
ctx context.Context,
zone string,
nlb *egoscale.NetworkLoadBalancer,
) error {
args := m.Called(ctx, zone, nlb)
return args.Error(0)
id v3.UUID,
serviceID v3.UUID,
req v3.UpdateLoadBalancerServiceRequest,
) (*v3.Operation, error) {
args := m.Called(ctx, id, serviceID, req)
return args.Get(0).(*v3.Operation), args.Error(1)
}

func (m *exoscaleClientMock) UpdateNetworkLoadBalancerService(
func (m *exoscaleClientMock) Wait(
ctx context.Context,
zone string,
nlb *egoscale.NetworkLoadBalancer,
svc *egoscale.NetworkLoadBalancerService,
) error {
args := m.Called(ctx, zone, nlb, svc)
return args.Error(0)
op *v3.Operation,
states ...v3.OperationState,
) (*v3.Operation, error) {
args := m.Called(ctx, op, states)
return args.Get(0).(*v3.Operation), args.Error(1)
}
3 changes: 1 addition & 2 deletions exoscale/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,11 @@ func (ts *exoscaleCCMTestSuite) Test_newRefreshableExoscaleClient_no_config() {

func (ts *exoscaleCCMTestSuite) Test_newRefreshableExoscaleClient_credentials() {
expected := &refreshableExoscaleClient{
RWMutex: &sync.RWMutex{},
RWMutex: &sync.RWMutex{}, //nolint:staticcheck
apiCredentials: exoscaleAPICredentials{
APIKey: testAPIKey,
APISecret: testAPISecret,
},
apiEnvironment: defaultComputeEnvironment,
}

actual, err := newRefreshableExoscaleClient(context.Background(), &testConfig_typical.Global)
Expand Down
29 changes: 5 additions & 24 deletions exoscale/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,24 @@ package exoscale
import (
"context"
"errors"
"io"
"net/http"
"strings"

egoscale "github.com/exoscale/egoscale/v2"
v3 "github.com/exoscale/egoscale/v3"
)

const metadataEndpoint = "http://metadata.exoscale.com/1.0/meta-data/"

func (p *cloudProvider) computeInstanceByProviderID(ctx context.Context, providerID string) (*egoscale.Instance, error) {
func (p *cloudProvider) computeInstanceByProviderID(ctx context.Context, providerID string) (*v3.Instance, error) {
id, err := formatProviderID(providerID)
if err != nil {
return nil, err
}

return p.client.GetInstance(ctx, p.zone, id)
return p.client.GetInstance(ctx, id)
}

func formatProviderID(providerID string) (string, error) {
func formatProviderID(providerID string) (v3.UUID, error) {
if providerID == "" {
return "", errors.New("provider ID cannot be empty")
}

return strings.TrimPrefix(providerID, providerPrefix), nil
}

func queryInstanceMetadata(key string) (string, error) {
resp, err := http.Get(metadataEndpoint + key)
if err != nil {
return "", err
}
defer resp.Body.Close()

value, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}

return string(value), nil
return v3.UUID(strings.TrimPrefix(providerID, providerPrefix)), nil
}
4 changes: 0 additions & 4 deletions exoscale/exoscale.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import (
"os"
"strings"

egoscale "github.com/exoscale/egoscale/v2"

"k8s.io/client-go/kubernetes"
cloudprovider "k8s.io/cloud-provider"
"k8s.io/klog/v2"
Expand Down Expand Up @@ -41,8 +39,6 @@ type cloudProvider struct {
}

func init() {
egoscale.UserAgent = fmt.Sprintf("Exoscale-K8s-Cloud-Controller/%s %s", versionString, egoscale.UserAgent)

cloudprovider.RegisterCloudProvider(ProviderName, func(config io.Reader) (cloudprovider.Interface, error) {
cfg, err := readExoscaleConfig(config)
if err != nil {
Expand Down
Loading
Loading