Skip to content

Commit

Permalink
Implement Brightbox cloud provider
Browse files Browse the repository at this point in the history
  • Loading branch information
NeilW committed Apr 30, 2020
1 parent 2ede9d5 commit cb998ef
Show file tree
Hide file tree
Showing 2 changed files with 251 additions and 22 deletions.
103 changes: 87 additions & 16 deletions cluster-autoscaler/cloudprovider/brightbox/brightbox_cloud_provider.go
Expand Up @@ -17,11 +17,15 @@ limitations under the License.
package brightbox

import (
"strings"

"github.com/brightbox/brightbox-cloud-controller-manager/k8ssdk"
apiv1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/autoscaler/cluster-autoscaler/cloudprovider"
"k8s.io/autoscaler/cluster-autoscaler/config"
"k8s.io/autoscaler/cluster-autoscaler/utils/errors"
"k8s.io/klog"
)

const (
Expand All @@ -34,23 +38,76 @@ var (

// brightboxCloudProvider implements cloudprovider.CloudProvider interface
type brightboxCloudProvider struct {
resourceLimiter *cloudprovider.ResourceLimiter
ClusterName string
nodeGroups []cloudprovider.NodeGroup
nodeMap map[string]string
*k8ssdk.Cloud
}

// Name returns name of the cloud provider.
func (b *brightboxCloudProvider) Name() string {
klog.V(4).Info("Name called")
return cloudprovider.BrightboxProviderName
}

// NodeGroups returns all node groups configured for this cloud provider.
func (b *brightboxCloudProvider) NodeGroups() []cloudprovider.NodeGroup {
panic("not implemented") // TODO: Implement
klog.V(4).Info("NodeGroups called")
// Duplicate the stored nodegroup elements and return it
//return append(b.nodeGroups[:0:0], b.nodeGroups...)
// Or just return the stored nodegroup elements by reference
return b.nodeGroups
}

// NodeGroupForNode returns the node group for the given node, nil if
// the node should not be processed by cluster autoscaler, or non-nil
// error if such occurred. Must be implemented.
func (b *brightboxCloudProvider) NodeGroupForNode(_ *apiv1.Node) (cloudprovider.NodeGroup, error) {
panic("not implemented") // TODO: Implement
func (b *brightboxCloudProvider) NodeGroupForNode(node *apiv1.Node) (cloudprovider.NodeGroup, error) {
klog.V(4).Info("NodeGroupForNode called")
klog.V(4).Infof("Looking for %v", node.Spec.ProviderID)
groupId, ok := b.nodeMap[k8ssdk.MapProviderIDToServerID(node.Spec.ProviderID)]
if ok {
klog.V(4).Infof("Found in group %v", groupId)
return b.findNodeGroup(groupId), nil
}
klog.V(4).Info("Not found")
return nil, nil
}

func (b *brightboxCloudProvider) findNodeGroup(grpId string) cloudprovider.NodeGroup {
for _, nodeGroup := range b.nodeGroups {
if nodeGroup.Id() == grpId {
return nodeGroup
}
}
return nil
}

// Refresh is called before every main loop and can be used to dynamically
// update cloud provider state.
// In particular the list of node groups returned by NodeGroups can
// change as a result of CloudProvider.Refresh().
func (b *brightboxCloudProvider) Refresh() error {
klog.V(4).Info("Refresh called")
groups, err := b.GetServerGroups()
if err != nil {
return err
}
clusterSuffix := "." + b.ClusterName
nodeGroups := make([]cloudprovider.NodeGroup, 0)
nodeMap := make(map[string]string)
for _, group := range groups {
if strings.HasSuffix(group.Description, clusterSuffix) {
nodeGroups = append(nodeGroups, &brightboxNodeGroup{id: group.Id})
for _, server := range group.Servers {
nodeMap[server.Id] = group.Id
}
}
}
b.nodeGroups = nodeGroups
b.nodeMap = nodeMap
return nil
}

// Pricing returns pricing model for this cloud provider or error if
Expand All @@ -64,7 +121,7 @@ func (b *brightboxCloudProvider) Pricing() (cloudprovider.PricingModel, errors.A
// from the cloud provider.
// Implementation optional.
func (b *brightboxCloudProvider) GetAvailableMachineTypes() ([]string, error) {
panic("not implemented") // TODO: Implement
return nil, cloudprovider.ErrNotImplemented
}

// NewNodeGroup builds a theoretical node group based on the node
Expand All @@ -73,38 +130,34 @@ func (b *brightboxCloudProvider) GetAvailableMachineTypes() ([]string, error) {
// until it is created.
// Implementation optional.
func (b *brightboxCloudProvider) NewNodeGroup(machineType string, labels map[string]string, systemLabels map[string]string, taints []apiv1.Taint, extraResources map[string]resource.Quantity) (cloudprovider.NodeGroup, error) {
panic("not implemented") // TODO: Implement
return nil, cloudprovider.ErrNotImplemented
}

// GetResourceLimiter returns struct containing limits (max, min) for
// resources (cores, memory etc.).
func (b *brightboxCloudProvider) GetResourceLimiter() (*cloudprovider.ResourceLimiter, error) {
panic("not implemented") // TODO: Implement
klog.V(4).Info("GetResourceLimiter called")
return b.resourceLimiter, nil
}

// GPULabel returns the label added to nodes with GPU resource.
func (b *brightboxCloudProvider) GPULabel() string {
klog.V(4).Info("GPULabel called")
return GPULabel
}

// GetAvailableGPUTypes return all available GPU types cloud provider
// supports.
func (b *brightboxCloudProvider) GetAvailableGPUTypes() map[string]struct{} {
klog.V(4).Info("GetAvailableGPUTypes called")
return availableGPUTypes
}

// Cleanup cleans up open resources before the cloud provider is
// destroyed, i.e. go routines etc.
func (b *brightboxCloudProvider) Cleanup() error {
panic("not implemented") // TODO: Implement
}

// Refresh is called before every main loop and can be used to dynamically
// update cloud provider state.
// In particular the list of node groups returned by NodeGroups can
// change as a result of CloudProvider.Refresh().
func (b *brightboxCloudProvider) Refresh() error {
panic("not implemented") // TODO: Implement
klog.V(4).Info("Cleanup called")
return nil
}

// BuildBrightbox builds the Brightbox provider
Expand All @@ -113,5 +166,23 @@ func BuildBrightbox(
do cloudprovider.NodeGroupDiscoveryOptions,
rl *cloudprovider.ResourceLimiter,
) cloudprovider.CloudProvider {
return &brightboxCloudProvider{}
klog.V(4).Info("BuildBrightbox called")
klog.V(4).Infof("Config: %+v", opts)
klog.V(4).Infof("Discovery Options: %+v", do)
if opts.CloudConfig != "" {
klog.Warning("supplied config is not read by this version. Using environment")
}
if opts.ClusterName == "" {
klog.Fatal("Set the cluster name option to the Fully Qualified Internal Domain Name of the cluster")
}
newCloudProvider := &brightboxCloudProvider{
ClusterName: opts.ClusterName,
resourceLimiter: rl,
Cloud: &k8ssdk.Cloud{},
}
_, err := newCloudProvider.CloudClient()
if err != nil {
klog.Fatalf("Failed to create Brightbox Cloud Client: %v", err)
}
return newCloudProvider
}
Expand Up @@ -17,30 +17,188 @@ limitations under the License.
package brightbox

import (
"encoding/json"
"strings"
"testing"

"github.com/brightbox/brightbox-cloud-controller-manager/k8ssdk"
"github.com/brightbox/brightbox-cloud-controller-manager/k8ssdk/mocks"
"github.com/brightbox/gobrightbox"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
"k8s.io/autoscaler/cluster-autoscaler/cloudprovider"
"k8s.io/autoscaler/cluster-autoscaler/config"
)

const (
fakeServer = "srv-testy"
fakeGroup = "grp-testy"
missingServer = "srv-notty"
fakeClusterName = "k8s-fake.cluster.local"
)

var (
fakeNodeMap = map[string]string{
fakeServer: fakeGroup,
}
fakeNodeGroup = &brightboxNodeGroup{
id: fakeGroup,
}
fakeNodeGroups = []cloudprovider.NodeGroup{
fakeNodeGroup,
}
)

func TestName(t *testing.T) {
assert.Equal(t, makeFakeCloudProvider().Name(), cloudprovider.BrightboxProviderName)
assert.Equal(t, makeFakeCloudProvider(nil).Name(), cloudprovider.BrightboxProviderName)
}

func TestGPULabel(t *testing.T) {
assert.Equal(t, makeFakeCloudProvider().GPULabel(), GPULabel)
assert.Equal(t, makeFakeCloudProvider(nil).GPULabel(), GPULabel)
}

func TestGetAvailableGPUTypes(t *testing.T) {
assert.Equal(t, makeFakeCloudProvider().GetAvailableGPUTypes(), availableGPUTypes)
assert.Equal(t, makeFakeCloudProvider(nil).GetAvailableGPUTypes(), availableGPUTypes)
}

func TestPricing(t *testing.T) {
obj, err := makeFakeCloudProvider().Pricing()
obj, err := makeFakeCloudProvider(nil).Pricing()
assert.Equal(t, err, cloudprovider.ErrNotImplemented)
assert.Nil(t, obj)
}

func TestGetAvailableMachineTypes(t *testing.T) {
obj, err := makeFakeCloudProvider(nil).GetAvailableMachineTypes()
assert.Equal(t, err, cloudprovider.ErrNotImplemented)
assert.Nil(t, obj)
}

func makeFakeCloudProvider() *brightboxCloudProvider {
return &brightboxCloudProvider{}
func TestNewNodeGroup(t *testing.T) {
obj, err := makeFakeCloudProvider(nil).NewNodeGroup("", nil, nil, nil, nil)
assert.Equal(t, err, cloudprovider.ErrNotImplemented)
assert.Nil(t, obj)
}

func TestCleanUp(t *testing.T) {
assert.Nil(t, makeFakeCloudProvider(nil).Cleanup())
}

func TestResourceLimiter(t *testing.T) {
client := makeFakeCloudProvider(nil)
obj, err := client.GetResourceLimiter()
assert.Equal(t, obj, client.resourceLimiter)
assert.NoError(t, err)
}

func TestNodeGroups(t *testing.T) {
client := makeFakeCloudProvider(nil)
assert.Zero(t, client.NodeGroups())
client.nodeGroups = make([]cloudprovider.NodeGroup, 0)
assert.NotZero(t, client.NodeGroups())
assert.Empty(t, client.NodeGroups())
nodeGroup := &brightboxNodeGroup{}
client.nodeGroups = append(client.nodeGroups, nodeGroup)
newGroups := client.NodeGroups()
assert.Len(t, newGroups, 1)
assert.Same(t, newGroups[0], client.nodeGroups[0])
}

func TestNodeGroupForNode(t *testing.T) {
client := makeFakeCloudProvider(nil)
client.nodeGroups = fakeNodeGroups
client.nodeMap = fakeNodeMap
node := makeNode(fakeServer)
nodeGroup, err := client.NodeGroupForNode(&node)
assert.Equal(t, fakeNodeGroup, nodeGroup)
assert.Nil(t, err)
node = makeNode(missingServer)
nodeGroup, err = client.NodeGroupForNode(&node)
assert.Nil(t, nodeGroup)
assert.Nil(t, err)
}

func TestBuildBrightBox(t *testing.T) {
ts := k8ssdk.GetAuthEnvTokenHandler(t)
defer k8ssdk.ResetAuthEnvironment()
defer ts.Close()
rl := cloudprovider.NewResourceLimiter(nil, nil)
do := cloudprovider.NodeGroupDiscoveryOptions{}
opts := config.AutoscalingOptions{
CloudProviderName: cloudprovider.BrightboxProviderName,
ClusterName: fakeClusterName,
}
cloud := BuildBrightbox(opts, do, rl)
assert.Equal(t, cloud.Name(), cloudprovider.BrightboxProviderName)
obj, err := cloud.GetResourceLimiter()
assert.Equal(t, rl, obj)
assert.Nil(t, err)
}

func TestRefresh(t *testing.T) {
mockclient := new(mocks.CloudAccess)
testclient := k8ssdk.MakeTestClient(mockclient, nil)
provider := makeFakeCloudProvider(testclient)
testGroups := fakeGroups()
mockclient.On("ServerGroups").Return(testGroups, nil)
err := provider.Refresh()
assert.NotEmpty(t, provider.nodeGroups)
assert.NotEmpty(t, provider.nodeMap)
assert.Nil(t, err)
}

func makeNode(serverID string) v1.Node {
return v1.Node{
Spec: v1.NodeSpec{
ProviderID: k8ssdk.MapServerIDToProviderID(serverID),
},
}
}

func makeFakeCloudProvider(brightboxCloudClient *k8ssdk.Cloud) *brightboxCloudProvider {
return &brightboxCloudProvider{
resourceLimiter: &cloudprovider.ResourceLimiter{},
ClusterName: fakeClusterName,
Cloud: brightboxCloudClient,
}
}

func fakeGroups() []brightbox.ServerGroup {
const groupjson = `
[{"id": "grp-sda44",
"resource_type": "server_group",
"url": "https://api.gb1.brightbox.com/1.0/server_groups/grp-sda44",
"name": "default",
"description": "storage.k8s-fake.cluster.local",
"created_at": "2011-10-01T00:00:00Z",
"default": true,
"account":
{"id": "acc-43ks4",
"resource_type": "account",
"url": "https://api.gb1.brightbox.com/1.0/accounts/acc-43ks4",
"name": "Brightbox",
"status": "active"},
"firewall_policy":
{"id": "fwp-j3654",
"resource_type": "firewall_policy",
"url": "https://api.gb1.brightbox.com/1.0/firewall_policies/fwp-j3654",
"default": true,
"name": "default",
"created_at": "2011-10-01T00:00:00Z",
"description": null},
"servers":
[{"id": "srv-lv426",
"resource_type": "server",
"url": "https://api.gb1.brightbox.com/1.0/servers/srv-lv426",
"name": "",
"status": "active",
"locked": false,
"hostname": "srv-lv426",
"fqdn": "srv-lv426.gb1.brightbox.com",
"created_at": "2011-10-01T01:00:00Z",
"started_at": "2011-10-01T01:01:00Z",
"deleted_at": null}]}]
`
var result []brightbox.ServerGroup
_ = json.NewDecoder(strings.NewReader(groupjson)).Decode(&result)
return result
}

0 comments on commit cb998ef

Please sign in to comment.