Merge pull request #8246 from bittopaz/ali-fix-lc

Alicloud: support modification of LaunchConfiguration
This commit is contained in:
Kubernetes Prow Robot 2020-02-10 19:32:01 -08:00 committed by GitHub
commit 4cea7ffe66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 205 additions and 39 deletions

View File

@ -118,7 +118,7 @@ func (b *ScalingGroupModelBuilder) Build(c *fi.ModelBuilderContext) error {
SecurityGroup: b.LinkToSecurityGroup(ig.Spec.Role),
RAMRole: b.LinkToRAMRole(ig.Spec.Role),
ImageId: s(ig.Spec.Image),
ImageID: s(ig.Spec.Image),
InstanceType: s(instanceType),
SystemDiskSize: i(int(volumeSize)),
SystemDiskCategory: s(volumeType),

View File

@ -39,6 +39,7 @@ go_library(
importpath = "k8s.io/kops/upup/pkg/fi/cloudup/alitasks",
visibility = ["//visibility:public"],
deps = [
"//pkg/featureflag:go_default_library",
"//pkg/pki:go_default_library",
"//upup/pkg/fi:go_default_library",
"//upup/pkg/fi/cloudup/aliup:go_default_library",

View File

@ -20,12 +20,17 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"math"
"sort"
"strconv"
"strings"
"time"
"github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ess"
"k8s.io/klog"
"k8s.io/kops/pkg/featureflag"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/aliup"
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
@ -33,12 +38,27 @@ import (
//go:generate fitask -type=LaunchConfiguration
type LaunchConfiguration struct {
Lifecycle *fi.Lifecycle
Name *string
ConfigurationId *string
const dateFormat = "2006-01-02T15:04Z"
ImageId *string
// defaultRetainLaunchConfigurationCount is the number of launch configurations (matching the name prefix) that we should
// keep, we delete older ones
var defaultRetainLaunchConfigurationCount = 3
// RetainLaunchConfigurationCount returns the number of launch configurations to keep
func RetainLaunchConfigurationCount() int {
if featureflag.KeepLaunchConfigurations.Enabled() {
return math.MaxInt32
}
return defaultRetainLaunchConfigurationCount
}
// LaunchConfiguration is the specification for a launch configuration
type LaunchConfiguration struct {
Lifecycle *fi.Lifecycle
ID *string
Name *string
ImageID *string
InstanceType *string
SystemDiskSize *int
SystemDiskCategory *string
@ -55,7 +75,7 @@ type LaunchConfiguration struct {
var _ fi.CompareWithID = &LaunchConfiguration{}
func (l *LaunchConfiguration) CompareWithID() *string {
return l.ConfigurationId
return l.ID
}
func (l *LaunchConfiguration) Find(c *fi.Context) (*LaunchConfiguration, error) {
@ -64,40 +84,28 @@ func (l *LaunchConfiguration) Find(c *fi.Context) (*LaunchConfiguration, error)
return nil, nil
}
cloud := c.Cloud.(aliup.ALICloud)
describeScalingConfigurationsArgs := &ess.DescribeScalingConfigurationsArgs{
RegionId: common.Region(cloud.Region()),
}
if l.ScalingGroup != nil && l.ScalingGroup.ScalingGroupId != nil {
describeScalingConfigurationsArgs.ScalingGroupId = fi.StringValue(l.ScalingGroup.ScalingGroupId)
}
configList, _, err := cloud.EssClient().DescribeScalingConfigurations(describeScalingConfigurationsArgs)
configurations, err := l.findLaunchConfigurations(c)
if err != nil {
return nil, fmt.Errorf("error finding ScalingConfigurations: %v", err)
}
// No ScalingConfigurations with specified Name.
if len(configList) == 0 {
if len(configurations) == 0 {
klog.V(2).Infof("can't found matching LaunchConfiguration: %q", fi.StringValue(l.Name))
return nil, nil
}
if len(configList) > 1 {
return nil, fmt.Errorf("found multiple LaunchConfiguration with name: %q", fi.StringValue(l.Name))
}
klog.V(2).Infof("found matching LaunchConfiguration: %q", fi.StringValue(l.Name))
lc := configList[0]
lc := configurations[len(configurations)-1]
klog.V(2).Infof("found matching LaunchConfiguration: %q", lc.ScalingConfigurationName)
actual := &LaunchConfiguration{
ImageId: fi.String(lc.ImageId),
Name: l.Name,
ID: fi.String(lc.ScalingConfigurationId),
ImageID: fi.String(lc.ImageId),
InstanceType: fi.String(lc.InstanceType),
SystemDiskSize: fi.Int(lc.SystemDiskSize),
SystemDiskCategory: fi.String(string(lc.SystemDiskCategory)),
ConfigurationId: fi.String(lc.ScalingConfigurationId),
Name: fi.String(lc.ScalingConfigurationName),
}
if lc.KeyPairName != "" {
@ -139,6 +147,64 @@ func (l *LaunchConfiguration) Find(c *fi.Context) (*LaunchConfiguration, error)
return actual, nil
}
func (l *LaunchConfiguration) findLaunchConfigurations(c *fi.Context) ([]*ess.ScalingConfigurationItemType, error) {
cloud := c.Cloud.(aliup.ALICloud)
prefix := *l.Name + "-"
var configurations []*ess.ScalingConfigurationItemType
pageNumber := 1
pageSize := 50
for {
describeSCArgs := &ess.DescribeScalingConfigurationsArgs{
RegionId: common.Region(cloud.Region()),
Pagination: common.Pagination{
PageNumber: pageNumber,
PageSize: pageSize,
},
}
if l.ScalingGroup != nil && l.ScalingGroup.ScalingGroupId != nil {
describeSCArgs.ScalingGroupId = fi.StringValue(l.ScalingGroup.ScalingGroupId)
}
configs, _, err := cloud.EssClient().DescribeScalingConfigurations(describeSCArgs)
if err != nil {
return nil, fmt.Errorf("error finding ScalingConfigurations: %v", err)
}
for _, c := range configs {
if strings.HasPrefix(c.ScalingConfigurationName, prefix) {
// Verify the CreationTime is parseble here, so we can ignore errors when sorting
_, err := time.Parse(dateFormat, c.CreationTime)
if err != nil {
return nil, fmt.Errorf("error parse CreationTime %s: %v", c.CreationTime, err)
}
cc := c // Copy the pointer during iteration
configurations = append(configurations, &cc)
}
}
if len(configs) < pageSize {
break
} else {
pageNumber++
}
klog.V(4).Infof("Describing ScalingConfigurations page %v...", pageNumber)
}
sort.Slice(configurations, func(i, j int) bool {
ti, _ := time.Parse(dateFormat, configurations[i].CreationTime)
tj, _ := time.Parse(dateFormat, configurations[j].CreationTime)
return ti.UnixNano() < tj.UnixNano()
})
return configurations, nil
}
func (l *LaunchConfiguration) Run(c *fi.Context) error {
c.Cloud.(aliup.ALICloud).AddClusterTags(l.Tags)
return fi.DefaultDeltaRunMethod(l, c)
@ -146,14 +212,14 @@ func (l *LaunchConfiguration) Run(c *fi.Context) error {
func (_ *LaunchConfiguration) CheckChanges(a, e, changes *LaunchConfiguration) error {
//Configuration can not be modified, we need to create a new one
if e.Name == nil {
return fi.RequiredField("Name")
}
if e.ImageId == nil {
if e.ImageID == nil {
return fi.RequiredField("ImageId")
}
if e.InstanceType == nil {
return fi.RequiredField("InstanceType")
}
@ -162,13 +228,13 @@ func (_ *LaunchConfiguration) CheckChanges(a, e, changes *LaunchConfiguration) e
}
func (_ *LaunchConfiguration) RenderALI(t *aliup.ALIAPITarget, a, e, changes *LaunchConfiguration) error {
klog.V(2).Infof("Creating LaunchConfiguration for ScalingGroup:%q", fi.StringValue(e.ScalingGroup.ScalingGroupId))
launchConfigurationName := *e.Name + "-" + fi.BuildTimestampString()
klog.V(2).Infof("Creating LaunchConfiguration with name:%q", launchConfigurationName)
createScalingConfiguration := &ess.CreateScalingConfigurationArgs{
ScalingConfigurationName: launchConfigurationName,
ScalingGroupId: fi.StringValue(e.ScalingGroup.ScalingGroupId),
ScalingConfigurationName: fi.StringValue(e.Name),
ImageId: fi.StringValue(e.ImageId),
ImageId: fi.StringValue(e.ImageID),
InstanceType: fi.StringValue(e.InstanceType),
SecurityGroupId: fi.StringValue(e.SecurityGroup.SecurityGroupId),
SystemDisk_Size: common.UnderlineString(strconv.Itoa(fi.IntValue(e.SystemDiskSize))),
@ -194,7 +260,7 @@ func (_ *LaunchConfiguration) RenderALI(t *aliup.ALIAPITarget, a, e, changes *La
if e.Tags != nil {
tagItem, err := json.Marshal(e.Tags)
if err != nil {
return fmt.Errorf("error rendering ScalingLaunchConfiguration Tags: %v", err)
return fmt.Errorf("error rendering LaunchConfiguration Tags: %v", err)
}
createScalingConfiguration.Tags = string(tagItem)
}
@ -203,12 +269,11 @@ func (_ *LaunchConfiguration) RenderALI(t *aliup.ALIAPITarget, a, e, changes *La
if err != nil {
return fmt.Errorf("error creating scalingConfiguration: %v", err)
}
e.ConfigurationId = fi.String(createScalingConfigurationResponse.ScalingConfigurationId)
e.ID = fi.String(createScalingConfigurationResponse.ScalingConfigurationId)
// Disable ScalingGroup, used to bind scalingConfig, we should execute EnableScalingGroup in the task LaunchConfiguration
// If the ScalingGroup is active, we can not execute EnableScalingGroup.
if e.ScalingGroup.Active != nil && fi.BoolValue(e.ScalingGroup.Active) {
klog.V(2).Infof("Disabling LoadBalancer with id:%q", fi.StringValue(e.ScalingGroup.ScalingGroupId))
disableScalingGroupArgs := &ess.DisableScalingGroupArgs{
@ -223,7 +288,7 @@ func (_ *LaunchConfiguration) RenderALI(t *aliup.ALIAPITarget, a, e, changes *La
//Enable this configuration
enableScalingGroupArgs := &ess.EnableScalingGroupArgs{
ScalingGroupId: fi.StringValue(e.ScalingGroup.ScalingGroupId),
ActiveScalingConfigurationId: fi.StringValue(e.ConfigurationId),
ActiveScalingConfigurationId: fi.StringValue(e.ID),
}
klog.V(2).Infof("Enabling new LaunchConfiguration of LoadBalancer with id:%q", fi.StringValue(e.ScalingGroup.ScalingGroupId))
@ -257,7 +322,7 @@ func (_ *LaunchConfiguration) RenderTerraform(t *terraform.TerraformTarget, a, e
userData := base64.StdEncoding.EncodeToString(data)
tf := &terraformLaunchConfiguration{
ImageID: e.ImageId,
ImageID: e.ImageID,
InstanceType: e.InstanceType,
SystemDiskCategory: e.SystemDiskCategory,
UserData: &userData,
@ -274,3 +339,68 @@ func (_ *LaunchConfiguration) RenderTerraform(t *terraform.TerraformTarget, a, e
func (l *LaunchConfiguration) TerraformLink() *terraform.Literal {
return terraform.LiteralProperty("alicloud_ess_scaling_configuration", fi.StringValue(l.Name), "id")
}
// deleteLaunchConfiguration tracks a LaunchConfiguration that we're going to delete
// It implements fi.Deletion
type deleteLaunchConfiguration struct {
lc *ess.ScalingConfigurationItemType
}
var _ fi.Deletion = &deleteLaunchConfiguration{}
func (d *deleteLaunchConfiguration) TaskName() string {
return "LaunchConfiguration"
}
func (d *deleteLaunchConfiguration) Item() string {
return d.lc.ScalingConfigurationName
}
func (d *deleteLaunchConfiguration) Delete(t fi.Target) error {
klog.V(2).Infof("deleting launch configuration %v", d)
aliTarget, ok := t.(*aliup.ALIAPITarget)
if !ok {
return fmt.Errorf("unexpected target type for deletion: %T", t)
}
request := &ess.DeleteScalingConfigurationArgs{
ScalingConfigurationId: d.lc.ScalingConfigurationId,
}
id := request.ScalingConfigurationId
klog.V(2).Infof("Calling ESS DeleteScalingConfiguration for %s", id)
_, err := aliTarget.Cloud.EssClient().DeleteScalingConfiguration(request)
if err != nil {
return fmt.Errorf("error deleting ESS LaunchConfiguration %s: %v", id, err)
}
return nil
}
func (d *deleteLaunchConfiguration) String() string {
return d.TaskName() + "-" + d.Item()
}
func (e *LaunchConfiguration) FindDeletions(c *fi.Context) ([]fi.Deletion, error) {
var removals []fi.Deletion
configurations, err := e.findLaunchConfigurations(c)
if err != nil {
return nil, err
}
if len(configurations) <= RetainLaunchConfigurationCount() {
return nil, nil
}
configurations = configurations[:len(configurations)-RetainLaunchConfigurationCount()]
for _, configuration := range configurations {
removals = append(removals, &deleteLaunchConfiguration{lc: configuration})
}
klog.V(2).Infof("will delete launch configurations: %v", removals)
return removals, nil
}

View File

@ -20,6 +20,7 @@ import (
"errors"
"fmt"
"os"
"time"
"k8s.io/klog"
@ -142,7 +143,41 @@ func (c *aliCloudImplementation) DeleteGroup(g *cloudinstances.CloudInstanceGrou
}
func (c *aliCloudImplementation) DeleteInstance(i *cloudinstances.CloudInstanceGroupMember) error {
return errors.New("DeleteInstance not implemented on aliCloud")
id := i.ID
if id == "" {
return fmt.Errorf("id was not set on CloudInstanceGroupMember: %v", i)
}
if err := c.EcsClient().StopInstance(id, false); err != nil {
return fmt.Errorf("error stopping instance %q: %v", id, err)
}
// Wait for 3 min to stop the instance
for i := 0; i < 36; i++ {
ins, err := c.EcsClient().DescribeInstanceAttribute(id)
if err != nil {
return fmt.Errorf("error describing instance %q: %v", id, err)
}
klog.V(8).Infof("stopping Alicloud ecs instance %q, current Status: %q", id, ins.Status)
time.Sleep(time.Second * 5)
if ins.Status == ecs.Stopped {
break
}
if i == 35 {
return fmt.Errorf("fail to stop ecs instance %s in 3 mins", id)
}
}
if err := c.EcsClient().DeleteInstance(id); err != nil {
return fmt.Errorf("error deleting instance %q: %v", id, err)
}
klog.V(8).Infof("deleted Alicloud ecs instance %q", id)
return nil
}
func (c *aliCloudImplementation) FindVPCInfo(id string) (*fi.VPCInfo, error) {