diff --git a/pkg/apis/compute/elasticip.go b/pkg/apis/compute/elasticip.go index 68d326930d..767187550f 100644 --- a/pkg/apis/compute/elasticip.go +++ b/pkg/apis/compute/elasticip.go @@ -104,3 +104,10 @@ type ElasticipAssociateInput struct { // default: server InstanceType string `json:"instance_type"` } + +type ElasticDissociateInput struct { + // 注意: 若关联到aws NAT网关后,目前没办法解除关联关系 + // 是否解绑后自动删除弹性公网IP + // default: false + AutoDelete bool `json:"auto_delete"` +} diff --git a/pkg/compute/models/elasticips.go b/pkg/compute/models/elasticips.go index 052b2110f7..0237bfd18b 100644 --- a/pkg/compute/models/elasticips.go +++ b/pkg/compute/models/elasticips.go @@ -1087,7 +1087,7 @@ func (self *SElasticip) StartEipAssociateTask(ctx context.Context, userCred mccl return task.ScheduleRun(nil) } -func (self *SElasticip) PerformDissociate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { +func (self *SElasticip) PerformDissociate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ElasticDissociateInput) (jsonutils.JSONObject, error) { if len(self.AssociateId) == 0 { return nil, nil // success } @@ -1111,9 +1111,7 @@ func (self *SElasticip) PerformDissociate(ctx context.Context, userCred mcclient return nil, httperrors.NewUnsupportOperationError("fixed public eip cannot be dissociated") } - autoDelete := jsonutils.QueryBoolean(data, "auto_delete", false) - - err = self.StartEipDissociateTask(ctx, userCred, autoDelete, "") + err = self.StartEipDissociateTask(ctx, userCred, input.AutoDelete, "") return nil, err } diff --git a/pkg/hostman/hostdeployer/apis/deploy.pb.go b/pkg/hostman/hostdeployer/apis/deploy.pb.go index 6637c473d5..ca468e20f7 100644 --- a/pkg/hostman/hostdeployer/apis/deploy.pb.go +++ b/pkg/hostman/hostdeployer/apis/deploy.pb.go @@ -10,11 +10,12 @@ package apis import ( context "context" fmt "fmt" + math "math" + proto "github.com/golang/protobuf/proto" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" - math "math" ) // Reference imports to suppress errors if they are not otherwise used. diff --git a/pkg/multicloud/aws/aws.go b/pkg/multicloud/aws/aws.go index 05071d8d9d..71a43ed9b5 100644 --- a/pkg/multicloud/aws/aws.go +++ b/pkg/multicloud/aws/aws.go @@ -23,14 +23,10 @@ import ( sdk "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/client" - "github.com/aws/aws-sdk-go/aws/client/metadata" - "github.com/aws/aws-sdk-go/aws/corehandlers" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/credentials/stscreds" "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/session" - v4 "github.com/aws/aws-sdk-go/aws/signer/v4" - "github.com/aws/aws-sdk-go/private/protocol/query" "github.com/aws/aws-sdk-go/service/cloudwatch" "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/elasticache" @@ -186,19 +182,6 @@ 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 - } -} -*/ - var ( // cache for describeRegions describeRegionResult map[string]*ec2.DescribeRegionsOutput = map[string]*ec2.DescribeRegionsOutput{} @@ -328,26 +311,6 @@ func (client *SAwsClient) fetchOwnerId() error { return errors.Wrap(err, "GetCallerIdentity") } client.ownerId = ident.Account - - /* 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 - } - } */ - return nil } @@ -527,40 +490,6 @@ func (self *SAwsClient) GetAccessEnv() string { } } -func (self *SAwsClient) request(regionId, serviceName, serviceId, apiVersion string, apiName string, params map[string]string, retval interface{}, assumeRole bool) error { - if len(regionId) == 0 { - regionId = self.getDefaultRegionId() - } - session, err := self.getAwsSession(regionId, assumeRole) - if err != nil { - return err - } - c := session.ClientConfig(serviceName) - metadata := metadata.ClientInfo{ - ServiceName: serviceName, - ServiceID: serviceId, - SigningName: c.SigningName, - SigningRegion: c.SigningRegion, - Endpoint: c.Endpoint, - APIVersion: apiVersion, - } - - if self.debug { - logLevel := aws.LogLevelType(uint(aws.LogDebugWithRequestErrors) + uint(aws.LogDebugWithHTTPBody)) - c.Config.LogLevel = &logLevel - } - - client := client.New(*c.Config, metadata, c.Handlers) - client.Handlers.Sign.PushBackNamed(v4.SignRequestHandler) - client.Handlers.Build.PushBackNamed(buildHandler) - client.Handlers.Unmarshal.PushBackNamed(UnmarshalHandler) - client.Handlers.UnmarshalMeta.PushBackNamed(query.UnmarshalMetaHandler) - client.Handlers.UnmarshalError.PushBackNamed(query.UnmarshalErrorHandler) - client.Handlers.Validate.Remove(corehandlers.ValidateEndpointHandler) - return jsonRequest(client, apiName, params, retval, true) - -} - func (self *SAwsClient) iamRequest(apiName string, params map[string]string, retval interface{}) error { return self.request("", IAM_SERVICE_NAME, IAM_SERVICE_ID, "2010-05-08", apiName, params, retval, true) } @@ -569,30 +498,6 @@ func (self *SAwsClient) stsRequest(apiName string, params map[string]string, ret return self.request("", STS_SERVICE_NAME, STS_SERVICE_ID, "2011-06-15", apiName, params, retval, false) } -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 { - if e, ok := err.(awserr.RequestFailure); ok && e.StatusCode() == 404 { - return cloudprovider.ErrNotFound - } - return err - } - return nil -} - func (self *SAwsClient) GetCapabilities() []string { caps := []string{ // cloudprovider.CLOUD_CAPABILITY_PROJECT, diff --git a/pkg/multicloud/aws/aws_request.go b/pkg/multicloud/aws/aws_request.go new file mode 100644 index 0000000000..9f3a21c5ca --- /dev/null +++ b/pkg/multicloud/aws/aws_request.go @@ -0,0 +1,233 @@ +// 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 ( + "encoding/xml" + "io" + "io/ioutil" + "net/url" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/aws/client" + "github.com/aws/aws-sdk-go/aws/client/metadata" + "github.com/aws/aws-sdk-go/aws/corehandlers" + "github.com/aws/aws-sdk-go/aws/request" + v4 "github.com/aws/aws-sdk-go/aws/signer/v4" + "github.com/aws/aws-sdk-go/private/protocol/query" + "github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +var UnmarshalHandler = request.NamedHandler{Name: "yunion.query.Unmarshal", Fn: Unmarshal} + +func Unmarshal(r *request.Request) { + defer r.HTTPResponse.Body.Close() + if r.DataFilled() { + var decoder *xml.Decoder + if DEBUG { + body, err := ioutil.ReadAll(r.HTTPResponse.Body) + if err != nil { + r.Error = awserr.NewRequestFailure( + awserr.New("ioutil.ReadAll", "read response body", err), + r.HTTPResponse.StatusCode, + r.RequestID, + ) + return + } + log.Debugf("response: \n%s", string(body)) + decoder = xml.NewDecoder(strings.NewReader(string(body))) + } else { + decoder = xml.NewDecoder(r.HTTPResponse.Body) + } + if r.ClientInfo.ServiceID == EC2_SERVICE_ID { + err := decoder.Decode(r.Data) + if err != nil { + r.Error = awserr.NewRequestFailure( + awserr.New("SerializationError", "failed decoding EC2 Query response", err), + r.HTTPResponse.StatusCode, + r.RequestID, + ) + } + return + } + for { + tok, err := decoder.Token() + if err != nil { + if err == io.EOF { + break + } + r.Error = awserr.NewRequestFailure( + awserr.New("decoder.Token()", "get token", err), + r.HTTPResponse.StatusCode, + r.RequestID, + ) + return + } + + if tok == nil { + break + } + + switch typed := tok.(type) { + case xml.CharData: + continue + case xml.StartElement: + if typed.Name.Local == r.Operation.Name+"Result" { + err = decoder.DecodeElement(r.Data, &typed) + if err != nil { + r.Error = awserr.NewRequestFailure( + awserr.New("DecodeElement", "failed decoding Query response", err), + r.HTTPResponse.StatusCode, + r.RequestID, + ) + } + return + } + case xml.EndElement: + break + } + } + + } +} + +var buildHandler = request.NamedHandler{Name: "yunion.query.Build", Fn: Build} + +func Build(r *request.Request) { + body := url.Values{ + "Action": {r.Operation.Name}, + "Version": {r.ClientInfo.APIVersion}, + } + if r.Params != nil { + if params, ok := r.Params.(map[string]string); ok { + for k, v := range params { + body.Add(k, v) + } + } + } + + if DEBUG { + log.Debugf("params: %s", body.Encode()) + } + + if !r.IsPresigned() { + r.HTTPRequest.Method = "POST" + r.HTTPRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=utf-8") + r.SetBufferBody([]byte(body.Encode())) + } else { // This is a pre-signed request + r.HTTPRequest.Method = "GET" + r.HTTPRequest.URL.RawQuery = body.Encode() + } +} + +var UnmarshalErrorHandler = request.NamedHandler{Name: "awssdk.ec2query.UnmarshalError", Fn: UnmarshalError} + +func UnmarshalError(r *request.Request) { + defer r.HTTPResponse.Body.Close() + + respErr := &struct { + XMLName xml.Name `xml:"Response"` + Code string `xml:"Errors>Error>Code"` + Message string `xml:"Errors>Error>Message"` + RequestID string `xml:"RequestID"` + }{} + + err := xmlutil.UnmarshalXMLError(&respErr, r.HTTPResponse.Body) + if err != nil { + r.Error = awserr.NewRequestFailure( + awserr.New(request.ErrCodeSerialization, + "failed to unmarshal error message", err), + r.HTTPResponse.StatusCode, + r.RequestID, + ) + return + } + + if strings.Contains(respErr.Code, "NotFound") { + r.Error = errors.Wrapf(cloudprovider.ErrNotFound, jsonutils.Marshal(respErr).String()) + return + } + + r.Error = awserr.NewRequestFailure( + awserr.New(respErr.Code, respErr.Message, nil), + r.HTTPResponse.StatusCode, + respErr.RequestID, + ) +} + +func (self *SAwsClient) request(regionId, serviceName, serviceId, apiVersion string, apiName string, params map[string]string, retval interface{}, assumeRole bool) error { + if len(regionId) == 0 { + regionId = self.getDefaultRegionId() + } + session, err := self.getAwsSession(regionId, assumeRole) + if err != nil { + return err + } + c := session.ClientConfig(serviceName) + metadata := metadata.ClientInfo{ + ServiceName: serviceName, + ServiceID: serviceId, + SigningName: c.SigningName, + SigningRegion: c.SigningRegion, + Endpoint: c.Endpoint, + APIVersion: apiVersion, + } + + if self.debug { + logLevel := aws.LogLevelType(uint(aws.LogDebugWithRequestErrors) + uint(aws.LogDebugWithHTTPBody)) + c.Config.LogLevel = &logLevel + } + + client := client.New(*c.Config, metadata, c.Handlers) + client.Handlers.Sign.PushBackNamed(v4.SignRequestHandler) + client.Handlers.Build.PushBackNamed(buildHandler) + client.Handlers.Unmarshal.PushBackNamed(UnmarshalHandler) + client.Handlers.UnmarshalMeta.PushBackNamed(query.UnmarshalMetaHandler) + client.Handlers.UnmarshalError.PushBackNamed(UnmarshalErrorHandler) + client.Handlers.Validate.Remove(corehandlers.ValidateEndpointHandler) + return jsonRequest(client, apiName, params, retval, true) +} + +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 { + if e, ok := err.(awserr.RequestFailure); ok && e.StatusCode() == 404 { + return cloudprovider.ErrNotFound + } + return err + } + return nil +} diff --git a/pkg/multicloud/aws/eip.go b/pkg/multicloud/aws/eip.go index 9daff068ea..72fe29e3bf 100644 --- a/pkg/multicloud/aws/eip.go +++ b/pkg/multicloud/aws/eip.go @@ -18,10 +18,7 @@ import ( "fmt" "time" - "github.com/aws/aws-sdk-go/service/ec2" - "yunion.io/x/jsonutils" - "yunion.io/x/log" "yunion.io/x/pkg/errors" "yunion.io/x/onecloud/pkg/apis/billing" @@ -40,17 +37,19 @@ type SEipAddress struct { multicloud.SEipBase multicloud.AwsTags - AllocationId string - Bandwidth int - Status string - InstanceId string - AssociationId string - Domain string - NetworkInterfaceId string - NetworkInterfaceOwnerId string - PrivateIpAddress string - IpAddress string - Name string + AllocationId string `xml:"allocationId"` + AssociationId string `xml:"associationId"` + CarrierIp string `xml:"carrierIp"` + CustomerOwnedIp string `xml:"customerOwnedIp"` + CustomerOwnedIpv4Pool string `xml:"customerOwnedIpv4Pool"` + Domain string `xml:"domain"` + InstanceId string `xml:"instanceId"` + NetworkBorderGroup string `xml:"networkBorderGroup"` + NetworkInterfaceId string `xml:"networkInterfaceId"` + NetworkInterfaceOwnerId string `xml:"networkInterfaceOwnerId"` + PrivateIpAddress string `xml:"privateIpAddress"` + PublicIp string `xml:"publicIp"` + PublicIpv4Pool string `xml:"publicIpv4Pool"` } func (self *SEipAddress) GetId() string { @@ -58,11 +57,11 @@ func (self *SEipAddress) GetId() string { } func (self *SEipAddress) GetName() string { - if len(self.Name) == 0 { - return self.IpAddress + name := self.AwsTags.GetName() + if len(name) > 0 { + return name } - - return self.Name + return self.AllocationId } func (self *SEipAddress) GetGlobalId() string { @@ -70,13 +69,7 @@ func (self *SEipAddress) GetGlobalId() string { } func (self *SEipAddress) GetStatus() string { - switch self.Status { - // todo: EIP_STATUS_INUSE 对应READY? - case EIP_STATUS_AVAILABLE, EIP_STATUS_INUSE: - return api.EIP_STATUS_READY - default: - return api.EIP_STATUS_UNKNOWN - } + return api.EIP_STATUS_READY } func (self *SEipAddress) Refresh() error { @@ -94,33 +87,67 @@ func (self *SEipAddress) IsEmulated() bool { if self.AllocationId == self.InstanceId { return true } - return false } func (self *SEipAddress) GetIpAddr() string { - return self.IpAddress + return self.PublicIp } func (self *SEipAddress) GetMode() string { if self.InstanceId == self.AllocationId { return api.EIP_MODE_INSTANCE_PUBLICIP - } else { - return api.EIP_MODE_STANDALONE_EIP } + return api.EIP_MODE_STANDALONE_EIP } func (self *SEipAddress) GetAssociationType() string { - // todo : ? + if len(self.InstanceId) > 0 { + return api.EIP_ASSOCIATE_TYPE_SERVER + } + if len(self.NetworkInterfaceId) > 0 { + net, err := self.region.GetNetworkInterface(self.NetworkInterfaceId) + if err != nil { + return "" + } + switch net.InterfaceType { + case "nat_gateway": + return api.EIP_ASSOCIATE_TYPE_NAT_GATEWAY + } + } return api.EIP_ASSOCIATE_TYPE_SERVER } func (self *SEipAddress) GetAssociationExternalId() string { + if len(self.InstanceId) > 0 { + return self.InstanceId + } + if len(self.NetworkInterfaceId) > 0 { + net, err := self.region.GetNetworkInterface(self.NetworkInterfaceId) + if err != nil { + return "" + } + switch net.InterfaceType { + case "nat_gateway": + nats, err := self.region.GetNatGateways(nil, net.VpcId, net.SubnetId) + if err != nil { + return "" + } + for i := range nats { + for _, addr := range nats[i].NatGatewayAddresses { + if addr.PublicIp == self.PublicIp { + return nats[i].GetGlobalId() + } + } + } + return "" + } + } return self.InstanceId } func (self *SEipAddress) GetBandwidth() int { - return self.Bandwidth + return 0 } func (self *SEipAddress) GetINetworkId() string { @@ -128,7 +155,6 @@ func (self *SEipAddress) GetINetworkId() string { } func (self *SEipAddress) GetInternetChargeType() string { - // todo : implement me return api.EIP_CHARGE_TYPE_BY_TRAFFIC } @@ -137,207 +163,117 @@ func (self *SEipAddress) Delete() error { } func (self *SEipAddress) Associate(conf *cloudprovider.AssociateConfig) error { - err := self.region.AssociateEip(self.AllocationId, conf.InstanceId) - if err != nil { - return err - } - err = cloudprovider.WaitStatusWithDelay(self, api.EIP_STATUS_READY, 5*time.Second, 10*time.Second, 180*time.Second) - return err + return self.region.AssociateEip(self.AllocationId, conf.InstanceId) } func (self *SEipAddress) Dissociate() error { - err := self.region.DissociateEip(self.AllocationId, self.InstanceId) - if err != nil { - return err + if len(self.InstanceId) > 0 { + return self.region.DissociateEip(self.InstanceId) } - err = cloudprovider.WaitStatus(self, api.EIP_STATUS_READY, 10*time.Second, 180*time.Second) - return err + return self.region.DissociateEip(self.AssociationId) } func (self *SEipAddress) ChangeBandwidth(bw int) error { return self.region.UpdateEipBandwidth(self.AllocationId, bw) } -func (self *SRegion) GetEips(eipId string, eipAddress string, offset int, limit int) ([]SEipAddress, int, error) { - params := ec2.DescribeAddressesInput{} - if len(eipId) > 0 { - params.SetAllocationIds([]*string{&eipId}) +func (self *SRegion) GetEips(id, ip, associateId string) ([]SEipAddress, error) { + params := map[string]string{} + if len(id) > 0 { + params["AllocationId.1"] = id } - - if len(eipAddress) > 0 { - params.SetPublicIps([]*string{&eipAddress}) + if len(ip) > 0 { + params["PublicIp.1"] = ip } - - ec2Client, err := self.getEc2Client() + idx := 1 + if len(associateId) > 0 { + params[fmt.Sprintf("Filter.%d.Name", idx)] = "association-id" + params[fmt.Sprintf("Filter.%d.Value.1", idx)] = associateId + idx++ + } + result := struct { + AddressesSet []SEipAddress `xml:"addressesSet>item"` + }{} + err := self.ec2Request("DescribeAddresses", params, &result) if err != nil { - return nil, 0, errors.Wrap(err, "getEc2Client") + return nil, errors.Wrapf(err, "DescribeAddresses") } - res, err := ec2Client.DescribeAddresses(¶ms) - err = parseNotFoundError(err) - if err != nil { - log.Errorf("DescribeEipAddresses fail %s", err) - return nil, 0, err - } - - eips := make([]SEipAddress, 0) - for _, ip := range res.Addresses { - if err := FillZero(ip); err != nil { - return nil, 0, err - } - - tagspec := TagSpec{ResourceType: "eip"} - tagspec.LoadingEc2Tags(ip.Tags) - - var status string - if len(*ip.AssociationId) > 0 { - status = EIP_STATUS_INUSE - } else { - status = EIP_STATUS_AVAILABLE - } - - eip := SEipAddress{ - region: self, - AllocationId: *ip.AllocationId, - Status: status, - InstanceId: *ip.InstanceId, - AssociationId: *ip.AssociationId, - Domain: *ip.Domain, - NetworkInterfaceId: *ip.NetworkInterfaceId, - NetworkInterfaceOwnerId: *ip.NetworkInterfaceOwnerId, - PrivateIpAddress: *ip.PrivateIpAddress, - IpAddress: *ip.PublicIp, - Name: tagspec.GetNameTag(), - } - jsonutils.Update(&eip.AwsTags.TagSet, ip.Tags) - eips = append(eips, eip) - } - return eips, len(eips), nil + return result.AddressesSet, nil } -func (self *SRegion) GetEip(eipId string) (*SEipAddress, error) { - // 这里必须强制要求eipId大于零。避免用户账号正好只有一个eip的情况,返回错误的eip。 - if len(eipId) == 0 { - return nil, fmt.Errorf("GetEip eipId should not be emtpy.") - } - - eips, total, err := self.GetEips(eipId, "", 0, 0) +func (self *SRegion) GetEip(id string) (*SEipAddress, error) { + eips, err := self.GetEips(id, "", "") if err != nil { - log.Errorf("GetEips %s: %s", eipId, err) - return nil, errors.Wrap(err, "GetEips") + return nil, errors.Wrapf(err, "GetEips") } - if total != 1 { - return nil, errors.Wrap(cloudprovider.ErrNotFound, "GetEips") + for i := range eips { + if eips[i].GetGlobalId() == id { + eips[i].region = self + return &eips[i], nil + } } - return &eips[0], nil + return nil, errors.Wrapf(cloudprovider.ErrNotFound, id) } func (self *SRegion) GetEipByIpAddress(eipAddress string) (*SEipAddress, error) { - eips, total, err := self.GetEips("", eipAddress, 0, 0) + eips, err := self.GetEips("", eipAddress, "") if err != nil { - log.Errorf("GetEips %s: %s", eipAddress, err) - return nil, errors.Wrap(err, "GetEips") + return nil, errors.Wrapf(err, "GetEips") } - - if total != 1 { - return nil, errors.Wrap(cloudprovider.ErrNotFound, "GetEips") - } - return &eips[0], nil -} - -func (self *SRegion) AllocateEIP(domainType string) (*SEipAddress, error) { - params := &ec2.AllocateAddressInput{} - params.SetDomain(domainType) - - ec2Client, err := self.getEc2Client() - if err != nil { - return nil, errors.Wrap(err, "getEc2Client") - } - eip, err := ec2Client.AllocateAddress(params) - if err != nil { - log.Errorf("AllocateEipAddress fail %s", err) - return nil, errors.Wrap(err, "AllocateAddress") - } - - err = self.fetchInfrastructure() - if err != nil { - return nil, errors.Wrap(err, "fetchInfrastructure") - } - return self.GetEip(*eip.AllocationId) -} - -func (self *SRegion) CreateEIP(eip *cloudprovider.SEip) (cloudprovider.ICloudEIP, error) { - ec2Client, err := self.getEc2Client() - if err != nil { - return nil, errors.Wrap(err, "getEc2Client") - } - // todo: aws 不支持指定bwMbps, chargeType ? - log.Debugf("CreateEip: aws not support specific params name/bwMbps/chargeType.") - ieip, err := self.AllocateEIP("vpc") - if err == nil && len(eip.Name) > 0 { - eipId := ieip.GetId() - k := "Name" - nameTag := &ec2.Tag{Key: &k, Value: &eip.Name} - params := &ec2.CreateTagsInput{} - params.SetResources([]*string{&eipId}) - params.SetTags([]*ec2.Tag{nameTag}) - - // name 创建成功与否不影响eip的正常使用 - if _, e := ec2Client.CreateTags(params); e != nil { - log.Infof("CreateEIP create name tag failed: %s", e) + for i := range eips { + if eips[i].GetIpAddr() == eipAddress { + eips[i].region = self + return &eips[i], nil } } + return nil, errors.Wrapf(cloudprovider.ErrNotFound, eipAddress) +} - return ieip, err +func (self *SRegion) AllocateEIP(opts *cloudprovider.SEip) (*SEipAddress, error) { + params := map[string]string{ + "Domain": "vpc", + } + if len(opts.Name) > 0 { + params["TagSpecification.1.ResourceType"] = "elastic-ip" + params["TagSpecification.1.Tag.1.Key"] = "Name" + params["TagSpecification.1.Tag.1.Value"] = opts.Name + } + ret := SEipAddress{region: self} + err := self.ec2Request("AllocateAddress", params, &ret) + if err != nil { + return nil, errors.Wrapf(err, "AllocateAddress") + } + return &ret, nil +} + +func (self *SRegion) CreateEIP(opts *cloudprovider.SEip) (cloudprovider.ICloudEIP, error) { + eip, err := self.AllocateEIP(opts) + if err != nil { + return nil, errors.Wrapf(err, "AllocateEIP") + } + return eip, nil } func (self *SRegion) DeallocateEIP(eipId string) error { - ec2Client, err := self.getEc2Client() - if err != nil { - return errors.Wrap(err, "getEc2Client") + params := map[string]string{ + "AllocationId": eipId, } - params := &ec2.ReleaseAddressInput{} - params.SetAllocationId(eipId) - _, err = ec2Client.ReleaseAddress(params) - return errors.Wrap(err, "ReleaseAddress") + return self.ec2Request("ReleaseAddress", params, nil) } func (self *SRegion) AssociateEip(eipId string, instanceId string) error { - params := &ec2.AssociateAddressInput{} - params.SetAllocationId(eipId) - params.SetInstanceId(instanceId) - ec2Client, err := self.getEc2Client() - if err != nil { - return errors.Wrap(err, "getEc2Client") + params := map[string]string{ + "AllocationId": eipId, + "InstanceId": instanceId, } - _, err = ec2Client.AssociateAddress(params) - return errors.Wrap(err, "AssociateAddress") + return self.ec2Request("AssociateAddress", params, nil) } -func (self *SRegion) DissociateEip(eipId string, instanceId string) error { - eip, err := self.GetEip(eipId) - if err != nil { - return err +func (self *SRegion) DissociateEip(insId string) error { + params := map[string]string{ + "AssociationId": insId, } - - if len(eip.AssociationId) == 0 { - // 已经是解绑状态 - return nil - } - - if eip.InstanceId != instanceId { - return fmt.Errorf("eip %s associate with another instance %s", eipId, eip.InstanceId) - } - - params := &ec2.DisassociateAddressInput{} - params.SetAssociationId(eip.AssociationId) - - ec2Client, err := self.getEc2Client() - if err != nil { - return errors.Wrap(err, "getEc2Client") - } - - _, err = ec2Client.DisassociateAddress(params) - return errors.Wrap(err, "DisassociateAddress") + return self.ec2Request("DisassociateAddress", params, nil) } func (self *SRegion) UpdateEipBandwidth(eipId string, bw int) error { diff --git a/pkg/multicloud/aws/instance.go b/pkg/multicloud/aws/instance.go index cd14c75d11..8caae7bce4 100644 --- a/pkg/multicloud/aws/instance.go +++ b/pkg/multicloud/aws/instance.go @@ -90,7 +90,7 @@ type SInstance struct { VlanId string // subnet ID ? VpcAttributes SVpcAttributes SecurityGroupIds SSecurityGroupIds - NetworkInterfaces SNetworkInterfaces + NetworkInterfaces []SNetworkInterface EipAddress SEipAddress Disks []string DeviceNames []string @@ -262,7 +262,7 @@ func (self *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) { func (self *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) { var ( - networkInterfaces = self.NetworkInterfaces.NetworkInterface + networkInterfaces = self.NetworkInterfaces nics = make([]cloudprovider.ICloudNic, 0) ) for _, networkInterface := range networkInterfaces { @@ -278,20 +278,18 @@ func (self *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) { } func (self *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) { - if len(self.EipAddress.IpAddress) > 0 { - return self.host.zone.region.GetEipByIpAddress(self.EipAddress.IpAddress) - } else if len(self.PublicIpAddress.IpAddress) > 0 { - eip := SEipAddress{} + if len(self.EipAddress.PublicIp) > 0 { + return self.host.zone.region.GetEipByIpAddress(self.EipAddress.PublicIp) + } + if len(self.PublicIpAddress.IpAddress) > 0 { + eip := SEipAddress{region: self.host.zone.region} eip.region = self.host.zone.region - eip.IpAddress = self.PublicIpAddress.IpAddress[0] + eip.PublicIp = self.PublicIpAddress.IpAddress[0] eip.InstanceId = self.InstanceId eip.AllocationId = self.InstanceId // fixed. AllocationId等于InstanceId即表示为 仿真EIP。 - eip.Bandwidth = 10000 - eip.Status = EIP_STATUS_INUSE return &eip, nil - } else { - return nil, nil } + return nil, nil } func (self *SInstance) GetVcpuCount() int { @@ -618,7 +616,7 @@ func (self *SRegion) GetInstances(zoneId string, ids []string, offset int, limit } } - var networkInterfaces SNetworkInterfaces + networkInterfaces := []SNetworkInterface{} eipAddress := SEipAddress{} for _, n := range instance.NetworkInterfaces { i := SNetworkInterface{ @@ -626,12 +624,12 @@ func (self *SRegion) GetInstances(zoneId string, ids []string, offset int, limit NetworkInterfaceId: *n.NetworkInterfaceId, PrivateIpAddress: *n.PrivateIpAddress, } - networkInterfaces.NetworkInterface = append(networkInterfaces.NetworkInterface, i) + networkInterfaces = append(networkInterfaces, i) // todo: 可能有多个EIP的情况。目前只支持一个EIP if n.Association != nil && StrVal(n.Association.IpOwnerId) != "amazon" { - if eipAddress.IpAddress == "" && len(StrVal(n.Association.PublicIp)) > 0 { - eipAddress.IpAddress = *n.Association.PublicIp + if eipAddress.PublicIp == "" && len(StrVal(n.Association.PublicIp)) > 0 { + eipAddress.PublicIp = *n.Association.PublicIp } } } diff --git a/pkg/multicloud/aws/natgateway.go b/pkg/multicloud/aws/natgateway.go new file mode 100644 index 0000000000..9a15de4c20 --- /dev/null +++ b/pkg/multicloud/aws/natgateway.go @@ -0,0 +1,244 @@ +// 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 ( + "fmt" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type NatGatewayAddress struct { + AllocationId string `xml:"allocationId"` + NetworkInterfaceId string `xml:"networkInterfaceId"` + PrivateIp string `xml:"privateIp"` + PublicIp string `xml:"publicIp"` +} + +type ProvisionedBandwidth struct { + ProvisionTime time.Time `xml:"provisionTime"` + Provisioned string `xml:"provisioned"` + RequestTime time.Time `xml:"requestTime"` + Requested string `xml:"requested"` + Status string `xml:"status"` +} + +type SNatGateway struct { + multicloud.SNatGatewayBase + multicloud.AwsTags + + region *SRegion + + ConnectivityType string `xml:"connectivityType"` + CreateTime time.Time `xml:"createTime"` + DeleteTime time.Time `xml:"deleteTime"` + FailureCode string `xml:"failureCode"` + FailureMessage string `xml:"failureMessage"` + NatGatewayAddresses []NatGatewayAddress `xml:"natGatewayAddressSet>item"` + NatGatewayId string `xml:"natGatewayId"` + ProvisionedBandwidth ProvisionedBandwidth `xml:"provisionedBandwidth"` + // pending | failed | available | deleting | deleted + State string `xml:"state"` + SubnetId string `xml:"subnetId"` + VpcId string `xml:"vpcId"` +} + +func (self *SNatGateway) GetName() string { + name := self.AwsTags.GetName() + if len(name) > 0 { + return name + } + return self.NatGatewayId +} + +func (self *SNatGateway) GetId() string { + return self.NatGatewayId +} + +func (self *SNatGateway) GetGlobalId() string { + return self.NatGatewayId +} + +func (self *SNatGateway) GetStatus() string { + switch self.State { + case "pending": + return api.NAT_STATUS_ALLOCATE + case "failed": + return api.NAT_STATUS_CREATE_FAILED + case "available": + return api.NAT_STAUTS_AVAILABLE + case "deleting", "deleted": + return api.NAT_STATUS_DELETING + default: + return api.NAT_STATUS_UNKNOWN + } +} + +func (self *SNatGateway) GetNatSpec() string { + return "" +} + +func (self *SNatGateway) Refresh() error { + nat, err := self.region.GetNatGateway(self.NatGatewayId) + if err != nil { + return err + } + return jsonutils.Update(self, nat) +} + +func (self *SNatGateway) GetIEips() ([]cloudprovider.ICloudEIP, error) { + eips, err := self.region.GetEips("", "", self.NatGatewayId) + if err != nil { + return nil, errors.Wrapf(err, "GetEIPs") + } + ret := []cloudprovider.ICloudEIP{} + for i := range eips { + eips[i].region = self.region + ret = append(ret, &eips[i]) + } + return ret, nil +} + +func (self *SNatGateway) GetINatDTable() ([]cloudprovider.ICloudNatDEntry, error) { + return []cloudprovider.ICloudNatDEntry{}, nil +} + +func (self *SNatGateway) GetINatSTable() ([]cloudprovider.ICloudNatSEntry, error) { + return []cloudprovider.ICloudNatSEntry{}, nil +} + +func (self *SNatGateway) GetINatDEntryByID(id string) (cloudprovider.ICloudNatDEntry, error) { + return nil, errors.Wrapf(cloudprovider.ErrNotFound, id) +} + +func (self *SNatGateway) GetINatSEntryByID(id string) (cloudprovider.ICloudNatSEntry, error) { + return nil, errors.Wrapf(cloudprovider.ErrNotFound, id) +} + +func (self *SNatGateway) CreateINatDEntry(rule cloudprovider.SNatDRule) (cloudprovider.ICloudNatDEntry, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (self *SNatGateway) CreateINatSEntry(rule cloudprovider.SNatSRule) (cloudprovider.ICloudNatSEntry, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (self *SNatGateway) GetINetworkId() string { + return self.SubnetId +} + +func (self *SNatGateway) GetBandwidthMb() int { + return 0 +} + +func (self *SNatGateway) GetIpAddr() string { + ipAddrs := []string{} + for _, addr := range self.NatGatewayAddresses { + if len(addr.PrivateIp) > 0 { + ipAddrs = append(ipAddrs, addr.PrivateIp) + } + } + return strings.Join(ipAddrs, ",") +} + +func (self *SNatGateway) Delete() error { + return self.region.DeleteNatgateway(self.NatGatewayId) +} + +func (self *SRegion) DeleteNatgateway(id string) error { + params := map[string]string{ + "NatGatewayId": id, + } + return self.ec2Request("DeleteNatGateway", params, nil) +} + +func (self *SRegion) GetNatGateways(ids []string, vpcId, subnetId string) ([]SNatGateway, error) { + params := map[string]string{} + for i, id := range ids { + params[fmt.Sprintf("NatGatewayId.%d", i+1)] = id + } + idx := 1 + if len(vpcId) > 0 { + params[fmt.Sprintf("Filter.%d.Name", idx)] = "vpc-id" + params[fmt.Sprintf("Filter.%d.Value.1", idx)] = vpcId + idx++ + } + if len(subnetId) > 0 { + params[fmt.Sprintf("Filter.%d.Name", idx)] = "subnet-id" + params[fmt.Sprintf("Filter.%d.Value.1", idx)] = subnetId + idx++ + } + params[fmt.Sprintf("Filter.%d.Name", idx)] = "state" + for i, state := range []string{ + "pending", + "failed", + "available", + "deleting", + } { + params[fmt.Sprintf("Filter.%d.Value.%d", idx, i+1)] = state + } + idx++ + ret := []SNatGateway{} + for { + result := struct { + Nats []SNatGateway `xml:"natGatewaySet>item"` + NextToken string `xml:"nextToken"` + }{} + err := self.ec2Request("DescribeNatGateways", params, &result) + if err != nil { + return nil, errors.Wrapf(err, "DescribeNatGateways") + } + ret = append(ret, result.Nats...) + if len(result.NextToken) == 0 || len(result.Nats) == 0 { + break + } + params["NextToken"] = result.NextToken + } + return ret, nil +} + +func (self *SRegion) GetNatGateway(id string) (*SNatGateway, error) { + nats, err := self.GetNatGateways([]string{id}, "", "") + if err != nil { + return nil, errors.Wrapf(err, "GetNatGateways") + } + for i := range nats { + if nats[i].GetGlobalId() == id { + nats[i].region = self + return &nats[i], nil + } + } + return nil, errors.Wrapf(cloudprovider.ErrNotFound, id) +} + +func (self *SVpc) GetINatGateways() ([]cloudprovider.ICloudNatGateway, error) { + nats, err := self.region.GetNatGateways(nil, self.VpcId, "") + if err != nil { + return nil, errors.Wrapf(err, "GetINatGateways") + } + ret := []cloudprovider.ICloudNatGateway{} + for i := range nats { + nats[i].region = self.region + ret = append(ret, &nats[i]) + } + return ret, nil +} diff --git a/pkg/multicloud/aws/networkinterface.go b/pkg/multicloud/aws/networkinterface.go index 8b869a9dd9..ce8a2784e2 100644 --- a/pkg/multicloud/aws/networkinterface.go +++ b/pkg/multicloud/aws/networkinterface.go @@ -18,6 +18,8 @@ import ( "time" "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" ) type SGroupSet struct { @@ -60,16 +62,39 @@ type SNetworkInterface struct { InterfaceType string `xml:"interfaceType"` } -type SNetworkInterfaces struct { - NetworkInterface []SNetworkInterface `xml:"networkInterfaceSet>item"` +func (self *SRegion) GetNetworkInterface(id string) (*SNetworkInterface, error) { + nets, err := self.GetNetworkInterfaces(id) + if err != nil { + return nil, errors.Wrapf(err, "GetNetworkInterface") + } + for i := range nets { + if nets[i].NetworkInterfaceId == id { + return &nets[i], nil + } + } + return nil, errors.Wrapf(cloudprovider.ErrNotFound, id) } -func (region *SRegion) GetNetworkInterfaces() ([]SNetworkInterface, error) { +func (self *SRegion) GetNetworkInterfaces(id string) ([]SNetworkInterface, error) { params := map[string]string{} - interfaces := SNetworkInterfaces{} - err := region.ec2Request("DescribeNetworkInterfaces", params, &interfaces) - if err != nil { - return nil, errors.Wrap(err, "DescribeNetworkInterfaces") + if len(id) > 0 { + params["NetworkInterfaceId.1"] = id } - return interfaces.NetworkInterface, nil + ret := []SNetworkInterface{} + for { + result := struct { + NetworkInterfaceSet []SNetworkInterface `xml:"networkInterfaceSet>item"` + NextToken string `xml:"nextToken"` + }{} + err := self.ec2Request("DescribeNetworkInterfaces", params, &result) + if err != nil { + return nil, errors.Wrap(err, "DescribeNetworkInterfaces") + } + ret = append(ret, result.NetworkInterfaceSet...) + if len(result.NextToken) == 0 || len(result.NetworkInterfaceSet) == 0 { + break + } + params["NextToken"] = result.NextToken + } + return ret, nil } diff --git a/pkg/multicloud/aws/region.go b/pkg/multicloud/aws/region.go index 8d8458a438..f88851e923 100644 --- a/pkg/multicloud/aws/region.go +++ b/pkg/multicloud/aws/region.go @@ -15,19 +15,13 @@ package aws import ( - "encoding/xml" "fmt" - "io" - "io/ioutil" - "net/url" "strings" "time" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/client" "github.com/aws/aws-sdk-go/aws/client/metadata" - "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/session" v4 "github.com/aws/aws-sdk-go/aws/signer/v4" "github.com/aws/aws-sdk-go/private/protocol/query" @@ -235,108 +229,6 @@ func (self *SRegion) getResourceGroupTagClient() (*resourcegroupstaggingapi.Reso return self.resourceGroupTagClient, nil } -var UnmarshalHandler = request.NamedHandler{Name: "yunion.query.Unmarshal", Fn: Unmarshal} - -func Unmarshal(r *request.Request) { - defer r.HTTPResponse.Body.Close() - if r.DataFilled() { - var decoder *xml.Decoder - if DEBUG { - body, err := ioutil.ReadAll(r.HTTPResponse.Body) - if err != nil { - r.Error = awserr.NewRequestFailure( - awserr.New("ioutil.ReadAll", "read response body", err), - r.HTTPResponse.StatusCode, - r.RequestID, - ) - return - } - log.Debugf("response: \n%s", string(body)) - decoder = xml.NewDecoder(strings.NewReader(string(body))) - } else { - decoder = xml.NewDecoder(r.HTTPResponse.Body) - } - if r.ClientInfo.ServiceID == EC2_SERVICE_ID { - err := decoder.Decode(r.Data) - if err != nil { - r.Error = awserr.NewRequestFailure( - awserr.New("SerializationError", "failed decoding EC2 Query response", err), - r.HTTPResponse.StatusCode, - r.RequestID, - ) - } - return - } - for { - tok, err := decoder.Token() - if err != nil { - if err == io.EOF { - break - } - r.Error = awserr.NewRequestFailure( - awserr.New("decoder.Token()", "get token", err), - r.HTTPResponse.StatusCode, - r.RequestID, - ) - return - } - - if tok == nil { - break - } - - switch typed := tok.(type) { - case xml.CharData: - continue - case xml.StartElement: - if typed.Name.Local == r.Operation.Name+"Result" { - err = decoder.DecodeElement(r.Data, &typed) - if err != nil { - r.Error = awserr.NewRequestFailure( - awserr.New("DecodeElement", "failed decoding Query response", err), - r.HTTPResponse.StatusCode, - r.RequestID, - ) - } - return - } - case xml.EndElement: - break - } - } - - } -} - -var buildHandler = request.NamedHandler{Name: "yunion.query.Build", Fn: Build} - -func Build(r *request.Request) { - body := url.Values{ - "Action": {r.Operation.Name}, - "Version": {r.ClientInfo.APIVersion}, - } - if r.Params != nil { - if params, ok := r.Params.(map[string]string); ok { - for k, v := range params { - body.Add(k, v) - } - } - } - - if DEBUG { - log.Debugf("params: %s", body.Encode()) - } - - if !r.IsPresigned() { - r.HTTPRequest.Method = "POST" - r.HTTPRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=utf-8") - r.SetBufferBody([]byte(body.Encode())) - } else { // This is a pre-signed request - r.HTTPRequest.Method = "GET" - r.HTTPRequest.URL.RawQuery = body.Encode() - } -} - func (self *SRegion) rdsRequest(apiName string, params map[string]string, retval interface{}) error { return self.client.request(self.RegionId, RDS_SERVICE_NAME, RDS_SERVICE_ID, "2014-10-31", apiName, params, retval, true) } @@ -553,19 +445,14 @@ func (self *SRegion) GetIDiskById(id string) (cloudprovider.ICloudDisk, error) { } func (self *SRegion) GetIEips() ([]cloudprovider.ICloudEIP, error) { - _, err := self.getEc2Client() - if err != nil { - return nil, errors.Wrap(err, "getEc2Client") - } - - eips, total, err := self.GetEips("", "", 0, 0) + eips, err := self.GetEips("", "", "") if err != nil { return nil, errors.Wrap(err, "GetEips") } - - ret := make([]cloudprovider.ICloudEIP, total) - for i := 0; i < len(eips); i += 1 { - ret[i] = &eips[i] + ret := []cloudprovider.ICloudEIP{} + for i := range eips { + eips[i].region = self + ret = append(ret, &eips[i]) } return ret, nil } @@ -732,19 +619,12 @@ func (self *SRegion) CreateIVpc(opts *cloudprovider.VpcCreateOptions) (cloudprov return self.GetIVpcById(*vpc.Vpc.VpcId) } -func (self *SRegion) GetIEipById(eipId string) (cloudprovider.ICloudEIP, error) { - eips, total, err := self.GetEips(eipId, "", 0, 0) +func (self *SRegion) GetIEipById(id string) (cloudprovider.ICloudEIP, error) { + eip, err := self.GetEip(id) if err != nil { - log.Errorf("GetEips %s: %s", eipId, err) - return nil, errors.Wrap(err, "GetEips") + return nil, errors.Wrap(err, "GetEip") } - if total == 0 { - return nil, errors.Wrap(cloudprovider.ErrNotFound, "GetIEipById") - } - if total > 1 { - return nil, cloudprovider.ErrDuplicateId - } - return &eips[0], nil + return eip, nil } func (self *SRegion) GetProvider() string { diff --git a/pkg/multicloud/aws/shell/eip.go b/pkg/multicloud/aws/shell/eip.go index d765b99cac..0b46266dfa 100644 --- a/pkg/multicloud/aws/shell/eip.go +++ b/pkg/multicloud/aws/shell/eip.go @@ -15,28 +15,32 @@ package shell import ( + "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/multicloud/aws" "yunion.io/x/onecloud/pkg/util/shellutils" ) func init() { type EipListOptions struct { - Offset int `help:"List offset"` - Limit int `help:"List limit"` + Id string + Addr string + AssociateId string } shellutils.R(&EipListOptions{}, "eip-list", "List eips", func(cli *aws.SRegion, args *EipListOptions) error { - eips, total, e := cli.GetEips("", "", args.Offset, args.Limit) - if e != nil { - return e + eips, err := cli.GetEips(args.Id, args.Addr, args.AssociateId) + if err != nil { + return err } - printList(eips, total, args.Offset, args.Limit, []string{}) + printList(eips, 0, 0, 0, []string{}) return nil }) type EipAllocateOptions struct { + Name string } shellutils.R(&EipAllocateOptions{}, "eip-create", "Allocate an EIP", func(cli *aws.SRegion, args *EipAllocateOptions) error { - eip, err := cli.AllocateEIP("vpc") + opts := cloudprovider.SEip{Name: args.Name} + eip, err := cli.AllocateEIP(&opts) if err != nil { return err } @@ -60,8 +64,13 @@ func init() { err := cli.AssociateEip(args.ID, args.INSTANCE) return err }) - shellutils.R(&EipAssociateOptions{}, "eip-dissociate", "Dissociate an EIP", func(cli *aws.SRegion, args *EipAssociateOptions) error { - err := cli.DissociateEip(args.ID, args.INSTANCE) + + type EipDissociateOptions struct { + INSTANCE string `help:"Instance ID"` + } + + shellutils.R(&EipDissociateOptions{}, "eip-dissociate", "Dissociate an EIP", func(cli *aws.SRegion, args *EipDissociateOptions) error { + err := cli.DissociateEip(args.INSTANCE) return err }) } diff --git a/pkg/multicloud/aws/shell/natgateway.go b/pkg/multicloud/aws/shell/natgateway.go new file mode 100644 index 0000000000..0779f14d5a --- /dev/null +++ b/pkg/multicloud/aws/shell/natgateway.go @@ -0,0 +1,45 @@ +// 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 shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/aws" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type NatListOptions struct { + Ids []string + VpcId string + SubnetId string + } + shellutils.R(&NatListOptions{}, "natgateway-list", "List natgateway", func(cli *aws.SRegion, args *NatListOptions) error { + nats, err := cli.GetNatGateways(args.Ids, args.VpcId, args.SubnetId) + if err != nil { + return err + } + printList(nats, 0, 0, 0, nil) + return nil + }) + + type NatIdOptions struct { + ID string + } + + shellutils.R(&NatIdOptions{}, "natgateway-delete", "Delete natgateway", func(cli *aws.SRegion, args *NatIdOptions) error { + return cli.DeleteNatgateway(args.ID) + }) + +} diff --git a/pkg/multicloud/aws/shell/networkinterface.go b/pkg/multicloud/aws/shell/networkinterface.go index c2239449a4..cbbcbfa6cd 100644 --- a/pkg/multicloud/aws/shell/networkinterface.go +++ b/pkg/multicloud/aws/shell/networkinterface.go @@ -21,9 +21,10 @@ import ( func init() { type NetworkInterfaceListOptions struct { + Id string } shellutils.R(&NetworkInterfaceListOptions{}, "network-interface-list", "List network interfaces", func(cli *aws.SRegion, args *NetworkInterfaceListOptions) error { - interfaces, err := cli.GetNetworkInterfaces() + interfaces, err := cli.GetNetworkInterfaces(args.Id) if err != nil { return err } diff --git a/pkg/multicloud/aws/vpc.go b/pkg/multicloud/aws/vpc.go index c6ab51635e..dec6b5c6a6 100644 --- a/pkg/multicloud/aws/vpc.go +++ b/pkg/multicloud/aws/vpc.go @@ -598,7 +598,7 @@ func (self *SRegion) assignSecurityGroups(secgroupIds []*string, instanceId stri return errors.Wrap(err, "getEc2Client") } - for _, eth := range instance.NetworkInterfaces.NetworkInterface { + for _, eth := range instance.NetworkInterfaces { params := &ec2.ModifyNetworkInterfaceAttributeInput{} params.SetNetworkInterfaceId(eth.NetworkInterfaceId) params.SetGroups(secgroupIds) diff --git a/pkg/multicloud/tag_base.go b/pkg/multicloud/tag_base.go index 374adeb3bf..458684640b 100644 --- a/pkg/multicloud/tag_base.go +++ b/pkg/multicloud/tag_base.go @@ -221,8 +221,22 @@ func (self *AzureTags) SetTags(tags map[string]string, replace bool) error { return errors.Wrap(cloudprovider.ErrNotImplemented, "SetTags") } +type SAwsTag struct { + Key string `xml:"key"` + Value string `xml:"value"` +} + type AwsTags struct { - TagSet []STag + TagSet []SAwsTag `xml:"tagSet>item"` +} + +func (self AwsTags) GetName() string { + for _, tag := range self.TagSet { + if strings.ToLower(tag.Key) == "name" { + return tag.Value + } + } + return "" } func (self *AwsTags) GetTags() (map[string]string, error) {