add wait utils and advance client
This commit is contained in:
parent
32ae252365
commit
ddc03f8b2e
|
@ -0,0 +1,178 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/yunify/qingcloud-sdk-go/config"
|
||||
"github.com/yunify/qingcloud-sdk-go/service"
|
||||
)
|
||||
|
||||
const (
|
||||
INSTANCE_STATUS_PENDING = "pending"
|
||||
INSTANCE_STATUS_RUNNING = "running"
|
||||
INSTANCE_STATUS_STOPPED = "stopped"
|
||||
INSTANCE_STATUS_SUSPENDED = "suspended"
|
||||
INSTANCE_STATUS_TERMINATED = "terminated"
|
||||
INSTANCE_STATUS_CEASED = "ceased"
|
||||
|
||||
defaultOpTimeout = 180*time.Second
|
||||
)
|
||||
|
||||
type QingCloudClient interface {
|
||||
RunInstance(arg *service.RunInstancesInput) (*service.Instance, error)
|
||||
DescribeInstance(instanceID string) (*service.Instance, error)
|
||||
StartInstance(instanceID string) error
|
||||
StopInstance(instanceID string, force bool) error
|
||||
RestartInstance(instanceID string) error
|
||||
TerminateInstance(instanceID string) error
|
||||
WaitInstanceStatus(instanceID string, status string) (*service.Instance, error)
|
||||
}
|
||||
|
||||
func NewClient(config *config.Config, zone string) (QingCloudClient, error) {
|
||||
qcService, err := service.Init(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
instanceService, err := qcService.Instance(zone)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
jobService, err := qcService.Job(zone)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := &client{
|
||||
InstanceService: instanceService,
|
||||
JobService: jobService,
|
||||
OperationTimeout: defaultOpTimeout,
|
||||
zone: zone,
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
type client struct {
|
||||
InstanceService *service.InstanceService
|
||||
JobService *service.JobService
|
||||
OperationTimeout time.Duration
|
||||
WaitInterval time.Duration
|
||||
zone string
|
||||
}
|
||||
|
||||
func (c *client) RunInstance(input *service.RunInstancesInput) (*service.Instance, error) {
|
||||
|
||||
output, err := c.InstanceService.RunInstances(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(output.Instances) == 0 {
|
||||
return nil, errors.New("Create instance response error.")
|
||||
}
|
||||
jobID := output.JobID
|
||||
jobErr := c.waitJob(*jobID)
|
||||
if jobErr != nil {
|
||||
return nil, jobErr
|
||||
}
|
||||
instanceID := *output.Instances[0]
|
||||
_, waitErr := c.WaitInstanceStatus(instanceID, INSTANCE_STATUS_RUNNING)
|
||||
if waitErr != nil {
|
||||
return nil, waitErr
|
||||
}
|
||||
ins, waitErr := c.waitInstanceNetwork(instanceID)
|
||||
if waitErr != nil {
|
||||
return nil, waitErr
|
||||
}
|
||||
return ins, nil
|
||||
}
|
||||
|
||||
func (c *client) DescribeInstance(instanceID string) (*service.Instance, error) {
|
||||
input := &service.DescribeInstancesInput{Instances: []*string{&instanceID}}
|
||||
output, err := c.InstanceService.DescribeInstances(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(output.InstanceSet) == 0 {
|
||||
return nil, fmt.Errorf("Instance with id [%s] not exist.", instanceID)
|
||||
}
|
||||
return output.InstanceSet[0], nil
|
||||
}
|
||||
|
||||
func (c *client) StartInstance(instanceID string) error {
|
||||
input := &service.StartInstancesInput{Instances: []*string{&instanceID}}
|
||||
output, err := c.InstanceService.StartInstances(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jobID := output.JobID
|
||||
waitErr := c.waitJob(*jobID)
|
||||
if waitErr != nil {
|
||||
return waitErr
|
||||
}
|
||||
_, err = c.WaitInstanceStatus(instanceID, INSTANCE_STATUS_RUNNING)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *client) StopInstance(instanceID string, force bool) error {
|
||||
var forceParam int
|
||||
if force {
|
||||
forceParam = 1
|
||||
} else {
|
||||
forceParam = 0
|
||||
}
|
||||
input := &service.StopInstancesInput{Instances: []*string{&instanceID}, Force: &forceParam}
|
||||
output, err := c.InstanceService.StopInstances(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jobID := output.JobID
|
||||
waitErr := c.waitJob(*jobID)
|
||||
if waitErr != nil {
|
||||
return waitErr
|
||||
}
|
||||
_, err = c.WaitInstanceStatus(instanceID, INSTANCE_STATUS_STOPPED)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *client) RestartInstance(instanceID string) error {
|
||||
input := &service.RestartInstancesInput{Instances: []*string{&instanceID}}
|
||||
output, err := c.InstanceService.RestartInstances(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jobID := output.JobID
|
||||
waitErr := c.waitJob(*jobID)
|
||||
if waitErr != nil {
|
||||
return waitErr
|
||||
}
|
||||
_, err = c.WaitInstanceStatus(instanceID, INSTANCE_STATUS_RUNNING)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *client) TerminateInstance(instanceID string) error {
|
||||
input := &service.TerminateInstancesInput{Instances: []*string{&instanceID}}
|
||||
output, err := c.InstanceService.TerminateInstances(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jobID := output.JobID
|
||||
waitErr := c.waitJob(*jobID)
|
||||
if waitErr != nil {
|
||||
return waitErr
|
||||
}
|
||||
_, err = c.WaitInstanceStatus(instanceID, INSTANCE_STATUS_TERMINATED)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *client) waitJob(jobID string) error {
|
||||
return WaitJob(c.JobService, jobID, c.OperationTimeout, c.WaitInterval)
|
||||
}
|
||||
|
||||
func (c *client) WaitInstanceStatus(instanceID string, status string) (*service.Instance, error) {
|
||||
return WaitInstanceStatus(c.InstanceService, instanceID, status, c.OperationTimeout, c.WaitInterval)
|
||||
}
|
||||
|
||||
func (c *client) waitInstanceNetwork(instanceID string) (*service.Instance, error) {
|
||||
return WaitInstanceNetwork(c.InstanceService, instanceID, c.OperationTimeout, c.WaitInterval)
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
"github.com/yunify/qingcloud-sdk-go/logger"
|
||||
"github.com/yunify/qingcloud-sdk-go/service"
|
||||
"github.com/yunify/qingcloud-sdk-go/utils"
|
||||
)
|
||||
|
||||
func WaitJob(jobService *service.JobService, jobID string, timeout time.Duration, waitInterval time.Duration) error {
|
||||
logger.Debug("Waiting for Job [%s] finished", jobID)
|
||||
return utils.WaitForSpecificOrError(func() (bool, error) {
|
||||
input := &service.DescribeJobsInput{Jobs: []*string{&jobID}}
|
||||
output, err := jobService.DescribeJobs(input)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(output.JobSet) == 0 {
|
||||
return false, fmt.Errorf("Can not find job [%s]", jobID)
|
||||
}
|
||||
j := output.JobSet[0]
|
||||
if j.Status == nil {
|
||||
logger.Error("Job [%s] status is nil ", jobID)
|
||||
return false, nil
|
||||
}
|
||||
if *j.Status == "working" || *j.Status == "pending" {
|
||||
return false, nil
|
||||
}
|
||||
if *j.Status == "successful" {
|
||||
return true, nil
|
||||
}
|
||||
if *j.Status == "failed" {
|
||||
return false, fmt.Errorf("Job [%s] failed", jobID)
|
||||
}
|
||||
logger.Error("Unknow status [%s] for job [%s]", *j.Status, jobID)
|
||||
return false, nil
|
||||
}, timeout, waitInterval)
|
||||
}
|
||||
|
||||
func describeInstance(instanceService *service.InstanceService, instanceID string) (*service.Instance, error) {
|
||||
input := &service.DescribeInstancesInput{Instances: []*string{&instanceID}}
|
||||
output, err := instanceService.DescribeInstances(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(output.InstanceSet) == 0 {
|
||||
return nil, fmt.Errorf("Instance with id [%s] not exist.", instanceID)
|
||||
}
|
||||
return output.InstanceSet[0], nil
|
||||
}
|
||||
|
||||
func WaitInstanceStatus(instanceService *service.InstanceService, instanceID string, status string, timeout time.Duration, waitInterval time.Duration) (ins *service.Instance, err error) {
|
||||
logger.Debug("Waiting for Instance [%s] status [%s] ", instanceID, status)
|
||||
errorTimes := 0
|
||||
err = utils.WaitForSpecificOrError(func() (bool, error) {
|
||||
i, err := describeInstance(instanceService, instanceID)
|
||||
if err != nil {
|
||||
logger.Error("DescribeInstance [%s] error : [%s]", instanceID, err.Error())
|
||||
errorTimes += 1
|
||||
if errorTimes > 3 {
|
||||
return false, err
|
||||
} else {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
if i.Status != nil && *i.Status == status {
|
||||
if i.TransitionStatus != nil && *i.TransitionStatus != "" {
|
||||
//wait transition to finished
|
||||
return false, nil
|
||||
}
|
||||
logger.Debug("Instance [%s] status is [%s] ", instanceID, *i.Status)
|
||||
ins = i
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}, timeout, waitInterval)
|
||||
return
|
||||
}
|
||||
|
||||
func WaitInstanceNetwork(instanceService *service.InstanceService, instanceID string, timeout time.Duration, waitInterval time.Duration) (ins *service.Instance, err error) {
|
||||
logger.Debug("Waiting for IP address to be assigned to Instance [%s]", instanceID)
|
||||
err = utils.WaitForSpecificOrError(func() (bool, error) {
|
||||
i, err := describeInstance(instanceService, instanceID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(i.VxNets) == 0 || i.VxNets[0].PrivateIP == nil || *i.VxNets[0].PrivateIP == "" {
|
||||
return false, nil
|
||||
}
|
||||
ins = i
|
||||
logger.Debug("Instance [%s] get IP address [%s]", instanceID, *ins.VxNets[0].PrivateIP)
|
||||
return true, nil
|
||||
}, timeout, waitInterval)
|
||||
return
|
||||
}
|
||||
|
||||
func describeLoadBalancer(lbService *service.LoadBalancerService, loadBalancerID string) (*service.LoadBalancer, error) {
|
||||
output, err := lbService.DescribeLoadBalancers(&service.DescribeLoadBalancersInput{
|
||||
LoadBalancers: []*string{&loadBalancerID},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(output.LoadBalancerSet) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return output.LoadBalancerSet[0], nil
|
||||
}
|
||||
|
||||
func WaitLoadBalancerStatus(lbService *service.LoadBalancerService, loadBalancerID string, status string, timeout time.Duration, waitInterval time.Duration) (lb *service.LoadBalancer, err error) {
|
||||
logger.Debug("Waiting for LoadBalancer [%s] status [%s] ", loadBalancerID, status)
|
||||
errorTimes := 0
|
||||
err = utils.WaitForSpecificOrError(func() (bool, error) {
|
||||
i, err := describeLoadBalancer(lbService, loadBalancerID)
|
||||
if err != nil {
|
||||
logger.Error("DescribeLoadBalancer [%s] error : [%s]", loadBalancerID, err.Error())
|
||||
errorTimes += 1
|
||||
if errorTimes > 3 {
|
||||
return false, err
|
||||
} else {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
if i.Status != nil && *i.Status == status {
|
||||
if i.TransitionStatus != nil && *i.TransitionStatus != "" {
|
||||
//wait transition to finished
|
||||
return false, nil
|
||||
}
|
||||
lb = i
|
||||
logger.Debug("LoadBalancer [%s] status is [%s] ", loadBalancerID, *i.Status)
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}, timeout, waitInterval)
|
||||
return
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func WaitForSpecificOrError(f func() (bool, error), timeout time.Duration, waitInterval time.Duration) error {
|
||||
ticker := time.NewTicker(waitInterval)
|
||||
defer ticker.Stop()
|
||||
timer := time.NewTimer(timeout)
|
||||
defer timer.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
stop, err := f()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if stop {
|
||||
return nil
|
||||
}
|
||||
case <-timer.C:
|
||||
return fmt.Errorf("Wait timeout [%s] ", timeout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WaitForSpecific(f func() bool, timeout time.Duration, waitInterval time.Duration) error {
|
||||
return WaitForSpecificOrError(func() (bool, error) {
|
||||
return f(), nil
|
||||
}, timeout, waitInterval)
|
||||
}
|
||||
|
||||
func WaitFor(f func() bool) error {
|
||||
return WaitForSpecific(f, 180*time.Second, 3*time.Second)
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func TestWaitForSpecificOrError(t *testing.T){
|
||||
|
||||
waitInterval := 100*time.Millisecond
|
||||
timeout := 1000*time.Millisecond
|
||||
times := 0
|
||||
err := WaitForSpecificOrError(func() (bool, error) {
|
||||
times += 1
|
||||
println("times", times)
|
||||
if times == 3 {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}, timeout, waitInterval)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 3, times)
|
||||
|
||||
times = 0
|
||||
err = WaitForSpecificOrError(func() (bool, error) {
|
||||
times += 1
|
||||
println("times", times)
|
||||
if times == 3 {
|
||||
return false, errors.New("error")
|
||||
}
|
||||
return false, nil
|
||||
}, timeout, waitInterval)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, 3, times)
|
||||
|
||||
times = 0
|
||||
err = WaitForSpecificOrError(func() (bool, error) {
|
||||
times += 1
|
||||
println("times", times)
|
||||
return false, nil
|
||||
}, timeout, waitInterval)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, 10, times)
|
||||
}
|
Loading…
Reference in New Issue