Skip to content

Commit

Permalink
Add CCE client (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
outcatcher authored Apr 30, 2020
1 parent 7c1222a commit 2641ca1
Show file tree
Hide file tree
Showing 7 changed files with 448 additions and 0 deletions.
2 changes: 2 additions & 0 deletions clientconfig/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,8 @@ func NewServiceClient(service string, opts *ClientOpts) (*huaweisdk.ServiceClien
return huaweicloud.NewNetworkV2(pClient, eo)
case "object-store":
return huaweicloud.NewObjectStorageV1(pClient, eo)
case "cce":
return huaweicloud.NewCCE(pClient, eo)
case "orchestration":
return huaweicloud.NewOrchestrationV1(pClient, eo)
case "sharev2":
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/opentelekomcloud-infra/crutch-house
go 1.14

require (
github.com/hashicorp/go-multierror v1.1.0
github.com/huaweicloud/golangsdk v0.0.0-20200414012957-3b8a408c2816
github.com/stretchr/testify v1.5.1
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/huaweicloud/golangsdk v0.0.0-20200414012957-3b8a408c2816 h1:odMiLWHZ8AdQpBMJFD8mrQzKgUy09rKdFkr4TUNwols=
github.com/huaweicloud/golangsdk v0.0.0-20200414012957-3b8a408c2816/go.mod h1:WQBcHRNX9shz3928lWEvstQJtAtYI7ks6XlgtRT9Tcw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand Down
8 changes: 8 additions & 0 deletions services/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"time"

huaweisdk "github.com/huaweicloud/golangsdk"
"github.com/huaweicloud/golangsdk/openstack/cce/v3/clusters"
"github.com/huaweicloud/golangsdk/openstack/cce/v3/nodes"
"github.com/huaweicloud/golangsdk/openstack/compute/v2/extensions/keypairs"
"github.com/huaweicloud/golangsdk/openstack/compute/v2/extensions/secgroups"
"github.com/huaweicloud/golangsdk/openstack/compute/v2/extensions/servergroups"
Expand Down Expand Up @@ -67,6 +69,11 @@ type Client interface {
AddTags(instanceID string, serverTags []string) error
CreateServerGroup(opts *servergroups.CreateOpts) (*servergroups.ServerGroup, error)
DeleteServerGroup(groupID string) error
InitCCE() error
CreateCluster(opts *CreateClusterOpts) (*clusters.Clusters, error)
DeleteCluster(clusterID string) error
CreateNodes(opts *CreateNodesOpts, count int) (*nodes.Nodes, error)
DeleteNodes(clusterID, nodeID string) error
}

// client contains service clients
Expand All @@ -75,6 +82,7 @@ type client struct {

ComputeV2 *huaweisdk.ServiceClient
VPC *huaweisdk.ServiceClient
CCE *huaweisdk.ServiceClient

opts *clientconfig.ClientOpts
}
Expand Down
336 changes: 336 additions & 0 deletions services/containers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,336 @@
package services

import (
"encoding/base64"
"fmt"
"log"
"strings"

"github.com/huaweicloud/golangsdk"
"github.com/huaweicloud/golangsdk/openstack/cce/v3/clusters"
"github.com/huaweicloud/golangsdk/openstack/cce/v3/nodes"

"github.com/hashicorp/go-multierror"

"github.com/opentelekomcloud-infra/crutch-house/clientconfig"
)

const (
ClusterTypeECS = "VirtualMachine"
ClusterTypeBMS = "BareMetal"

ContainerNetworkModeOverlay = "overlay_l2"
ContainerNetworkModeUnderlay = "underlay_ipvlan"
ContainerNetworkModeVPC = "vpc-router"

ClusterAvailable = "Available"
NodeActive = "Active"

EulerOSVersion = "EulerOS 2.5"
)

type Metadata struct {
Labels map[string]string
Annotations map[string]string
}

type CreateClusterOpts struct {
Metadata
Name string
Description string
ClusterType string // required, VirtualMachine or BareMetal
ClusterVersion string // optional, uses latest available version by default
FlavorID string // required, one of CCE flavour
VpcID string // required
SubnetID string // required
HighwaySubnetID string // optional, used for BMS
ContainerNetwork clusters.ContainerNetworkSpec // required, `Mode` should be one of ContainerNetworkMode const
AuthenticationMode string // required, recommended: rbac
BillingMode int
MultiAZ bool
FloatingIP string
ExtendParam map[string]string
}

type CreateNodesOpts struct {
Metadata
Name string
ClusterID string // required
Region string // required, project name actually
FlavorID string // required
AvailabilityZone string // required
KeyPair string // required
RootVolume nodes.VolumeSpec // required, 40G+
DataVolumes []nodes.VolumeSpec // at least one is required required, 100G+
Os string // by default EulerOS 2.5
MaxPods int
PreInstall string
PostInstall string
EipCount int
EipOpts ElasticIPOpts
BillingMode int
PublicKey string
ChargingMode int
PerformanceType string
OrderID string
ProductID string
}

// InitCCE initializes Compute v2 service
func (c *client) InitCCE() error {
if c.CCE != nil {
return nil
}
cce, err := clientconfig.NewServiceClient("cce", c.opts)
if err != nil {
return err
}
c.CCE = cce
return nil
}

func (c *client) getClusterStatus(clusterID string) (string, error) {
state, err := clusters.Get(c.CCE, clusterID).Extract()
if err != nil {
return "", err
}
return state.Status.Phase, nil
}

func (c *client) getNodeStatus(clusterID, nodeIDs string) (string, error) {
state, err := nodes.Get(c.CCE, clusterID, nodeIDs).Extract()
if err != nil {
return "", err
}
return state.Status.Phase, nil
}

func (c *client) waitForCluster(clusterID string) error {
return golangsdk.WaitFor(600, func() (b bool, err error) {
state, err := c.getClusterStatus(clusterID)
if err != nil {
return true, err
}
if state == ClusterAvailable {
return true, nil
}
return false, nil
})
}

func (c *client) waitForClusterDelete(clusterID string) error {
return golangsdk.WaitFor(600, func() (bool, error) {
_, err := c.getClusterStatus(clusterID)
if err != nil {
return true, err
}
switch err.(type) {
case golangsdk.ErrDefault404:
return true, nil
default:
return true, err
}
})
}

// CreateCluster create CCE cluster and wait until it is available
func (c *client) CreateCluster(opts *CreateClusterOpts) (*clusters.Clusters, error) {
opts.ExtendParam = emptyIfNil(opts.ExtendParam)
if opts.MultiAZ {
opts.ExtendParam["clusterAZ"] = "multi_az"
}
if opts.FloatingIP != "" {
opts.ExtendParam["clusterExternalIP"] = opts.FloatingIP
}
createOpts := clusters.CreateOpts{
Kind: "Cluster",
ApiVersion: "v3",
Metadata: clusters.CreateMetaData{
Name: opts.Name,
Labels: emptyIfNil(opts.Labels),
Annotations: emptyIfNil(opts.Annotations),
},
Spec: clusters.Spec{
Type: opts.ClusterType,
Flavor: opts.FlavorID,
Version: opts.ClusterVersion,
Description: opts.Description,
HostNetwork: clusters.HostNetworkSpec{
VpcId: opts.VpcID,
SubnetId: opts.SubnetID,
HighwaySubnet: opts.HighwaySubnetID,
},

ContainerNetwork: opts.ContainerNetwork,
Authentication: clusters.AuthenticationSpec{
Mode: opts.AuthenticationMode,
AuthenticatingProxy: make(map[string]string),
},
BillingMode: opts.BillingMode,
ExtendParam: opts.ExtendParam,
},
}

create, err := clusters.Create(c.CCE, createOpts).Extract()

if err != nil {
return nil, fmt.Errorf("error creating OpenTelekomCloud cluster: %s", err)
}

clusterID := create.Metadata.Id
log.Printf("Waiting for OpenTelekomCloud CCE cluster (%s) to become available", clusterID)

return create, c.waitForCluster(clusterID)
}

func (c *client) DeleteCluster(clusterID string) error {
err := clusters.Delete(c.CCE, clusterID).Err
if err != nil {
return err
}
log.Printf("Waiting for OpenTelekomCloud CCE cluster (%s) to be deleted", clusterID)
return c.waitForClusterDelete(clusterID)
}

func installScriptEncode(script string) string {
if _, err := base64.StdEncoding.DecodeString(script); err != nil {
return base64.StdEncoding.EncodeToString([]byte(script))
}
return script
}

func splitNodeIDs(nodeIDs string) []string {
ids := strings.Split(nodeIDs, ",")
return ids[:len(ids)-1]
}

func (c *client) waitForMultipleNodes(clusterID, nodeIDs string, predicate func(nodeStatus string, err error) (bool, error)) (err *multierror.Error) {
ids := splitNodeIDs(nodeIDs)
var errChan = make(chan error, len(ids))
for _, nodeID := range ids {
go func(node string) {
errChan <- golangsdk.WaitFor(600, func() (bool, error) {
nodeStatus, err := c.getNodeStatus(clusterID, node)
return predicate(nodeStatus, err)
})
}(nodeID)
}

for range ids {
err = multierror.Append(err, <-errChan)
}
return err
}

func (c *client) waitForNodesActive(clusterID, nodeIDs string) *multierror.Error {
return c.waitForMultipleNodes(clusterID, nodeIDs, func(nodeStatus string, err error) (bool, error) {
if err != nil {
return true, err
}
return nodeStatus == NodeActive, nil
})
}

func (c *client) waitForNodesDeleted(clusterID, nodeIDs string) *multierror.Error {
return c.waitForMultipleNodes(clusterID, nodeIDs, func(nodeStatus string, err error) (bool, error) {
if err == nil {
return false, nil
}
switch err.(type) {
case golangsdk.ErrDefault404:
return true, nil
default:
return true, err
}
})
}

func emptyIfNil(src map[string]string) map[string]string {
if src == nil {
return make(map[string]string)
}
return src
}

// CreateNodes create `count` nodes and wait until they are active
func (c *client) CreateNodes(opts *CreateNodesOpts, count int) (*nodes.Nodes, error) {
var base64PreInstall, base64PostInstall string
if opts.PreInstall != "" {
base64PreInstall = installScriptEncode(opts.PreInstall)
}
if opts.PostInstall != "" {
base64PostInstall = installScriptEncode(opts.PostInstall)
}
if opts.Os == "" {
opts.Os = EulerOSVersion
}
createOpts := nodes.CreateOpts{
Kind: "Node",
ApiVersion: "v3",
Metadata: nodes.CreateMetaData{
Name: opts.Name,
Labels: opts.Labels,
Annotations: opts.Annotations,
},
Spec: nodes.Spec{
Flavor: opts.FlavorID,
Az: opts.AvailabilityZone,
Os: opts.Os,
Login: nodes.LoginSpec{SshKey: opts.KeyPair},
RootVolume: opts.RootVolume,
DataVolumes: opts.DataVolumes,
PublicIP: nodes.PublicIPSpec{
Count: opts.EipCount,
Eip: nodes.EipSpec{
IpType: opts.EipOpts.IPType,
Bandwidth: nodes.BandwidthOpts{
Size: opts.EipOpts.BandwidthSize,
ShareType: opts.EipOpts.BandwidthType,
},
},
},
BillingMode: opts.BillingMode,
Count: count,
ExtendParam: nodes.ExtendParam{
ChargingMode: opts.ChargingMode,
EcsPerformanceType: opts.PerformanceType,
MaxPods: opts.MaxPods,
OrderID: opts.OrderID,
ProductID: opts.ProductID,
PublicKey: opts.PublicKey,
PreInstall: base64PreInstall,
PostInstall: base64PostInstall,
},
},
}

clusterID := opts.ClusterID
if err := c.waitForCluster(clusterID); err != nil {
return nil, err
}
created, err := nodes.Create(c.CCE, clusterID, createOpts).Extract()
if err != nil {
return nil, err
}
nodeIDs := created.Metadata.Id
log.Printf("Waiting for OpenTelekomCloud CCE nodes (%s) to become available", nodeIDs)
err = c.waitForNodesActive(clusterID, nodeIDs).ErrorOrNil()
return created, err
}

func (c *client) DeleteNodes(clusterID, nodeIDs string) error {
ids := splitNodeIDs(nodeIDs)
var errChan = make(chan error, len(ids))
for _, nodeID := range ids {
go func(node string) {
errChan <- nodes.Delete(c.CCE, clusterID, node).Err
}(nodeID)
}
var err *multierror.Error
for range ids {
err = multierror.Append(err, <-errChan)
}
log.Printf("Waiting for OpenTelekomCloud CCE nodes (%s) to be deleted", nodeIDs)
err = multierror.Append(err, c.waitForNodesDeleted(clusterID, nodeIDs))
return err.ErrorOrNil()
}
Loading

0 comments on commit 2641ca1

Please sign in to comment.