add wait utils and advance client

This commit is contained in:
jolestar 2017-01-17 17:24:29 +08:00 committed by Aspire
parent 32ae252365
commit ddc03f8b2e
4 changed files with 401 additions and 0 deletions

178
client/client.go Normal file
View File

@ -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)
}

138
client/util.go Normal file
View File

@ -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
}

39
utils/wait.go Normal file
View File

@ -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)
}

46
utils/wait_test.go Normal file
View File

@ -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)
}