mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-06-23 12:07:15 +08:00
441 lines
11 KiB
Go
441 lines
11 KiB
Go
// Copyright 2019 Yunion
|
|
//
|
|
// 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 aws
|
|
|
|
import (
|
|
sdk "github.com/aws/aws-sdk-go/aws"
|
|
"github.com/aws/aws-sdk-go/aws/client"
|
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
|
"github.com/aws/aws-sdk-go/aws/request"
|
|
"github.com/aws/aws-sdk-go/aws/session"
|
|
"github.com/aws/aws-sdk-go/service/cloudwatch"
|
|
"github.com/aws/aws-sdk-go/service/ec2"
|
|
"github.com/aws/aws-sdk-go/service/s3"
|
|
|
|
"yunion.io/x/log"
|
|
"yunion.io/x/pkg/errors"
|
|
|
|
api "yunion.io/x/onecloud/pkg/apis/compute"
|
|
"yunion.io/x/onecloud/pkg/cloudprovider"
|
|
"yunion.io/x/onecloud/pkg/util/httputils"
|
|
)
|
|
|
|
const (
|
|
CLOUD_PROVIDER_AWS = api.CLOUD_PROVIDER_AWS
|
|
CLOUD_PROVIDER_AWS_CN = "AWS"
|
|
|
|
AWS_INTERNATIONAL_CLOUDENV = "InternationalCloud"
|
|
AWS_CHINA_CLOUDENV = "ChinaCloud"
|
|
|
|
AWS_INTERNATIONAL_DEFAULT_REGION = "us-west-1"
|
|
AWS_CHINA_DEFAULT_REGION = "cn-north-1"
|
|
AWS_API_VERSION = "2018-10-10"
|
|
)
|
|
|
|
var (
|
|
DEBUG = false
|
|
)
|
|
|
|
type AwsClientConfig struct {
|
|
cpcfg cloudprovider.ProviderConfig
|
|
|
|
accessUrl string // 服务区域 ChinaCloud | InternationalCloud
|
|
accessKey string
|
|
accessSecret string
|
|
|
|
debug bool
|
|
}
|
|
|
|
func NewAwsClientConfig(accessUrl, accessKey, accessSecret string) *AwsClientConfig {
|
|
cfg := &AwsClientConfig{
|
|
accessUrl: accessUrl,
|
|
accessKey: accessKey,
|
|
accessSecret: accessSecret,
|
|
}
|
|
return cfg
|
|
}
|
|
|
|
func (cfg *AwsClientConfig) CloudproviderConfig(cpcfg cloudprovider.ProviderConfig) *AwsClientConfig {
|
|
cfg.cpcfg = cpcfg
|
|
return cfg
|
|
}
|
|
|
|
func (cfg *AwsClientConfig) Debug(debug bool) *AwsClientConfig {
|
|
cfg.debug = debug
|
|
return cfg
|
|
}
|
|
|
|
type SAwsClient struct {
|
|
*AwsClientConfig
|
|
|
|
ownerId string
|
|
ownerName string
|
|
|
|
iregions []cloudprovider.ICloudRegion
|
|
iBuckets []cloudprovider.ICloudBucket
|
|
}
|
|
|
|
func NewAwsClient(cfg *AwsClientConfig) (*SAwsClient, error) {
|
|
client := SAwsClient{
|
|
AwsClientConfig: cfg,
|
|
}
|
|
err := client.fetchRegions()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "fetchRegions")
|
|
}
|
|
err = client.fetchBuckets()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "fetchBuckets")
|
|
}
|
|
if client.debug {
|
|
log.Debugf("ownerId: %s ownerName: %s", client.ownerId, client.ownerName)
|
|
}
|
|
return &client, nil
|
|
}
|
|
|
|
func GetDefaultRegionId(accessUrl string) string {
|
|
defaultRegion := AWS_INTERNATIONAL_DEFAULT_REGION
|
|
switch accessUrl {
|
|
case AWS_INTERNATIONAL_CLOUDENV:
|
|
defaultRegion = AWS_INTERNATIONAL_DEFAULT_REGION
|
|
case AWS_CHINA_CLOUDENV:
|
|
defaultRegion = AWS_CHINA_DEFAULT_REGION
|
|
}
|
|
|
|
return defaultRegion
|
|
}
|
|
|
|
func (self *SAwsClient) getDefaultRegionId() string {
|
|
return GetDefaultRegionId(self.accessUrl)
|
|
}
|
|
|
|
func (client *SAwsClient) getDefaultSession() (*session.Session, error) {
|
|
return client.getAwsSession(client.getDefaultRegionId())
|
|
}
|
|
|
|
func (self *SAwsClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
|
|
// todo: implement me
|
|
err := self.fetchRegions()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
subAccount := cloudprovider.SSubAccount{}
|
|
subAccount.Name = self.cpcfg.Name
|
|
subAccount.Account = self.accessKey
|
|
subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL
|
|
return []cloudprovider.SSubAccount{subAccount}, nil
|
|
}
|
|
|
|
func (client *SAwsClient) GetAccountId() string {
|
|
return client.ownerId
|
|
}
|
|
|
|
func (self *SAwsClient) UpdateAccount(accessKey, secret string) error {
|
|
if self.accessKey != accessKey || self.accessSecret != secret {
|
|
self.accessKey = accessKey
|
|
self.accessSecret = secret
|
|
self.iregions = nil
|
|
return self.fetchRegions()
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// 用于初始化region信息
|
|
func (self *SAwsClient) fetchRegions() error {
|
|
if self.iregions != nil {
|
|
return nil
|
|
}
|
|
s, err := self.getDefaultSession()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
svc := ec2.New(s)
|
|
// https://docs.aws.amazon.com/sdk-for-go/api/service/ec2/#EC2.DescribeRegions
|
|
result, err := svc.DescribeRegions(&ec2.DescribeRegionsInput{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
regions := make([]SRegion, 0)
|
|
// empty iregions
|
|
// if self.iregions != nil {
|
|
// self.iregions = self.iregions[:0]
|
|
// }
|
|
|
|
for _, region := range result.Regions {
|
|
name := *region.RegionName
|
|
endpoint := *region.Endpoint
|
|
sregion := SRegion{client: self, RegionId: name, RegionEndpoint: endpoint}
|
|
// 初始化region client
|
|
sregion.getEc2Client()
|
|
regions = append(regions, sregion)
|
|
self.iregions = append(self.iregions, &sregion)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (client *SAwsClient) getAwsSession(regionId string) (*session.Session, error) {
|
|
httpClient := httputils.GetDefaultClient()
|
|
httputils.SetClientProxyFunc(httpClient, client.cpcfg.ProxyFunc)
|
|
return session.NewSession(&sdk.Config{
|
|
Region: sdk.String(regionId),
|
|
Credentials: credentials.NewStaticCredentials(
|
|
client.accessKey, client.accessSecret, "",
|
|
),
|
|
HTTPClient: httpClient,
|
|
DisableParamValidation: sdk.Bool(true),
|
|
CredentialsChainVerboseErrors: sdk.Bool(true),
|
|
})
|
|
}
|
|
|
|
func (self *SAwsClient) invalidateIBuckets() {
|
|
self.iBuckets = nil
|
|
}
|
|
|
|
func (self *SAwsClient) getIBuckets() ([]cloudprovider.ICloudBucket, error) {
|
|
if self.iBuckets == nil {
|
|
err := self.fetchBuckets()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "fetchBuckets")
|
|
}
|
|
}
|
|
return self.iBuckets, nil
|
|
}
|
|
|
|
func (client *SAwsClient) fetchBuckets() error {
|
|
s, err := client.getDefaultSession()
|
|
if err != nil {
|
|
return errors.Wrap(err, "getDefaultSession")
|
|
}
|
|
s3cli := s3.New(s)
|
|
output, err := s3cli.ListBuckets(&s3.ListBucketsInput{})
|
|
if err != nil {
|
|
return errors.Wrap(err, "ListBuckets")
|
|
}
|
|
|
|
if output.Owner != nil {
|
|
if output.Owner.ID != nil {
|
|
client.ownerId = *output.Owner.ID
|
|
}
|
|
if output.Owner.DisplayName != nil {
|
|
client.ownerName = *output.Owner.DisplayName
|
|
}
|
|
}
|
|
|
|
ret := make([]cloudprovider.ICloudBucket, 0)
|
|
for _, bInfo := range output.Buckets {
|
|
if err := FillZero(bInfo); err != nil {
|
|
log.Errorf("s3cli.Binfo.FillZero error %s", err)
|
|
continue
|
|
}
|
|
|
|
input := &s3.GetBucketLocationInput{}
|
|
input.Bucket = bInfo.Name
|
|
output, err := s3cli.GetBucketLocation(input)
|
|
if err != nil {
|
|
log.Errorf("s3cli.GetBucketLocation error %s", err)
|
|
continue
|
|
}
|
|
|
|
if err := FillZero(output); err != nil {
|
|
log.Errorf("s3cli.GetBucketLocation.FillZero error %s", err)
|
|
continue
|
|
}
|
|
|
|
location := *output.LocationConstraint
|
|
region, err := client.getIRegionByRegionId(location)
|
|
if err != nil {
|
|
log.Errorf("client.getIRegionByRegionId %s fail %s", location, err)
|
|
continue
|
|
}
|
|
b := SBucket{
|
|
region: region.(*SRegion),
|
|
Name: *bInfo.Name,
|
|
Location: location,
|
|
CreationDate: *bInfo.CreationDate,
|
|
}
|
|
ret = append(ret, &b)
|
|
}
|
|
|
|
client.iBuckets = ret
|
|
|
|
return nil
|
|
}
|
|
|
|
// 只是使用fetchRegions初始化好的self.iregions. 本身并不从云服务器厂商拉取region信息
|
|
func (self *SAwsClient) GetRegions() []SRegion {
|
|
regions := make([]SRegion, len(self.iregions))
|
|
for i := 0; i < len(regions); i += 1 {
|
|
region := self.iregions[i].(*SRegion)
|
|
regions[i] = *region
|
|
}
|
|
return regions
|
|
}
|
|
|
|
func (self *SAwsClient) GetIRegions() []cloudprovider.ICloudRegion {
|
|
return self.iregions
|
|
}
|
|
|
|
func (self *SAwsClient) GetRegion(regionId string) *SRegion {
|
|
if len(regionId) == 0 {
|
|
regionId = AWS_INTERNATIONAL_DEFAULT_REGION
|
|
switch self.accessUrl {
|
|
case AWS_INTERNATIONAL_CLOUDENV:
|
|
regionId = AWS_INTERNATIONAL_DEFAULT_REGION
|
|
case AWS_CHINA_CLOUDENV:
|
|
regionId = AWS_CHINA_DEFAULT_REGION
|
|
}
|
|
}
|
|
for i := 0; i < len(self.iregions); i += 1 {
|
|
if self.iregions[i].GetId() == regionId {
|
|
return self.iregions[i].(*SRegion)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (self *SAwsClient) getIRegionByRegionId(id string) (cloudprovider.ICloudRegion, error) {
|
|
for i := 0; i < len(self.iregions); i += 1 {
|
|
if self.iregions[i].GetId() == id {
|
|
return self.iregions[i], nil
|
|
}
|
|
}
|
|
return nil, ErrorNotFound()
|
|
}
|
|
|
|
func (self *SAwsClient) GetIRegionById(id string) (cloudprovider.ICloudRegion, error) {
|
|
for i := 0; i < len(self.iregions); i += 1 {
|
|
if self.iregions[i].GetGlobalId() == id {
|
|
return self.iregions[i], nil
|
|
}
|
|
}
|
|
return nil, ErrorNotFound()
|
|
}
|
|
|
|
func (self *SAwsClient) GetIHostById(id string) (cloudprovider.ICloudHost, error) {
|
|
for i := 0; i < len(self.iregions); i += 1 {
|
|
ihost, err := self.iregions[i].GetIHostById(id)
|
|
if err == nil {
|
|
return ihost, nil
|
|
} else if err != cloudprovider.ErrNotFound {
|
|
return nil, err
|
|
}
|
|
}
|
|
return nil, ErrorNotFound()
|
|
}
|
|
|
|
func (self *SAwsClient) GetIVpcById(id string) (cloudprovider.ICloudVpc, error) {
|
|
for i := 0; i < len(self.iregions); i += 1 {
|
|
ihost, err := self.iregions[i].GetIVpcById(id)
|
|
if err == nil {
|
|
return ihost, nil
|
|
} else if err != cloudprovider.ErrNotFound {
|
|
return nil, err
|
|
}
|
|
}
|
|
return nil, ErrorNotFound()
|
|
}
|
|
|
|
func (self *SAwsClient) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) {
|
|
for i := 0; i < len(self.iregions); i += 1 {
|
|
ihost, err := self.iregions[i].GetIStorageById(id)
|
|
if err == nil {
|
|
return ihost, nil
|
|
} else if err != cloudprovider.ErrNotFound {
|
|
return nil, err
|
|
}
|
|
}
|
|
return nil, ErrorNotFound()
|
|
}
|
|
|
|
type SAccountBalance struct {
|
|
AvailableAmount float64
|
|
AvailableCashAmount float64
|
|
CreditAmount float64
|
|
MybankCreditAmount float64
|
|
Currency string
|
|
}
|
|
|
|
func (self *SAwsClient) QueryAccountBalance() (*SAccountBalance, error) {
|
|
return nil, cloudprovider.ErrNotSupported
|
|
}
|
|
|
|
func (self *SAwsClient) GetIProjects() ([]cloudprovider.ICloudProject, error) {
|
|
return nil, cloudprovider.ErrNotImplemented
|
|
}
|
|
|
|
func (self *SAwsClient) GetAccessEnv() string {
|
|
switch self.accessUrl {
|
|
case AWS_INTERNATIONAL_CLOUDENV:
|
|
return api.CLOUD_ACCESS_ENV_AWS_GLOBAL
|
|
case AWS_CHINA_CLOUDENV:
|
|
return api.CLOUD_ACCESS_ENV_AWS_CHINA
|
|
default:
|
|
return api.CLOUD_ACCESS_ENV_AWS_GLOBAL
|
|
}
|
|
}
|
|
|
|
func jsonRequest(cli *client.Client, apiName string, params map[string]string, retval interface{}, debug bool) error {
|
|
op := &request.Operation{
|
|
Name: apiName,
|
|
HTTPMethod: "POST",
|
|
HTTPPath: "/",
|
|
Paginator: &request.Paginator{
|
|
InputTokens: []string{"NextToken"},
|
|
OutputTokens: []string{"NextToken"},
|
|
LimitToken: "MaxResults",
|
|
TruncationToken: "",
|
|
},
|
|
}
|
|
|
|
req := cli.NewRequest(op, params, retval)
|
|
err := req.Send()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (self *SAwsClient) GetCapabilities() []string {
|
|
caps := []string{
|
|
// cloudprovider.CLOUD_CAPABILITY_PROJECT,
|
|
cloudprovider.CLOUD_CAPABILITY_COMPUTE,
|
|
cloudprovider.CLOUD_CAPABILITY_NETWORK,
|
|
cloudprovider.CLOUD_CAPABILITY_LOADBALANCER,
|
|
cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE,
|
|
cloudprovider.CLOUD_CAPABILITY_RDS,
|
|
// cloudprovider.CLOUD_CAPABILITY_CACHE,
|
|
// cloudprovider.CLOUD_CAPABILITY_EVENT,
|
|
}
|
|
return caps
|
|
}
|
|
|
|
func cloudWatchRequest(cli *client.Client, apiName string, params *cloudwatch.GetMetricStatisticsInput, retval interface{}, debug bool) error {
|
|
op := &request.Operation{
|
|
Name: apiName,
|
|
HTTPMethod: "POST",
|
|
HTTPPath: "/",
|
|
}
|
|
|
|
req := cli.NewRequest(op, params, retval)
|
|
err := req.Send()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|