// 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 ksyun import ( "fmt" "strings" "time" "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/errors" billing_api "yunion.io/x/cloudmux/pkg/apis/billing" api "yunion.io/x/cloudmux/pkg/apis/compute" "yunion.io/x/cloudmux/pkg/cloudprovider" "yunion.io/x/cloudmux/pkg/multicloud" ) type SEipResp struct { RequestID string `json:"RequestId"` NextToken string `json:"NextToken"` AddressesSet []SEip `json:"AddressesSet"` TotalCount int `json:"TotalCount"` } type SEip struct { multicloud.SEipBase region *SRegion SKsyunTags PublicIP string `json:"PublicIp"` AllocationId string `json:"AllocationId"` State string `json:"State"` IPState string `json:"IpState"` LineId string `json:"LineId"` BandWidth int `json:"BandWidth"` InstanceType string `json:"InstanceType"` InstanceId string `json:"InstanceId"` ChargeType string `json:"ChargeType"` IPVersion string `json:"IpVersion"` ProjectId string `json:"ProjectId"` CreateTime string `json:"CreateTime"` Mode string `json:"Mode"` NetworkInterfaceId string `json:"NetworkInterfaceId,omitempty"` NetworkInterfaceType string `json:"NetworkInterfaceType,omitempty"` PrivateIPAddress string `json:"PrivateIpAddress,omitempty"` InternetGatewayId string `json:"InternetGatewayId,omitempty"` HostType string `json:"HostType,omitempty"` } func (region *SRegion) GetEips(eipIds []string) ([]SEip, error) { params := map[string]interface{}{ "MaxResults": "1000", } for i, eipId := range eipIds { params[fmt.Sprintf("AllocationId.%d", i+1)] = eipId } res := []SEip{} for { resp, err := region.eipRequest("DescribeAddresses", params) if err != nil { return nil, errors.Wrap(err, "get eips") } part := SEipResp{} err = resp.Unmarshal(&part) if err != nil { return nil, errors.Wrap(err, "unmarshal eip") } res = append(res, part.AddressesSet...) if len(part.NextToken) == 0 { break } params["NextToken"] = part.NextToken } return res, nil } func (region *SRegion) GetEip(eipId string) (*SEip, error) { eips, err := region.GetEips([]string{eipId}) if err != nil { return nil, errors.Wrap(err, "GetEips") } for _, eip := range eips { if eip.GetId() == eipId { return &eip, nil } } return nil, errors.Wrapf(errors.ErrNotFound, "eip id:%s", eipId) } func (eip *SEip) GetId() string { return eip.AllocationId } func (eip *SEip) GetName() string { return eip.AllocationId } func (eip *SEip) GetGlobalId() string { return eip.AllocationId } func (eip *SEip) GetTags() (map[string]string, error) { tags, err := eip.region.ListTags("eip", eip.AllocationId) if err != nil { return nil, err } return tags.GetTags(), nil } func (eip *SEip) SetTags(tags map[string]string, replace bool) error { return eip.region.SetResourceTags("eip", eip.AllocationId, tags, replace) } func (eip *SEip) GetStatus() string { switch eip.State { case "associate": return api.EIP_STATUS_READY case "disassociate": return api.EIP_STATUS_READY default: return api.EIP_STATUS_UNKNOWN } } func (eip *SEip) Refresh() error { extEip, err := eip.region.GetEip(eip.AllocationId) if err != nil { return errors.Wrap(err, "region.GetEip") } return jsonutils.Update(eip, &extEip) } func (eip *SEip) GetIpAddr() string { return eip.PublicIP } func (eip *SEip) GetMode() string { return api.EIP_MODE_STANDALONE_EIP } func (eip *SEip) GetAssociationType() string { switch eip.InstanceType { case "Ipfwd": return api.EIP_ASSOCIATE_TYPE_SERVER case "Slb": return api.EIP_ASSOCIATE_TYPE_LOADBALANCER default: return eip.InstanceType } } func (eip *SEip) GetAssociationExternalId() string { return eip.InstanceId } func (eip *SEip) GetBandwidth() int { return int(eip.BandWidth) // Mb } func (eip *SEip) GetINetworkId() string { return "" } func (eip *SEip) GetInternetChargeType() string { if eip.ChargeType == "Monthly" { return api.EIP_CHARGE_TYPE_BY_BANDWIDTH } return api.EIP_CHARGE_TYPE_BY_TRAFFIC } func (eip *SEip) GetBillingType() string { if eip.ChargeType == "Monthly" { return billing_api.BILLING_TYPE_PREPAID } return billing_api.BILLING_TYPE_POSTPAID } func (eip *SEip) GetCreatedAt() time.Time { createdAt, _ := time.Parse("2006-01-02 15:04:05", eip.CreateTime) return createdAt } func (eip *SEip) GetExpiredAt() time.Time { return time.Time{} } func (eip *SEip) Delete() error { return eip.region.DeallocateEIP(eip.AllocationId) } func (eip *SEip) Associate(conf *cloudprovider.AssociateConfig) error { return eip.region.AssociateEip(eip.AllocationId, conf.InstanceId) } func (eip *SEip) Dissociate() error { return eip.region.DissociateEip(eip.AllocationId) } func (eip *SEip) ChangeBandwidth(bw int) error { return cloudprovider.ErrNotImplemented } func (region *SRegion) DeallocateEIP(eipId string) error { params := map[string]interface{}{ "AllocationId": eipId, } _, err := region.eipRequest("ReleaseAddress", params) return err } func (region *SRegion) AssociateEip(eipId string, instanceId string) error { params := map[string]interface{}{ "AllocationId": eipId, "InstanceId": instanceId, "InstanceType": "Ipfwd", } vm, err := region.GetInstance(instanceId) if err != nil { return errors.Wrap(err, "GetInstance") } for _, nic := range vm.NetworkInterfaceSet { if len(nic.AllocationId) == 0 { params["NetworkInterfaceId"] = nic.NetworkInterfaceId break } } _, err = region.eipRequest("AssociateAddress", params) return err } func (region *SRegion) DissociateEip(eipId string) error { params := map[string]interface{}{ "AllocationId": eipId, } _, err := region.eipRequest("DisassociateAddress", params) return err } func (eip *SEip) GetProjectId() string { return eip.ProjectId } func (region *SRegion) CreateEip(opts *cloudprovider.SEip) (*SEip, error) { lines, err := region.GetLines() if err != nil { return nil, errors.Wrap(err, "GetLines") } lineId := "" for i := range lines { if strings.Contains(lines[i].LineName, "BGP") { lineId = lines[i].LineId break } } if len(lineId) == 0 { return nil, errors.Wrap(errors.ErrNotFound, "No bgp lines found") } params := map[string]interface{}{ "LineId": lineId, "BandWidth": fmt.Sprintf("%d", opts.BandwidthMbps), } chargeTypes := []string{} if opts.ChargeType == api.EIP_CHARGE_TYPE_BY_BANDWIDTH { chargeTypes = append(chargeTypes, "Monthly") params["PurchaseTime"] = "1" } else { chargeTypes = append(chargeTypes, "TrafficMonthly") chargeTypes = append(chargeTypes, "DailyPaidByTransfer") } ret := &SEip{region: region} var body jsonutils.JSONObject for _, chargeType := range chargeTypes { params["ChargeType"] = chargeType body, err = region.eipRequest("AllocateAddress", params) if err != nil { log.Debugf("not support charge type %s, error: %v", chargeType, err) if e, ok := err.(*sKsyunError); ok && e.ErrorMsg.Code == "PackageNotExists" { continue } return nil, errors.Wrap(err, "AllocateAddress") } err = body.Unmarshal(ret) if err != nil { return nil, errors.Wrap(err, "Unmarshal") } } if err != nil { return nil, errors.Wrap(err, "AllocateAddress") } if len(opts.Tags) > 0 { err = region.CreateTags("eip", ret.AllocationId, opts.Tags) if err != nil { log.Errorf("set tags %s to eip %s failed: %v", opts.Tags, ret.AllocationId, err) } } return ret, nil } type SLine struct { LineId string `json:"LineId"` LineName string `json:"LineName"` } func (region *SRegion) GetLines() ([]SLine, error) { params := map[string]interface{}{} body, err := region.eipRequest("GetLines", params) if err != nil { return nil, errors.Wrap(err, "GetLines") } ret := []SLine{} err = body.Unmarshal(&ret, "LineSet") if err != nil { return nil, errors.Wrap(err, "Unmarshal") } return ret, nil }