Files
cloudpods/pkg/multicloud/aws/aws.go
2020-03-22 19:36:44 +08:00

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
}