From 8314f1c5baa58cac5edf1e35252cd59d1ec1cd94 Mon Sep 17 00:00:00 2001 From: cuisongliu Date: Wed, 29 Dec 2021 17:52:55 +0800 Subject: [PATCH] [WIP] support Infra huawei (#772) * refactor(dev): huawei feature * refactor(dev): huawei feature VPC feature * refactor(dev): types consts to infra module * refactor(dev): add deepCopy * refactor(dev): huawei feature lic --- Makefile | 8 +- go.mod | 3 +- go.sum | 3 + pkg/infra/aliyun/ali_const.go | 30 +- pkg/infra/aliyun/ali_ecs.go | 15 +- pkg/infra/aliyun/ali_image.go | 6 +- pkg/infra/aliyun/ali_provider.go | 23 +- pkg/infra/aliyun/ali_vpc.go | 16 +- pkg/infra/huawei/hw_const.go | 85 ++++++ pkg/infra/huawei/hw_ecs.go | 338 +++++++++++++++++++++ pkg/infra/huawei/hw_image.go | 206 +++++++++++++ pkg/infra/huawei/hw_provider.go | 279 +++++++++++++++++ pkg/infra/huawei/hw_vpc.go | 218 +++++++++++++ pkg/infra/huawei/retry.go | 111 +++++++ pkg/infra/infra.go | 28 +- pkg/infra/infra_test.go | 88 ++++-- pkg/types/v1beta1/defaults.go | 52 ++++ pkg/types/v1beta1/types.go | 32 +- pkg/types/v1beta1/zz_generated.deepcopy.go | 55 ++++ pkg/types/validation/validation.go | 19 +- pkg/utils/strings.go | 9 + 21 files changed, 1536 insertions(+), 88 deletions(-) create mode 100644 pkg/infra/huawei/hw_const.go create mode 100644 pkg/infra/huawei/hw_ecs.go create mode 100644 pkg/infra/huawei/hw_image.go create mode 100644 pkg/infra/huawei/hw_provider.go create mode 100644 pkg/infra/huawei/hw_vpc.go create mode 100644 pkg/infra/huawei/retry.go diff --git a/Makefile b/Makefile index a783fb177..760df3f01 100644 --- a/Makefile +++ b/Makefile @@ -75,10 +75,14 @@ generator-contributors: git log --format='%aN <%aE>' | sort -uf > CONTRIBUTORS +DEEPCOPY_BIN = $(shell pwd)/bin/deepcopy-gen +install-deepcopy: ## check license if not exist install go-lint tools + $(call go-get-tool,$(DEEPCOPY_BIN),k8s.io/code-generator/cmd/deepcopy-gen@latest) + HEAD_FILE := hack/template/boilerplate.go.txt INPUT_DIR := github.com/fanux/sealos/pkg/types/v1beta1 -deepcopy: - deepcopy-gen \ +deepcopy:install-deepcopy + $(DEEPCOPY_BIN) \ --input-dirs="$(INPUT_DIR)" \ -O zz_generated.deepcopy \ --go-header-file "$(HEAD_FILE)" \ diff --git a/go.mod b/go.mod index 00bda2d9b..88aec6d45 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect github.com/dustin/go-humanize v1.0.0 // indirect github.com/google/uuid v1.1.2 // indirect + github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.0.72 github.com/mitchellh/go-homedir v1.1.0 github.com/onsi/ginkgo v1.11.0 github.com/onsi/gomega v1.7.0 @@ -24,7 +25,7 @@ require ( golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 google.golang.org/protobuf v1.25.0 // indirect gopkg.in/ini.v1 v1.61.0 // indirect - gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/api v0.18.0 k8s.io/apimachinery v0.18.0 k8s.io/client-go v0.18.0 diff --git a/go.sum b/go.sum index 248d84fe0..e8a2d4371 100644 --- a/go.sum +++ b/go.sum @@ -223,6 +223,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.0.72 h1:gfQoGtygEg2ZX5fxHyIynxGqdZvb3dfIXK5BBN2+LHI= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.0.72/go.mod h1:Z+vVu7nV/6xqti0P2evPEqhzh86ArBELsXTOU9zsnoM= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= @@ -383,6 +385,7 @@ github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdh github.com/sealyun/lvscare v1.1.2-alpha.2 h1:SlnEAXOPn5gC6l8tgvbn9fApyJaQ0ecQHSV3cEfvQYY= github.com/sealyun/lvscare v1.1.2-alpha.2/go.mod h1:FtOEdsXuYtw9Jwd/Jct25K+PcpUFSDemvF4VgNygjj0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= diff --git a/pkg/infra/aliyun/ali_const.go b/pkg/infra/aliyun/ali_const.go index e530c259d..08719fbf4 100644 --- a/pkg/infra/aliyun/ali_const.go +++ b/pkg/infra/aliyun/ali_const.go @@ -19,32 +19,22 @@ package aliyun import ( "time" - v2 "github.com/fanux/sealos/pkg/types/v1beta1" + "github.com/fanux/sealos/pkg/types/v1beta1" ) const ( Scheme = "https" - IPProtocol = "tcp" - APIServerPortRange = "6443/6443" - SSHPortRange = "22/22" - SourceCidrIP = "0.0.0.0/0" - CidrBlock = "172.16.0.0/24" - Policy = "accept" - DestinationResource = "InstanceType" - InstanceChargeType = "PostPaid" - InternetChargeType = "PayByTraffic" Product = "product" Role = "role" Arch = "arch" - Stopped = "Stopped" - AvailableTypeStatus = "WithStock" - Bandwidth = "100" AliDomain = "www.sealyun.com/" TryTimes = 10 TrySleepTime = time.Second JustGetInstanceInfo = 0 ) +const AliyunProvider v1beta1.Provider = "AliyunProvider" + type ResourceName string const ( @@ -56,29 +46,29 @@ const ( ShouldBeDeleteInstancesIDs ResourceName = "ShouldBeDeleteInstancesIDs" ) -func (r ResourceName) ClusterValue(infra v2.InfraSpec) string { +func (r ResourceName) ClusterValue(infra v1beta1.InfraSpec) string { return infra.Cluster.Annotations[string(r)] } -func (r ResourceName) ClusterSetValue(infra v2.InfraSpec, val string) { +func (r ResourceName) ClusterSetValue(infra v1beta1.InfraSpec, val string) { infra.Cluster.Annotations[string(r)] = val } -func (r ResourceName) Value(status v2.InfraStatus) string { +func (r ResourceName) Value(status v1beta1.InfraStatus) string { return status.Cluster.Annotations[string(r)] } -func (r ResourceName) SetValue(status v2.InfraStatus, val string) { +func (r ResourceName) SetValue(status v1beta1.InfraStatus, val string) { status.Cluster.Annotations[string(r)] = val } type ImageArch string -func ConvertImageArch(arch v2.Arch) ImageArch { +func ConvertImageArch(arch v1beta1.Arch) ImageArch { switch arch { - case v2.ARM64: + case v1beta1.ARM64: return "arm64" - case v2.AMD64: + case v1beta1.AMD64: return "x86_64" } return "" diff --git a/pkg/infra/aliyun/ali_ecs.go b/pkg/infra/aliyun/ali_ecs.go index 886bd6839..ad9f45c46 100644 --- a/pkg/infra/aliyun/ali_ecs.go +++ b/pkg/infra/aliyun/ali_ecs.go @@ -35,9 +35,6 @@ type Instance struct { PrimaryIPAddress string } -type EcsManager struct { -} - func (a *AliProvider) InputIPlist(host *v1beta1.Host) (ipList []string, err error) { if host == nil { return nil, err @@ -89,7 +86,7 @@ func (a *AliProvider) ChangeInstanceType(instanceID string, host *v1beta1.Host) if err != nil { return err } - if instanceStatus != Stopped { + if instanceStatus != "Stopped" { err = a.PowerOffInstance(instanceID) if err != nil { return err @@ -314,14 +311,14 @@ func (a *AliProvider) RunInstances(host *v1beta1.Host, count int) error { return nil } -func (a *AliProvider) AuthorizeSecurityGroup(securityGroupID, portRange string) bool { +func (a *AliProvider) AuthorizeSecurityGroup(securityGroupID string, exportPort v1beta1.ExportPort) bool { request := ecs.CreateAuthorizeSecurityGroupRequest() request.Scheme = Scheme request.SecurityGroupId = securityGroupID - request.IpProtocol = IPProtocol - request.PortRange = portRange - request.SourceCidrIp = SourceCidrIP - request.Policy = Policy + request.IpProtocol = string(exportPort.Protocol) + request.PortRange = exportPort.PortRange + request.SourceCidrIp = exportPort.CidrIP + request.Policy = "accept" response := ecs.CreateAuthorizeSecurityGroupResponse() err := a.RetryEcsRequest(request, response) diff --git a/pkg/infra/aliyun/ali_image.go b/pkg/infra/aliyun/ali_image.go index 951c05f45..d3bcc3e48 100644 --- a/pkg/infra/aliyun/ali_image.go +++ b/pkg/infra/aliyun/ali_image.go @@ -180,8 +180,8 @@ func (a *AliProvider) GetAvailableResource(host *v1beta1.Host, systemCategory, d request.Scheme = Scheme request.RegionId = a.Infra.Status.Cluster.RegionID request.ZoneId = a.Infra.Status.Cluster.ZoneID - request.DestinationResource = DestinationResource - request.InstanceChargeType = InstanceChargeType + request.DestinationResource = "InstanceType" + request.InstanceChargeType = "PostPaid" request.SpotStrategy = a.Infra.Status.Cluster.SpotStrategy request.SystemDiskCategory = systemCategory request.DataDiskCategory = dataCategory @@ -198,7 +198,7 @@ func (a *AliProvider) GetAvailableResource(host *v1beta1.Host, systemCategory, d for _, i := range response.AvailableZones.AvailableZone { for _, f := range i.AvailableResources.AvailableResource { for _, r := range f.SupportedResources.SupportedResource { - if r.StatusCategory == AvailableTypeStatus { + if r.StatusCategory == "WithStock" { instanceType = append(instanceType, r.Value) } } diff --git a/pkg/infra/aliyun/ali_provider.go b/pkg/infra/aliyun/ali_provider.go index 01feb51b2..d7ec58bdd 100644 --- a/pkg/infra/aliyun/ali_provider.go +++ b/pkg/infra/aliyun/ali_provider.go @@ -18,15 +18,15 @@ import ( "fmt" "strings" + "k8s.io/apimachinery/pkg/util/validation/field" + "github.com/fanux/sealos/pkg/utils" "k8s.io/apimachinery/pkg/util/sets" "github.com/pkg/errors" - "github.com/fanux/sealos/pkg/types/validation" - - v2 "github.com/fanux/sealos/pkg/types/v1beta1" + "github.com/fanux/sealos/pkg/types/v1beta1" "github.com/fanux/sealos/pkg/utils/logger" "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" @@ -52,7 +52,7 @@ const ( type AliProvider struct { EcsClient ecs.Client VpcClient vpc.Client - Infra *v2.Infra + Infra *v1beta1.Infra } type AliFunc func() error @@ -237,21 +237,20 @@ func (a *AliProvider) Reconcile() error { } func (a *AliProvider) Apply() error { - if err := v2.Default(a.Infra, defaultInfra); err != nil { - return err - } - if err := validation.ValidateInfra(a.Infra); len(err) != 0 { - return err.ToAggregate() - } return a.Reconcile() } -func defaultInfra(infra *v2.Infra) error { +func DefaultInfra(infra *v1beta1.Infra) error { //https://help.aliyun.com/document_detail/63440.htm?spm=a2c4g.11186623.0.0.f5952752gkxpB7#t9856.html - if infra.Spec.Cluster.IsSeize { + if infra.Spec.Cluster.Metadata.IsSeize { infra.Status.Cluster.SpotStrategy = "SpotAsPriceGo" } else { infra.Status.Cluster.SpotStrategy = "NoSpot" } return nil } + +func DefaultValidate(infra *v1beta1.Infra) field.ErrorList { + allErrors := field.ErrorList{} + return allErrors +} diff --git a/pkg/infra/aliyun/ali_vpc.go b/pkg/infra/aliyun/ali_vpc.go index f497a8da9..5e067cf5b 100644 --- a/pkg/infra/aliyun/ali_vpc.go +++ b/pkg/infra/aliyun/ali_vpc.go @@ -68,10 +68,9 @@ func (a *AliProvider) CreateVSwitch() error { request := vpc.CreateCreateVSwitchRequest() request.Scheme = Scheme request.ZoneId = a.Infra.Status.Cluster.ZoneID - request.CidrBlock = CidrBlock + request.CidrBlock = a.Infra.Spec.Cluster.Metadata.Network.PrivateCidrIP request.VpcId = VpcID.Value(a.Infra.Status) request.RegionId = a.Infra.Status.Cluster.RegionID - //response, err := d.Client.CreateVSwitch(request) response := vpc.CreateCreateVSwitchResponse() err := a.RetryVpcRequest(request, response) if err != nil { @@ -110,11 +109,10 @@ func (a *AliProvider) CreateSecurityGroup() error { return err } - if !a.AuthorizeSecurityGroup(response.SecurityGroupId, SSHPortRange) { - return fmt.Errorf("authorize securitygroup ssh port failed") - } - if !a.AuthorizeSecurityGroup(response.SecurityGroupId, APIServerPortRange) { - return fmt.Errorf("authorize securitygroup apiserver port failed") + for _, port := range a.Infra.Spec.Cluster.Metadata.Network.ExportPorts { + if !a.AuthorizeSecurityGroup(response.SecurityGroupId, port) { + return fmt.Errorf("authorize securitygroup port: %v failed", port) + } } SecurityGroupID.SetValue(a.Infra.Status, response.SecurityGroupId) return nil @@ -197,8 +195,8 @@ func (a *AliProvider) BindEipForMaster0() error { func (a *AliProvider) allocateEipAddress() (eIP, eIPID string, err error) { request := vpc.CreateAllocateEipAddressRequest() request.Scheme = Scheme - request.Bandwidth = Bandwidth - request.InternetChargeType = InternetChargeType + request.Bandwidth = a.Infra.Spec.Cluster.Metadata.Network.Bandwidth + request.InternetChargeType = "PayByTraffic" response := vpc.CreateAllocateEipAddressResponse() err = a.RetryVpcRequest(request, response) if err != nil { diff --git a/pkg/infra/huawei/hw_const.go b/pkg/infra/huawei/hw_const.go new file mode 100644 index 000000000..4e71c2331 --- /dev/null +++ b/pkg/infra/huawei/hw_const.go @@ -0,0 +1,85 @@ +// Copyright © 2021 sealos. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package huawei + +import ( + "time" + + v2 "github.com/fanux/sealos/pkg/types/v1beta1" +) + +const ( + Scheme = "https" + IPProtocol = "tcp" + APIServerPortRange = "6443/6443" + SSHPortRange = "22/22" + SourceCidrIP = "0.0.0.0/0" + CidrBlock = "172.16.0.0/24" + Policy = "accept" + DestinationResource = "InstanceType" + InstanceChargeType = "PostPaid" + InternetChargeType = "PayByTraffic" + Product = "product" + Role = "role" + Arch = "arch" + Stopped = "Stopped" + AvailableTypeStatus = "WithStock" + Bandwidth = "100" + HwDomain = "www.sealyun.com/" + TryTimes = 10 + TrySleepTime = time.Second + JustGetInstanceInfo = 0 +) + +const HuaweiProvider v2.Provider = "HuaweiProvider" + +type ResourceName string + +const ( + EipID ResourceName = HwDomain + "EipID" + VpcID ResourceName = HwDomain + "VpcID" + VSwitchID ResourceName = HwDomain + "VSwitchID" + SecurityGroupID ResourceName = HwDomain + "SecurityGroupID" + ZoneID ResourceName = HwDomain + "ZoneID" + ShouldBeDeleteInstancesIDs ResourceName = "ShouldBeDeleteInstancesIDs" +) + +func (r ResourceName) ClusterValue(infra v2.InfraSpec) string { + return infra.Cluster.Annotations[string(r)] +} + +func (r ResourceName) ClusterSetValue(infra v2.InfraSpec, val string) { + infra.Cluster.Annotations[string(r)] = val +} + +func (r ResourceName) Value(status v2.InfraStatus) string { + return status.Cluster.Annotations[string(r)] +} + +func (r ResourceName) SetValue(status v2.InfraStatus, val string) { + status.Cluster.Annotations[string(r)] = val +} + +type ImageArch string + +func ConvertImageArch(arch v2.Arch) ImageArch { + switch arch { + case v2.ARM64: + return "arm64" + case v2.AMD64: + return "x86_64" + } + return "" +} diff --git a/pkg/infra/huawei/hw_ecs.go b/pkg/infra/huawei/hw_ecs.go new file mode 100644 index 000000000..87a341f0e --- /dev/null +++ b/pkg/infra/huawei/hw_ecs.go @@ -0,0 +1,338 @@ +// Copyright © 2021 sealos. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package huawei + +import ( + "errors" + + "github.com/fanux/sealos/pkg/utils" + "github.com/fanux/sealos/pkg/utils/logger" + v2 "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/ecs/v2" + "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/ecs/v2/model" +) + +//type Instance struct { +// CPU int +// Memory int +// InstanceID string +// PrimaryIPAddress string +//} +// + +func (a *HwProvider) GetAvailableZoneID() error { + if a.Infra.Status.Cluster.ZoneID != "" { + logger.Debug("zoneID using status value") + return nil + } + defer func() { + logger.Info("create resource success %s: %s", "GetAvailableZoneID", a.Infra.Status.Cluster.ZoneID) + }() + + if len(a.Infra.Spec.Cluster.ZoneIDs) != 0 { + a.Infra.Status.Cluster.ZoneID = a.Infra.Spec.Cluster.ZoneIDs[utils.Rand(len(a.Infra.Spec.Cluster.ZoneIDs))] + return nil + } + resp, err := a.RetryEcsRequest(&model.NovaListAvailabilityZonesRequest{}, v2.GenReqDefForNovaListAvailabilityZones()) + if err != nil { + return err + } + if zones, ok := resp.(*model.NovaListAvailabilityZonesResponse); ok { + if zones.HttpStatusCode != 200 || zones.AvailabilityZoneInfo == nil || len(*zones.AvailabilityZoneInfo) == 0 { + return errors.New("not available ZoneID ") + } + if zones.AvailabilityZoneInfo != nil { + zoneID := (*zones.AvailabilityZoneInfo)[utils.Rand(len(*zones.AvailabilityZoneInfo))].ZoneName + a.Infra.Status.Cluster.ZoneID = zoneID + } + } + return nil +} + +//func (a *HwProvider) InputIPlist(host *v1beta1.Host) (ipList []string, err error) { +// if host == nil { +// return nil, err +// } +// instances, err := a.GetInstancesInfo(host, host.Count) +// if err != nil { +// return nil, err +// } +// for _, instance := range instances { +// ipList = append(ipList, instance.PrimaryIPAddress) +// } +// return ipList, nil +//} +// +//func (a *HwProvider) GetInstanceStatus(instanceID string) (instanceStatus string, err error) { +// request := ecs.CreateDescribeInstanceStatusRequest() +// request.Scheme = Scheme +// request.InstanceId = &[]string{instanceID} +// response := ecs.CreateDescribeInstanceStatusResponse() +// err = a.RetryEcsRequest(request, response) +// if err != nil { +// return "", fmt.Errorf("get GetAvailableZoneID status failed %v , error :%v", instanceID, err) +// } +// if len(response.InstanceStatuses.InstanceStatus) == 0 { +// return "", fmt.Errorf("GetAvailableZoneID list is empty") +// } +// return response.InstanceStatuses.InstanceStatus[0].Status, nil +//} +// +//func (a *HwProvider) PowerOffInstance(instanceID string) error { +// request := ecs.CreateStopInstancesRequest() +// request.Scheme = Scheme +// request.InstanceId = &[]string{instanceID} +// +// response := ecs.CreateStopInstancesResponse() +// return a.RetryEcsRequest(request, response) +//} +// +//func (a *HwProvider) StartInstance(instanceID string) error { +// request := ecs.CreateStartInstanceRequest() +// request.Scheme = Scheme +// request.InstanceId = instanceID +// response := ecs.CreateStartInstanceResponse() +// return a.RetryEcsRequest(request, response) +//} +// +//func (a *HwProvider) ChangeInstanceType(instanceID string, host *v1beta1.Host) error { +// instanceStatus, err := a.GetInstanceStatus(instanceID) +// if err != nil { +// return err +// } +// if instanceStatus != Stopped { +// err = a.PowerOffInstance(instanceID) +// if err != nil { +// return err +// } +// } +// expectInstanceType, err := a.GetAvailableInstanceType(host) +// if err != nil { +// return err +// } +// +// request := ecs.CreateModifyInstanceSpecRequest() +// request.Scheme = Scheme +// request.InstanceId = instanceID +// response := ecs.CreateModifyInstanceSpecResponse() +// err = a.RetryEcsInstanceType(request, response, expectInstanceType, host.Roles) +// if err != nil { +// return err +// } +// return a.StartInstance(instanceID) +//} +// +//func (a *HwProvider) GetInstancesInfo(host *v1beta1.Host, expectCount int) (instances []Instance, err error) { +// var count int +// tag := make(map[string]string) +// tag[Product] = a.Infra.Name +// tag[Role] = strings.Join(host.Roles, ",") +// tag[Arch] = string(host.Arch) +// if expectCount == 0 { +// count = -1 +// } else { +// count = expectCount +// } +// instancesTags := CreateDescribeInstancesTag(tag) +// request := ecs.CreateDescribeInstancesRequest() +// request.Scheme = Scheme +// request.RegionId = a.Infra.Status.Cluster.RegionID +// request.VSwitchId = VSwitchID.Value(a.Infra.Status) +// request.SecurityGroupId = SecurityGroupID.Value(a.Infra.Status) +// request.Tag = &instancesTags +// response := ecs.CreateDescribeInstancesResponse() +// err = a.TryGetInstance(request, response, count) +// if err != nil { +// return nil, err +// } +// +// for _, i := range response.Instances.Instance { +// instances = append(instances, +// Instance{ +// CPU: i.Cpu, +// Memory: i.Memory / 1024, +// InstanceID: i.InstanceId, +// PrimaryIPAddress: i.NetworkInterfaces.NetworkInterface[0].PrimaryIpAddress}) +// } +// return +//} +// +//func (a *HwProvider) ReconcileInstances(host *v1beta1.Host, status *v1beta1.HostStatus) error { +// var instances []Instance +// var instancesIDs string +// var IPList []string +// instancesIDs = status.IDs +// switch host.ToRole() { +// case v1beta1.Master: +// if host.Count == 0 { +// return errors.New("master count not set") +// } +// case v1beta1.Node: +// if host == nil { +// return nil +// } +// } +// if host == nil { +// return errors.New("hosts not set") +// } +// var err error +// if instancesIDs != "" { +// instances, err = a.GetInstancesInfo(host, JustGetInstanceInfo) +// } +// if err != nil { +// return err +// } +// if i := host.Count; len(instances) < i { +// err = a.RunInstances(host, i-len(instances)) +// if err != nil { +// return err +// } +// ipList, err := a.InputIPlist(host) +// if err != nil { +// return err +// } +// IPList = utils.AppendIPList(IPList, ipList) +// logger.Info("get scale up IP list %v, append iplist %v, host count %d", ipList, IPList, host.Count) +// } +// +// for _, instance := range instances { +// if instance.CPU != host.CPU || instance.Memory != host.Memory { +// err = a.ChangeInstanceType(instance.InstanceID, host) +// if err != nil { +// return err +// } +// } +// } +// status.IPs = IPList +// logger.Info("reconcile %s instances success %v ", host.String(), IPList) +// return nil +//} +// +//func (a *HwProvider) DeleteInstances() error { +// instanceIDs := strings.Split(ShouldBeDeleteInstancesIDs.Value(a.Infra.Status), ",") +// if len(instanceIDs) == 0 { +// return nil +// } +// request := ecs.CreateDeleteInstancesRequest() +// request.Scheme = Scheme +// request.InstanceId = &instanceIDs +// request.Force = requests.NewBoolean(true) +// response := ecs.CreateDeleteInstancesResponse() +// err := a.RetryEcsRequest(request, response) +// if err != nil { +// return err +// } +// ShouldBeDeleteInstancesIDs.SetValue(a.Infra.Status, "") +// if v1beta1.In(a.Infra.Status.Cluster.Master0ID, instanceIDs) { +// logger.Debug("delete instance success,need delete about instance info[master0id,master0InternalIP,eip,eipID]") +// a.Infra.Status.Cluster.Master0ID = "" +// a.Infra.Status.Cluster.Master0InternalIP = "" +// a.Infra.Status.Cluster.EIP = "" +// delete(a.Infra.Status.Cluster.Annotations, string(EipID)) +// } +// return nil +//} +// +//func CreateDescribeInstancesTag(tags map[string]string) (instanceTags []ecs.DescribeInstancesTag) { +// for k, v := range tags { +// instanceTags = append(instanceTags, ecs.DescribeInstancesTag{Key: k, Value: v}) +// } +// return +//} +// +//func CreateInstanceDataDisk(dataDisks []v1beta1.Disk, category string) (instanceDisks []ecs.RunInstancesDataDisk) { +// for _, v := range dataDisks { +// instanceDisks = append(instanceDisks, +// ecs.RunInstancesDataDisk{Size: strconv.Itoa(v.Capacity), Category: category}) +// } +// return +//} +// +//func (a *HwProvider) RunInstances(host *v1beta1.Host, count int) error { +// if host == nil { +// return errors.New("host not set") +// } +// j := a.Infra.Status.FindHostsByRoles(host.Roles) +// if j == -1 { +// return fmt.Errorf("failed to get status, %v", "not find host status,pelase retry") +// } +// systemDiskSize := host.Disks[0] +// var instanceType []string +// var err error +// var imageID string +// if imageID, err = a.GetAvailableImageID(host); err != nil { +// return err +// } +// a.Infra.Status.Hosts[j].ImageID = imageID +// a.Infra.Status.Hosts[j].Arch = host.Arch +// instanceType, err = a.GetAvailableInstanceType(host) +// if err != nil { +// return err +// } +// tag := make(map[string]string) +// tag[Product] = a.Infra.Name +// tag[Role] = strings.Join(host.Roles, ",") +// tag[Arch] = string(host.Arch) +// instancesTag := CreateInstanceTag(tag) +// +// dataDisks := host.Disks[1:] +// datadisk := CreateInstanceDataDisk(dataDisks, a.Infra.Status.Hosts[j].DataCategory) +// +// request := ecs.CreateRunInstancesRequest() +// request.Scheme = Scheme +// request.ImageId = imageID +// +// request.Password = a.Infra.Spec.Cluster.AccessChannels.SSH.Passwd +// request.SecurityGroupId = SecurityGroupID.Value(a.Infra.Status) +// request.VSwitchId = VSwitchID.Value(a.Infra.Status) +// request.SystemDiskSize = strconv.Itoa(systemDiskSize.Capacity) +// request.SystemDiskCategory = a.Infra.Status.Hosts[j].SystemCategory +// request.DataDisk = &datadisk +// request.SpotStrategy = a.Infra.Status.Cluster.SpotStrategy +// request.Amount = requests.NewInteger(count) +// request.Tag = &instancesTag +// response := ecs.CreateRunInstancesResponse() +// err = a.RetryEcsInstanceType(request, response, instanceType, host.Roles) +// if err != nil { +// return err +// } +// +// instancesIDs := strings.Join(response.InstanceIdSets.InstanceIdSet, ",") +// a.Infra.Status.Hosts[j].IDs += instancesIDs +// return nil +//} +// +//func (a *HwProvider) AuthorizeSecurityGroup(securityGroupID, portRange string) bool { +// request := ecs.CreateAuthorizeSecurityGroupRequest() +// request.Scheme = Scheme +// request.SecurityGroupId = securityGroupID +// request.IpProtocol = IPProtocol +// request.PortRange = portRange +// request.SourceCidrIp = SourceCidrIP +// request.Policy = Policy +// +// response := ecs.CreateAuthorizeSecurityGroupResponse() +// err := a.RetryEcsRequest(request, response) +// if err != nil { +// logger.Error("%v", err) +// return false +// } +// return response.BaseResponse.IsSuccess() +//} +//func CreateInstanceTag(tags map[string]string) (instanceTags []ecs.RunInstancesTag) { +// for k, v := range tags { +// instanceTags = append(instanceTags, ecs.RunInstancesTag{Key: k, Value: v}) +// } +// return +//} diff --git a/pkg/infra/huawei/hw_image.go b/pkg/infra/huawei/hw_image.go new file mode 100644 index 000000000..05cc05583 --- /dev/null +++ b/pkg/infra/huawei/hw_image.go @@ -0,0 +1,206 @@ +// Copyright © 2021 sealos. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package huawei + +//func (a *HwProvider) GetAvailableImageID(host *v1beta1.Host) (string, error) { +// if host.OS.ID != "" { +// logger.Info("host tags is %v,using imageID is %s", host.Roles, host.OS.ID) +// return host.OS.ID, nil +// } +// logger.Warn("tips: imageID not set,so imageID is random.that will maybe let instanceType not find.") +// request := ecs.CreateDescribeImagesRequest() +// request.ImageOwnerAlias = "system" +// request.PageSize = "100" +// request.Architecture = string(ConvertImageArch(host.Arch)) +// request.OSType = "linux" +// response := ecs.CreateDescribeImagesResponse() +// err := a.RetryEcsRequest(request, response) +// if err != nil { +// return "", fmt.Errorf("get ImageID failed , error :%v", err) +// } +// if response.TotalCount == 0 { +// return "", fmt.Errorf("ImageID list is empty") +// } +// var images []string +// +// for _, image := range response.Images.Image { +// flag := true +// imagesName := strings.ToLower(image.Platform) +// imagesName = strings.ReplaceAll(imagesName, " ", "_") +// if strings.ToLower(host.OS.Name) != "" { +// if imagesName != strings.ToLower(host.OS.Name) { +// flag = false +// } +// } +// if strings.ToLower(host.OS.Version) != "" { +// imageID := strings.ReplaceAll(image.ImageName, imagesName, "") +// imageIDArr := strings.Split(imageID, "_") +// var versions []string +// for _, v := range imageIDArr { +// if v == string(ConvertImageArch(host.Arch)) { +// break +// } else { +// versions = append(versions, v) +// } +// } +// version := strings.Join(versions, ".") +// //7.1,7,7.3,8.7 +// if !strings.HasPrefix(version, strings.ToLower(host.OS.Version)) { +// flag = false +// } +// } +// if flag { +// logger.Debug("host tags is %v,search imageID is %s", host.Roles, image.ImageId) +// images = append(images, image.ImageId) +// } +// } +// if len(images) == 0 { +// return "", fmt.Errorf("search ImageID list is empty") +// } +// logger.Info("host tags is %v,using first imageID is %s", host.Roles, images[0]) +// return images[utils.Rand(len(images))], nil +//} +// +//func (a *HwProvider) GetDefaultDiskCategories(host *v1beta1.Host) (system []string, data []string) { +// categories := []string{"cloud", "cloud_efficiency", "cloud_ssd", "cloud_essd"} +// if host.Disks[0].Category != "" { +// system = []string{host.Disks[0].Category} +// } else { +// logger.Warn("host tags is %v,system category not set", host.Roles) +// system = categories +// } +// if len(host.Disks) > 1 { +// if host.Disks[1].Category != "" { +// data = []string{host.Disks[1].Category} +// } else { +// logger.Warn("host tags is %v,data category not set", host.Roles) +// data = categories +// } +// } +// return +//} +// +//func (a *HwProvider) GetAvailableInstanceType(host *v1beta1.Host) ([]string, error) { +// if host.EcsType != "" { +// return []string{host.EcsType}, nil +// } +// var systemInstanceTypes []string +// j := a.Infra.Status.FindHostsByRoles(host.Roles) +// if j == -1 { +// return nil, fmt.Errorf("failed to get host, %v", "not find host status,pelase retry") +// } +// var err error +// systemDisk, dataDisk := a.GetDefaultDiskCategories(host) +// +// for _, sys := range systemDisk { +// if len(dataDisk) > 0 { +// for _, data := range dataDisk { +// logger.Debug("host tags is %v,search systemDiskCategory=%s,dataDiskCategory=%s", host.Roles, sys, data) +// systemInstanceTypes, err = a.GetAvailableResource(host, sys, data) +// if err == nil { +// for i := 0; i < len(host.Disks); i++ { +// host.Disks[i].Category = data +// } +// host.Disks[0].Category = sys +// a.Infra.Status.Hosts[j].DataCategory = data +// a.Infra.Status.Hosts[j].SystemCategory = sys +// break +// } +// } +// } else { +// logger.Debug("host tags is %v,search systemDiskCategory=%s", host.Roles, sys) +// systemInstanceTypes, err = a.GetAvailableResource(host, sys, "") +// if err == nil { +// host.Disks[0].Category = sys +// a.Infra.Status.Hosts[j].SystemCategory = sys +// break +// } +// } +// } +// +// if len(systemInstanceTypes) < 1 { +// return nil, fmt.Errorf("host tags is %v,systemInstanceType not find", host.Roles) +// } +// +// var instanceTypes []string +// if err != nil { +// return nil, err +// } +// +// request := ecs.CreateDescribeImageSupportInstanceTypesRequest() +// request.Scheme = Scheme +// request.RegionId = a.Infra.Status.Cluster.RegionID +// request.ImageId = a.Infra.Status.Hosts[j].ImageID +// +// //response := ecs.CreateDescribeImageSupportInstanceTypesResponse() +// //err = a.EcsClient.DoAction(request, response) +// //if err != nil { +// // return nil, err +// //} +// //for _, i := range response.InstanceTypes.InstanceType { +// // if i.CpuCoreCount == host.CPU && int(i.MemorySize) == host.Memory { +// // if v1beta1.In(i.InstanceTypeId, systemInstanceTypes) { +// // logger.Debug("host tags is %v,append InstanceType is %s", host.Roles, i.InstanceTypeId) +// // instanceTypes = append(instanceTypes, i.InstanceTypeId) +// // } +// // } +// //} +// if len(instanceTypes) < 1 { +// return nil, fmt.Errorf("host tags is %v,instanceType not find", host.Roles) +// } +// return instanceTypes, nil +//} +// +//func (a *HwProvider) GetAvailableResource(host *v1beta1.Host, systemCategory, dataCategory string) (instanceType []string, err error) { +// request := ecs.CreateDescribeAvailableResourceRequest() +// request.Scheme = Scheme +// request.RegionId = a.Infra.Status.Cluster.RegionID +// request.ZoneId = a.Infra.Status.Cluster.ZoneID +// request.DestinationResource = DestinationResource +// request.InstanceChargeType = InstanceChargeType +// request.SpotStrategy = a.Infra.Status.Cluster.SpotStrategy +// request.SystemDiskCategory = systemCategory +// request.DataDiskCategory = dataCategory +// request.Cores = requests.NewInteger(host.CPU) +// request.Memory = requests.NewFloat(float64(host.Memory)) +// //response := ecs.CreateDescribeAvailableResourceResponse() +// //err = a.EcsClient.DoAction(request, response) +// //if err != nil { +// // return nil, err +// //} +// //if len(response.AvailableZones.AvailableZone) < 1 { +// // return nil, fmt.Errorf("available zone not find") +// //} +// //for _, i := range response.AvailableZones.AvailableZone { +// // for _, f := range i.AvailableResources.AvailableResource { +// // for _, r := range f.SupportedResources.SupportedResource { +// // if r.StatusCategory == AvailableTypeStatus { +// // instanceType = append(instanceType, r.Value) +// // } +// // } +// // } +// //} +// j := a.Infra.Status.FindHostsByRoles(host.Roles) +// if j == -1 { +// return nil, fmt.Errorf("failed to get ecs instance type, %v", "not find host status,pelase retry") +// } +// +// if a.Infra.Status.Hosts[j].InstanceType != "" { +// defaultInstanceType := []string{a.Infra.Status.Hosts[j].InstanceType} +// instanceType = append(defaultInstanceType, instanceType...) +// } +// +// return +//} diff --git a/pkg/infra/huawei/hw_provider.go b/pkg/infra/huawei/hw_provider.go new file mode 100644 index 000000000..481056686 --- /dev/null +++ b/pkg/infra/huawei/hw_provider.go @@ -0,0 +1,279 @@ +// Copyright © 2021 sealos. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package huawei + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/util/validation/field" + + v2 "github.com/fanux/sealos/pkg/types/v1beta1" + "github.com/fanux/sealos/pkg/utils" + "github.com/fanux/sealos/pkg/utils/logger" + "github.com/huaweicloud/huaweicloud-sdk-go-v3/core" + "github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic" + ecs "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/ecs/v2" + + vpc "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/vpc/v2" +) + +type ActionName string + +const ( + CreateVPC ActionName = "CreateVPC" + CreateVSwitch ActionName = "CreateVSwitch" + CreateSecurityGroup ActionName = "CreateSecurityGroup" + ReconcileInstance ActionName = "ReconcileInstance" + BindEIP ActionName = "BindEIP" + ReleaseEIP ActionName = "ReleaseEIP" + ClearInstances ActionName = "ClearInstances" + DeleteVSwitch ActionName = "DeleteVSwitch" + DeleteSecurityGroup ActionName = "DeleteSecurityGroup" + DeleteVPC ActionName = "DeleteVPC" + GetZoneID ActionName = "GetAvailableZoneID" +) + +type HwProvider struct { + EcsClient ecs.EcsClient + VpcClient vpc.VpcClient + Infra *v2.Infra +} + +type HwFunc func() error + +func (a *HwProvider) ReconcileResource(resourceKey ResourceName, action HwFunc) error { + if resourceKey.Value(a.Infra.Status) != "" { + logger.Debug("using resource status value %s: %s", resourceKey, resourceKey.Value(a.Infra.Status)) + return nil + } + if err := action(); err != nil { + logger.Error("reconcile resource %s failed err: %s", resourceKey, err) + return err + } + if resourceKey.Value(a.Infra.Status) != "" { + logger.Info("create resource success %s: %s", resourceKey, resourceKey.Value(a.Infra.Status)) + } + return nil +} + +func (a *HwProvider) DeleteResource(resourceKey ResourceName, action HwFunc) { + val := resourceKey.Value(a.Infra.Status) + if val == "" { + logger.Warn("delete resource not exists %s", resourceKey) + return + } + if err := action(); err != nil { + logger.Error("delete resource %s failed err: %s", resourceKey, err) + } else { + logger.Info("delete resource Success %s: %s", resourceKey, val) + } +} + +var RecocileFuncMap = map[ActionName]func(provider *HwProvider) error{ + CreateVPC: func(hwProvider *HwProvider) error { + return hwProvider.ReconcileResource(VpcID, hwProvider.CreateVPC) + }, + + //CreateVSwitch: func(hwProvider *HwProvider) error { + // return hwProvider.ReconcileResource(VSwitchID, hwProvider.CreateVSwitch) + //}, + //CreateSecurityGroup: func(hwProvider *HwProvider) error { + // return hwProvider.ReconcileResource(SecurityGroupID, hwProvider.CreateSecurityGroup) + //}, + //ReconcileInstance: func(hwProvider *HwProvider) error { + // var errorMsg []string + // current := sets.NewString() + // spec := sets.NewString() + // for _, h := range hwProvider.Infra.Status.Hosts { + // current.Insert(strings.Join(h.Roles, ",")) + // } + // for _, h := range hwProvider.Infra.Spec.Hosts { + // spec.Insert(strings.Join(h.Roles, ",")) + // host := &h + // statusIndex := hwProvider.Infra.Status.FindHostsByRoles(h.Roles) + // if statusIndex < 0 { + // errorMsg = append(errorMsg, fmt.Sprintf("infra status not fount in role tag: %v", h.Roles)) + // continue + // } + // status := &hwProvider.Infra.Status.Hosts[statusIndex] + // err := hwProvider.ReconcileInstances(host, status) + // if err != nil { + // errorMsg = append(errorMsg, err.Error()) + // status.Ready = false + // } else { + // status.Ready = true + // } + // } + // deleteData := current.Difference(spec) + // var instanceIDs []string + // finalStatus := hwProvider.Infra.Status.Hosts + // for _, roles := range deleteData.List() { + // statusIndex := hwProvider.Infra.Status.FindHostsByRolesString(roles) + // ids := hwProvider.Infra.Status.Hosts[statusIndex].IDs + // instanceIDs = append(instanceIDs, ids) + // finalStatus = append(finalStatus[:statusIndex], finalStatus[statusIndex+1:]...) + // } + // if len(instanceIDs) != 0 { + // ShouldBeDeleteInstancesIDs.SetValue(hwProvider.Infra.Status, strings.Join(instanceIDs, ",")) + // hwProvider.DeleteResource(ShouldBeDeleteInstancesIDs, hwProvider.DeleteInstances) + // hwProvider.Infra.Status.Hosts = finalStatus + // } + // + // if len(errorMsg) == 0 { + // return nil + // } + // return errors.New(strings.Join(errorMsg, " && ")) + //}, + GetZoneID: func(hwProvider *HwProvider) error { + return hwProvider.ReconcileResource(ZoneID, hwProvider.GetAvailableZoneID) + }, + //BindEIP: func(hwProvider *HwProvider) error { + // return hwProvider.ReconcileResource(EipID, hwProvider.BindEipForMaster0) + //}, +} + +var DeleteFuncMap = map[ActionName]func(provider *HwProvider){ + //ReleaseEIP: func(hwProvider *HwProvider) { + // hwProvider.DeleteResource(EipID, hwProvider.ReleaseEipAddress) + //}, + //ClearInstances: func(hwProvider *HwProvider) { + // var instanceIDs []string + // for _, h := range hwProvider.Infra.Status.Hosts { + // instances, err := hwProvider.GetInstancesInfo(h.ToHost(), JustGetInstanceInfo) + // if err != nil { + // logger.Error("get %s instanceInfo failed %v", strings.Join(h.Roles, ","), err) + // } + // for _, instance := range instances { + // instanceIDs = append(instanceIDs, instance.InstanceID) + // } + // } + // + // if len(instanceIDs) != 0 { + // ShouldBeDeleteInstancesIDs.SetValue(hwProvider.Infra.Status, strings.Join(instanceIDs, ",")) + // } + // hwProvider.DeleteResource(ShouldBeDeleteInstancesIDs, hwProvider.DeleteInstances) + //}, + //DeleteVSwitch: func(hwProvider *HwProvider) { + // hwProvider.DeleteResource(VSwitchID, hwProvider.DeleteVSwitch) + //}, + //DeleteSecurityGroup: func(hwProvider *HwProvider) { + // hwProvider.DeleteResource(SecurityGroupID, hwProvider.DeleteSecurityGroup) + //}, + DeleteVPC: func(hwProvider *HwProvider) { + hwProvider.DeleteResource(VpcID, hwProvider.DeleteVPC) + }, +} + +func (a *HwProvider) basicAuth() basic.Credentials { + //https://developer.huaweicloud.com/endpoint?ECS + core.NewHcHttpClientBuilder() + return basic.NewCredentialsBuilder(). + WithAk(a.Infra.Spec.Credential.AccessKey). + WithSk(a.Infra.Spec.Credential.AccessSecret). + WithProjectId(a.Infra.Spec.Credential.ProjectID). + Build() +} + +func (a *HwProvider) NewClient() (err error) { + regionID := a.Infra.Spec.Cluster.RegionIDs[utils.Rand(len(a.Infra.Spec.Cluster.RegionIDs))] + a.Infra.Status.Cluster.RegionID = regionID + logger.Info("using regionID is %s", regionID) + ecsEndpoint := fmt.Sprintf("https://ecs.%s.myhuaweicloud.com", regionID) + vpcEndpoint := fmt.Sprintf("https://vpc.%s.myhuaweicloud.com", regionID) + logger.Info("using ecs endpoint is %s", ecsEndpoint) + logger.Info("using vpc endpoint is %s", vpcEndpoint) + //var err error + defer func() { + if info := recover(); info != nil { + err = fmt.Errorf("%v", info) + } + }() + ecsClient := ecs.NewEcsClient( + ecs.EcsClientBuilder(). + WithEndpoint(ecsEndpoint). + WithCredential(a.basicAuth()). + Build()) + vpcClient := vpc.NewVpcClient( + vpc.VpcClientBuilder(). + WithEndpoint(vpcEndpoint). + WithCredential(a.basicAuth()). + Build()) + a.EcsClient = *ecsClient + a.VpcClient = *vpcClient + return +} + +func (a *HwProvider) ClearCluster() { + todolist := []ActionName{ + //ReleaseEIP, + //ClearInstances, + //DeleteVSwitch, + //DeleteSecurityGroup, + DeleteVPC, + } + for _, name := range todolist { + DeleteFuncMap[name](a) + } +} + +func (a *HwProvider) Reconcile() error { + if a.Infra.DeletionTimestamp != nil { + logger.Info("deletionTimestamp not nil will clear infra") + a.ClearCluster() + return nil + } + todolist := []ActionName{ + GetZoneID, + CreateVPC, + //CreateVSwitch, + //CreateSecurityGroup, + //ReconcileInstance, + //BindEIP, + } + + for _, actionName := range todolist { + err := RecocileFuncMap[actionName](a) + if err != nil { + logger.Warn("actionName: %s,err: %v ,skip it", actionName, err) + } + } + + return nil +} + +func (a *HwProvider) Apply() error { + return a.Reconcile() +} + +func DefaultInfra(infra *v2.Infra) error { + //if infra.Spec.Cluster.IsSeize { + // infra.Status.Cluster.SpotStrategy = "SpotAsPriceGo" + //} else { + // infra.Status.Cluster.SpotStrategy = "NoSpot" + //} + return nil +} + +func DefaultValidate(infra *v2.Infra) field.ErrorList { + allErrors := field.ErrorList{} + //if provide == v1beta1.HuaweiProvider { + // if len(credential.ProjectID) == 0 { + // logger.Warn("in huawei cloud , you need fetch projectID in you iam, please visit https://support.huaweicloud.com/apm_faq/apm_03_0001.html") + // allErrors = append(allErrors, field.Invalid(fldPath.Key("projectID"), credential.ProjectID, + // "projectID not empty")) + // } + //} + return allErrors +} diff --git a/pkg/infra/huawei/hw_vpc.go b/pkg/infra/huawei/hw_vpc.go new file mode 100644 index 000000000..839864ddd --- /dev/null +++ b/pkg/infra/huawei/hw_vpc.go @@ -0,0 +1,218 @@ +// Copyright © 2021 sealos. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package huawei + +import ( + "errors" + + "github.com/fanux/sealos/pkg/utils/logger" + v2 "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/vpc/v2" + "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/vpc/v2/model" +) + +func (a *HwProvider) CreateVPC() error { + if vpcID := VpcID.ClusterValue(a.Infra.Spec); vpcID != "" { + VpcID.SetValue(a.Infra.Status, vpcID) + logger.Debug("VpcID using default value") + return nil + } + request := &model.CreateVpcRequest{} + request.Body = &model.CreateVpcRequestBody{ + Vpc: &model.CreateVpcOption{ + Cidr: &a.Infra.Spec.Cluster.Metadata.Network.PrivateCidrIP, + }, + } + resp, err := a.RetryVpcRequest(request, v2.GenReqDefForCreateVpc()) + if err != nil { + return err + } + if vpc, ok := resp.(*model.CreateVpcResponse); ok { + if vpc.HttpStatusCode != 200 || vpc.Vpc == nil { + return errors.New("not available vpcID ") + } + VpcID.SetValue(a.Infra.Status, vpc.Vpc.Id) + } + return nil +} + +func (a *HwProvider) DeleteVPC() error { + if VpcID.ClusterValue(a.Infra.Spec) != "" && VpcID.Value(a.Infra.Status) != "" { + return nil + } + request := &model.DeleteVpcRequest{} + resp, err := a.RetryVpcRequest(request, v2.GenReqDefForDeleteVpc()) + if err != nil { + return err + } + if vpcResp, ok := resp.(*model.DeleteVpcResponse); ok { + if vpcResp.HttpStatusCode != 200 { + return errors.New("delete vpc field,return httpCode is not 200") + } + } + return nil +} + +// +//func (a *HwProvider) CreateVSwitch() error { +// if vSwitchID := VSwitchID.ClusterValue(a.Infra.Spec); vSwitchID != "" { +// logger.Debug("VSwitchID using default value") +// VSwitchID.SetValue(a.Infra.Status, vSwitchID) +// return nil +// } +// request := vpc.CreateCreateVSwitchRequest() +// request.Scheme = Scheme +// request.ZoneId = a.Infra.Status.Cluster.ZoneID +// request.CidrBlock = CidrBlock +// request.VpcId = VpcID.Value(a.Infra.Status) +// request.RegionId = a.Infra.Status.Cluster.RegionID +// //response, err := d.Client.CreateVSwitch(request) +// response := vpc.CreateCreateVSwitchResponse() +// err := a.RetryVpcRequest(request, response) +// if err != nil { +// return err +// } +// VSwitchID.SetValue(a.Infra.Status, response.VSwitchId) +// +// return nil +//} +// +//func (a *HwProvider) DeleteVSwitch() error { +// if VSwitchID.ClusterValue(a.Infra.Spec) != "" && VSwitchID.Value(a.Infra.Status) != "" { +// return nil +// } +// request := vpc.CreateDeleteVSwitchRequest() +// request.Scheme = Scheme +// request.VSwitchId = VSwitchID.Value(a.Infra.Status) +// +// response := vpc.CreateDeleteVSwitchResponse() +// return a.RetryVpcRequest(request, response) +//} +// +//func (a *HwProvider) CreateSecurityGroup() error { +// if securityGroupID := SecurityGroupID.ClusterValue(a.Infra.Spec); securityGroupID != "" { +// logger.Debug("securityGroupID using default value") +// SecurityGroupID.SetValue(a.Infra.Status, securityGroupID) +// return nil +// } +// request := ecs.CreateCreateSecurityGroupRequest() +// request.Scheme = Scheme +// request.RegionId = a.Infra.Status.Cluster.RegionID +// request.VpcId = VpcID.Value(a.Infra.Status) +// response := ecs.CreateCreateSecurityGroupResponse() +// err := a.RetryEcsRequest(request, response) +// if err != nil { +// return err +// } +// +// if !a.AuthorizeSecurityGroup(response.SecurityGroupId, SSHPortRange) { +// return fmt.Errorf("authorize securitygroup ssh port failed") +// } +// if !a.AuthorizeSecurityGroup(response.SecurityGroupId, APIServerPortRange) { +// return fmt.Errorf("authorize securitygroup apiserver port failed") +// } +// SecurityGroupID.SetValue(a.Infra.Status, response.SecurityGroupId) +// return nil +//} +// +//func (a *HwProvider) DeleteSecurityGroup() error { +// if SecurityGroupID.ClusterValue(a.Infra.Spec) != "" && SecurityGroupID.Value(a.Infra.Status) != "" { +// return nil +// } +// request := ecs.CreateDeleteSecurityGroupRequest() +// request.Scheme = Scheme +// request.SecurityGroupId = SecurityGroupID.Value(a.Infra.Status) +// +// response := ecs.CreateDeleteSecurityGroupResponse() +// return a.RetryEcsRequest(request, response) +//} + +// +//func (a *HwProvider) BindEipForMaster0() error { +// var host *v1beta1.Host +// for i, h := range a.Infra.Status.Hosts { +// if v1beta1.In(v1beta1.Master, h.Roles) && h.Ready { +// host = &a.Infra.Spec.Hosts[i] +// break +// } +// } +// if host == nil { +// return fmt.Errorf("bind eip for master error: ready master host not fount") +// } +// instances, err := a.GetInstancesInfo(host, JustGetInstanceInfo) +// if err != nil { +// return err +// } +// if len(instances) == 0 { +// return errors.New("can not find master0 ") +// } +// master0 := instances[0] +// eIP, eIPID, err := a.allocateEipAddress() +// if err != nil { +// return err +// } +// err = a.associateEipAddress(master0.InstanceID, eIPID) +// if err != nil { +// return err +// } +// a.Infra.Status.Cluster.EIP = eIP +// EipID.SetValue(a.Infra.Status, eIPID) +// a.Infra.Status.Cluster.Master0ID = master0.InstanceID +// a.Infra.Status.Cluster.Master0InternalIP = master0.PrimaryIPAddress +// return nil +//} +// +//func (a *HwProvider) allocateEipAddress() (eIP, eIPID string, err error) { +// request := vpc.CreateAllocateEipAddressRequest() +// request.Scheme = Scheme +// request.Bandwidth = Bandwidth +// request.InternetChargeType = InternetChargeType +// response := vpc.CreateAllocateEipAddressResponse() +// err = a.RetryVpcRequest(request, response) +// if err != nil { +// return "", "", err +// } +// return response.EipAddress, response.AllocationId, nil +//} +// +//func (a *HwProvider) associateEipAddress(instanceID, eipID string) error { +// request := vpc.CreateAssociateEipAddressRequest() +// request.Scheme = Scheme +// request.InstanceId = instanceID +// request.AllocationId = eipID +// +// response := vpc.CreateAssociateEipAddressResponse() +// return a.RetryVpcRequest(request, response) +//} +// +//func (a *HwProvider) unAssociateEipAddress() error { +// request := vpc.CreateUnassociateEipAddressRequest() +// request.Scheme = Scheme +// request.AllocationId = EipID.Value(a.Infra.Status) +// request.Force = requests.NewBoolean(true) +// response := vpc.CreateUnassociateEipAddressResponse() +// return a.RetryVpcRequest(request, response) +//} +// +//func (a *HwProvider) ReleaseEipAddress() error { +// err := a.unAssociateEipAddress() +// if err != nil { +// return err +// } +// request := vpc.CreateReleaseEipAddressRequest() +// request.Scheme = Scheme +// request.AllocationId = EipID.Value(a.Infra.Status) +// response := vpc.CreateReleaseEipAddressResponse() +// return a.RetryVpcRequest(request, response) +//} diff --git a/pkg/infra/huawei/retry.go b/pkg/infra/huawei/retry.go new file mode 100644 index 000000000..194085062 --- /dev/null +++ b/pkg/infra/huawei/retry.go @@ -0,0 +1,111 @@ +// Copyright © 2021 sealos. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package huawei + +import ( + "github.com/huaweicloud/huaweicloud-sdk-go-v3/core/def" + + "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" + "github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses" + "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" + "github.com/fanux/sealos/pkg/utils" +) + +func (a *HwProvider) RetryVpcRequest(request interface{}, def *def.HttpRequestDef) (interface{}, error) { + var respInterface interface{} + var err error + allErr := utils.Retry(TryTimes, TrySleepTime, func() error { + respInterface, err = a.VpcClient.HcClient.Sync(request, def) + if err != nil { + return err + } + return nil + }) + return respInterface, allErr +} + +func (a *HwProvider) RetryEcsRequest(request interface{}, def *def.HttpRequestDef) (interface{}, error) { + return a.RetryEcsAction(request, def, TryTimes) +} + +func (a *HwProvider) RetryEcsAction(request interface{}, def *def.HttpRequestDef, tryTimes int) (interface{}, error) { + var respInterface interface{} + var err error + allErr := utils.Retry(tryTimes, TrySleepTime, func() error { + respInterface, err = a.EcsClient.HcClient.Sync(request, def) + if err != nil { + return err + } + return nil + }) + return respInterface, allErr +} + +func (a *HwProvider) RetryEcsInstanceType(request requests.AcsRequest, response responses.AcsResponse, instances, instanceRole []string) error { + //for i := 0; i < len(instances); i++ { + // switch req := request.(type) { + // case *ecs.ModifyInstanceSpecRequest: + // req.InstanceType = instances[i] + // case *ecs.RunInstancesRequest: + // req.InstanceType = instances[i] + // } + // err := a.RetryEcsAction(request, response, 4) + // if err == nil { + // logger.Info("use instance type: %s", instances[i]) + // j := a.Infra.Status.FindHostsByRoles(instanceRole) + // if j == -1 { + // return fmt.Errorf("failed to get ecs instance type, %v", "not find host status,pelase retry") + // } + // a.Infra.Status.Hosts[j].InstanceType = instances[i] + // break + // } else { + // logger.Warn("failed to get ecs instance type: %s", instances[i]) + // if i == len(instances)-1 { + // return fmt.Errorf("failed to get ecs instance type, %v", err) + // } + // } + //} + return nil +} + +func (a *HwProvider) TryGetInstance(request *ecs.DescribeInstancesRequest, response *ecs.DescribeInstancesResponse, expectCount int) error { + return utils.Retry(TryTimes, TrySleepTime, func() error { + //err := a.EcsClient.DoAction(request, response) + //var ipList []string + //if err != nil { + // return err + //} + //instances := response.Instances.Instance + //if expectCount == -1 { + // return nil + //} + // + //if len(instances) != expectCount { + // return errors.New("the number of instances is not as expected") + //} + //for _, instance := range instances { + // if instance.NetworkInterfaces.NetworkInterface[0].PrimaryIpAddress == "" { + // return errors.New("PrimaryIpAddress cannt nob be nil") + // } + // if len(ipList) != 0 && !utils.NotIn(instance.NetworkInterfaces.NetworkInterface[0].PrimaryIpAddress, ipList) { + // return errors.New("PrimaryIpAddress cannt nob be same") + // } + // + // ipList = append(ipList, instance.NetworkInterfaces.NetworkInterface[0].PrimaryIpAddress) + //} + + return nil + }) +} diff --git a/pkg/infra/infra.go b/pkg/infra/infra.go index f79a4549a..7661914d1 100644 --- a/pkg/infra/infra.go +++ b/pkg/infra/infra.go @@ -18,6 +18,9 @@ import ( "fmt" "os" + "github.com/fanux/sealos/pkg/infra/huawei" + "github.com/fanux/sealos/pkg/types/validation" + "github.com/fanux/sealos/pkg/infra/aliyun" v2 "github.com/fanux/sealos/pkg/types/v1beta1" ) @@ -45,17 +48,40 @@ func loadConfig(infra *v2.Infra) { func newAliProvider(infra *v2.Infra) (Interface, error) { aliProvider := new(aliyun.AliProvider) aliProvider.Infra = infra + if err := v2.Default(aliProvider.Infra, aliyun.DefaultInfra); err != nil { + return nil, err + } + if err := validation.ValidateInfra(aliProvider.Infra, aliyun.DefaultValidate); len(err) != 0 { + return nil, err.ToAggregate() + } if err := aliProvider.NewClient(); err != nil { return nil, err } return aliProvider, nil } +func newHwProvider(infra *v2.Infra) (Interface, error) { + hwProvider := new(huawei.HwProvider) + hwProvider.Infra = infra + if err := v2.Default(hwProvider.Infra, huawei.DefaultInfra); err != nil { + return nil, err + } + if err := validation.ValidateInfra(hwProvider.Infra, huawei.DefaultValidate); len(err) != 0 { + return nil, err.ToAggregate() + } + if err := hwProvider.NewClient(); err != nil { + return nil, err + } + return hwProvider, nil +} + func NewDefaultProvider(infra *v2.Infra) (Interface, error) { loadConfig(infra) switch infra.Spec.Provider { - case v2.AliyunProvider: + case aliyun.AliyunProvider: return newAliProvider(infra) + case huawei.HuaweiProvider: + return newHwProvider(infra) default: return nil, fmt.Errorf("the provider is invalid, please set the provider correctly") } diff --git a/pkg/infra/infra_test.go b/pkg/infra/infra_test.go index 277e6bfc6..9332450b7 100644 --- a/pkg/infra/infra_test.go +++ b/pkg/infra/infra_test.go @@ -19,6 +19,9 @@ import ( "testing" "time" + "github.com/fanux/sealos/pkg/infra/aliyun" + "github.com/fanux/sealos/pkg/infra/huawei" + "sigs.k8s.io/yaml" v2 "github.com/fanux/sealos/pkg/types/v1beta1" @@ -26,7 +29,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func TestApply(t *testing.T) { +func TestAliApply(t *testing.T) { //setup infra infra := v2.Infra{ TypeMeta: metav1.TypeMeta{ @@ -46,30 +49,11 @@ func TestApply(t *testing.T) { Port: 22, }, }, - IsSeize: true, + Metadata: v2.ClusterMeta{ + IsSeize: true, + }, }, Hosts: []v2.Host{ - //{ - // Roles: []string{"master", "ssd"}, - // CPU: 2, - // Memory: 4, - // Count: 1, - // Disks: []v2.Disk{}, - // OS: v2.OS{ - // Name: "centos", - // }, - //}, - //{ - // Roles: []string{"node", "ssd"}, - // CPU: 2, - // Memory: 4, - // Count: 1, - // Disks: []v2.Disk{}, - // Arch: v2.ARM64, - // OS: v2.OS{ - // Name: "centos", - // }, - //}, { Roles: []string{"master", "ssdxxx"}, CPU: 2, @@ -81,7 +65,7 @@ func TestApply(t *testing.T) { }, }, }, - Provider: v2.AliyunProvider, + Provider: aliyun.AliyunProvider, }, } @@ -143,3 +127,59 @@ func TestApply(t *testing.T) { infra.ObjectMeta.DeletionTimestamp = &now t.Log(fmt.Sprintf("%v", aliProvider.Apply())) } + +func TestHuaweiApply(t *testing.T) { + //setup infra + infra := v2.Infra{ + TypeMeta: metav1.TypeMeta{ + Kind: "Infra", + APIVersion: v2.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "my-infra", + }, + Spec: v2.InfraSpec{ + Credential: v2.Credential{ProjectID: "06b264130780105b2f50c0145ba32d41"}, + Cluster: v2.Cluster{ + RegionIDs: []string{"cn-north-4"}, + ZoneIDs: []string{""}, + AccessChannels: v2.AccessChannels{ + SSH: v2.SSH{ + Passwd: "Fanux#123", + Port: 22, + }, + }, + Metadata: v2.ClusterMeta{IsSeize: true}, + }, + Hosts: []v2.Host{ + { + Roles: []string{"master", "ssdxxx"}, + CPU: 2, + Memory: 4, + Count: 1, + Disks: []v2.Disk{}, + OS: v2.OS{ + Name: "ubuntu", + }, + }, + }, + Provider: huawei.HuaweiProvider, + }, + } + + hwProvider, err := NewDefaultProvider(&infra) + if err != nil { + fmt.Printf("%v", err) + return + } + fmt.Printf("%v", hwProvider.Apply()) + // todo + t.Run("default", func(t *testing.T) { + + }) + //teardown + time.Sleep(20 * time.Second) + now := metav1.Now() + infra.ObjectMeta.DeletionTimestamp = &now + t.Log(fmt.Sprintf("%v", hwProvider.Apply())) +} diff --git a/pkg/types/v1beta1/defaults.go b/pkg/types/v1beta1/defaults.go index b354a3119..ffcf512b4 100644 --- a/pkg/types/v1beta1/defaults.go +++ b/pkg/types/v1beta1/defaults.go @@ -19,6 +19,28 @@ package v1beta1 import ( "math/rand" "time" + + "github.com/fanux/sealos/pkg/utils" + "k8s.io/apimachinery/pkg/util/sets" +) + +const ( + SourceCidrIP = "0.0.0.0/0" + SSHPort = "22/22" + APIServerPort = "6443/6443" +) + +var ( + sshExportPort = ExportPort{ + Protocol: ProtocolTCP, + CidrIP: SourceCidrIP, + PortRange: SSHPort, + } + apiserverExportPort = ExportPort{ + Protocol: ProtocolTCP, + CidrIP: SourceCidrIP, + PortRange: APIServerPort, + } ) func Default(infra *Infra, fn func(infra *Infra) error) error { @@ -36,6 +58,36 @@ func defaultCluster(infra *Infra) { if infra.Spec.Cluster.Annotations == nil { infra.Spec.Cluster.Annotations = make(map[string]string) } + + if infra.Spec.Cluster.RegionIDs != nil { + infra.Spec.Cluster.RegionIDs = utils.RemoveSliceEmpty(infra.Spec.Cluster.RegionIDs) + } + if infra.Spec.Cluster.ZoneIDs != nil { + infra.Spec.Cluster.ZoneIDs = utils.RemoveSliceEmpty(infra.Spec.Cluster.ZoneIDs) + } + if infra.Spec.Cluster.Metadata.Network.Bandwidth == "" { + infra.Spec.Cluster.Metadata.Network.Bandwidth = "100" + } + if len(infra.Spec.Cluster.Metadata.Network.ExportPorts) == 0 { + infra.Spec.Cluster.Metadata.Network.ExportPorts = []ExportPort{ + sshExportPort, + apiserverExportPort, + } + } else { + ports := sets.NewString() + for _, port := range infra.Spec.Cluster.Metadata.Network.ExportPorts { + ports.Insert(port.PortRange) + } + if !ports.Has(SSHPort) { + infra.Spec.Cluster.Metadata.Network.ExportPorts = append(infra.Spec.Cluster.Metadata.Network.ExportPorts, sshExportPort) + } + if !ports.Has(APIServerPort) { + infra.Spec.Cluster.Metadata.Network.ExportPorts = append(infra.Spec.Cluster.Metadata.Network.ExportPorts, apiserverExportPort) + } + } + if infra.Spec.Cluster.Metadata.Network.PrivateCidrIP == "" { + infra.Spec.Cluster.Metadata.Network.PrivateCidrIP = "172.16.0.0/24" + } } func defaultHosts(infra *Infra) { diff --git a/pkg/types/v1beta1/types.go b/pkg/types/v1beta1/types.go index df9ae69e3..34032466d 100644 --- a/pkg/types/v1beta1/types.go +++ b/pkg/types/v1beta1/types.go @@ -26,10 +26,6 @@ import ( type Provider string -const ( - AliyunProvider Provider = "AliyunProvider" -) - type Arch string const ( @@ -80,18 +76,44 @@ type Disk struct { type Credential struct { AccessKey string `json:"accessKey"` AccessSecret string `json:"accessSecret"` + ProjectID string `json:"ProjectID,omitempty"` } type AccessChannels struct { SSH SSH `json:"ssh,omitempty"` } +type ClusterMeta struct { + IsSeize bool `json:"isSeize,omitempty"` + + Network ClusterNetworkMeta `json:"network"` +} + +type ClusterNetworkMeta struct { + Bandwidth string `json:"bandwidth"` + ExportPorts []ExportPort `json:"exportPorts,omitempty"` + PrivateCidrIP string `json:"privateCidrIP,omitempty"` +} + +type Protocol string + +const ( + ProtocolTCP Protocol = "tcp" + ProtocolUDP Protocol = "udp" +) + +type ExportPort struct { + Protocol Protocol `json:"protocol"` + CidrIP string `json:"cidrIP"` + PortRange string `json:"portRange"` +} + type Cluster struct { RegionIDs []string `json:"regionIDs,omitempty"` ZoneIDs []string `json:"zoneIDs,omitempty"` Annotations map[string]string `json:"annotations,omitempty"` AccessChannels AccessChannels `json:"accessChannels"` - IsSeize bool `json:"isSeize,omitempty"` + Metadata ClusterMeta `json:"metadata,omitempty"` } // InfraSpec defines the desired state of Infra diff --git a/pkg/types/v1beta1/zz_generated.deepcopy.go b/pkg/types/v1beta1/zz_generated.deepcopy.go index c3258e8a4..8c9a226c2 100644 --- a/pkg/types/v1beta1/zz_generated.deepcopy.go +++ b/pkg/types/v1beta1/zz_generated.deepcopy.go @@ -60,6 +60,7 @@ func (in *Cluster) DeepCopyInto(out *Cluster) { } } out.AccessChannels = in.AccessChannels + in.Metadata.DeepCopyInto(&out.Metadata) return } @@ -73,6 +74,44 @@ func (in *Cluster) DeepCopy() *Cluster { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterMeta) DeepCopyInto(out *ClusterMeta) { + *out = *in + in.Network.DeepCopyInto(&out.Network) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterMeta. +func (in *ClusterMeta) DeepCopy() *ClusterMeta { + if in == nil { + return nil + } + out := new(ClusterMeta) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterNetworkMeta) DeepCopyInto(out *ClusterNetworkMeta) { + *out = *in + if in.ExportPorts != nil { + in, out := &in.ExportPorts, &out.ExportPorts + *out = make([]ExportPort, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterNetworkMeta. +func (in *ClusterNetworkMeta) DeepCopy() *ClusterNetworkMeta { + if in == nil { + return nil + } + out := new(ClusterNetworkMeta) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClusterStatus) DeepCopyInto(out *ClusterStatus) { *out = *in @@ -128,6 +167,22 @@ func (in *Disk) DeepCopy() *Disk { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExportPort) DeepCopyInto(out *ExportPort) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExportPort. +func (in *ExportPort) DeepCopy() *ExportPort { + if in == nil { + return nil + } + out := new(ExportPort) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Host) DeepCopyInto(out *Host) { *out = *in diff --git a/pkg/types/validation/validation.go b/pkg/types/validation/validation.go index 1ed61e036..ab0f96ade 100644 --- a/pkg/types/validation/validation.go +++ b/pkg/types/validation/validation.go @@ -72,14 +72,29 @@ func validateCluster(cluster *v1beta1.Cluster, fldPath *field.Path) field.ErrorL return allErrors } -func ValidateInfra(infra *v1beta1.Infra) field.ErrorList { +func validateCredential(credential *v1beta1.Credential, fldPath *field.Path) field.ErrorList { + allErrors := field.ErrorList{} + + if len(credential.AccessKey) == 0 { + allErrors = append(allErrors, field.Invalid(fldPath.Key("accessKey"), credential.AccessKey, + "accessKey not empty")) + } + if len(credential.AccessSecret) == 0 { + allErrors = append(allErrors, field.Invalid(fldPath.Key("accessSecret"), credential.AccessSecret, + "accessSecret not empty")) + } + return allErrors +} + +func ValidateInfra(infra *v1beta1.Infra, fun func(infra *v1beta1.Infra) field.ErrorList) field.ErrorList { allErrors := apimachineryvalidation.ValidateObjectMeta(&infra.ObjectMeta, false, ValidateInfraName, field.NewPath("metadata")) allErrors = append(allErrors, validateInfraSpec(&infra.Spec, field.NewPath("spec"))...) + allErrors = append(allErrors, fun(infra)...) return allErrors } func validateInfraSpec(spec *v1beta1.InfraSpec, fldPath *field.Path) field.ErrorList { allErrors := field.ErrorList{} - + allErrors = append(allErrors, validateCredential(&spec.Credential, fldPath.Child("credential"))...) allErrors = append(allErrors, validateCluster(&spec.Cluster, fldPath.Child("cluster"))...) var roles []string roleSet := sets.NewString() diff --git a/pkg/utils/strings.go b/pkg/utils/strings.go index 48873e821..352261697 100644 --- a/pkg/utils/strings.go +++ b/pkg/utils/strings.go @@ -129,3 +129,12 @@ func IsEmptyLine(str string) bool { func TrimWS(str string) string { return strings.Trim(str, " \n\t") } + +func RemoveSliceEmpty(list []string) (fList []string) { + for i := range list { + if strings.TrimSpace(list[i]) != "" { + fList = append(fList, list[i]) + } + } + return +}