diff --git a/pkg/cloudprovider/resources.go b/pkg/cloudprovider/resources.go index 918309d394..883295ca53 100644 --- a/pkg/cloudprovider/resources.go +++ b/pkg/cloudprovider/resources.go @@ -337,6 +337,11 @@ type ICloudNic interface { GetDriver() string GetINetwork() ICloudNetwork + // GetSubAddress returns non-primary/secondary/alias ipv4 addresses of + // the network interface + // + // Implement it when any AssignXx ops methods are implemented + GetSubAddress() ([]string, error) AssignNAddress(count int) ([]string, error) AssignAddress(ipAddrs []string) error // UnassignAddress should not return error if the network interface is @@ -355,6 +360,9 @@ func (d DummyICloudNic) GetMAC() string { panic(errors.ErrNotImpleme func (d DummyICloudNic) InClassicNetwork() bool { panic(errors.ErrNotImplemented) } func (d DummyICloudNic) GetDriver() string { panic(errors.ErrNotImplemented) } func (d DummyICloudNic) GetINetwork() ICloudNetwork { panic(errors.ErrNotImplemented) } +func (d DummyICloudNic) GetSubAddress() ([]string, error) { + return nil, nil +} func (d DummyICloudNic) AssignNAddress(count int) ([]string, error) { return nil, errors.ErrNotImplemented } diff --git a/pkg/multicloud/aliyun/instancenic.go b/pkg/multicloud/aliyun/instancenic.go index a35a399a2c..931487a515 100644 --- a/pkg/multicloud/aliyun/instancenic.go +++ b/pkg/multicloud/aliyun/instancenic.go @@ -15,6 +15,13 @@ package aliyun import ( + "fmt" + "time" + + "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" + + "yunion.io/x/pkg/errors" + "yunion.io/x/onecloud/pkg/cloudprovider" ) @@ -31,6 +38,13 @@ func (self *SInstanceNic) GetId() string { return self.id } +func (self *SInstanceNic) mustGetId() string { + if self.id == "" { + panic("empty network interface id") + } + return self.id +} + func (self *SInstanceNic) GetIP() string { return self.ipAddr } @@ -62,3 +76,158 @@ func (self *SInstanceNic) GetINetwork() cloudprovider.ICloudNetwork { } return nil } + +func (self *SInstanceNic) GetSubAddress() ([]string, error) { + selfId := self.mustGetId() + region := self.instance.host.zone.region + params := map[string]string{ + "RegionId": region.GetId(), + "NetworkInterfaceId.1": selfId, + } + body, err := region.ecsRequest("DescribeNetworkInterfaces", params) + if err != nil { + return nil, err + } + + type DescribeNetworkInterfacesResponse struct { + TotalCount int `json:"TotalCount"` + RequestID string `json:"RequestId"` + PageSize int `json:"PageSize"` + NextToken string `json:"NextToken"` + PageNumber int `json:"PageNumber"` + NetworkInterfaceSets struct { + NetworkInterfaceSet []struct { + Status string `json:"Status"` + PrivateIPAddress string `json:"PrivateIpAddress"` + ZoneID string `json:"ZoneId"` + ResourceGroupID string `json:"ResourceGroupId"` + InstanceID string `json:"InstanceId"` + VSwitchID string `json:"VSwitchId"` + NetworkInterfaceID string `json:"NetworkInterfaceId"` + MacAddress string `json:"MacAddress"` + SecurityGroupIds struct { + SecurityGroupID []string `json:"SecurityGroupId"` + } `json:"SecurityGroupIds"` + Type string `json:"Type"` + Ipv6Sets struct { + Ipv6Set []struct { + Ipv6Address string `json:"Ipv6Address"` + } `json:"Ipv6Set"` + } `json:"Ipv6Sets"` + VpcID string `json:"VpcId"` + OwnerID string `json:"OwnerId"` + AssociatedPublicIP struct { + } `json:"AssociatedPublicIp"` + CreationTime time.Time `json:"CreationTime"` + Tags struct { + Tag []struct { + TagKey string `json:"TagKey"` + TagValue string `json:"TagValue"` + } `json:"Tag"` + } `json:"Tags"` + PrivateIPSets struct { + PrivateIPSet []struct { + PrivateIPAddress string `json:"PrivateIpAddress"` + AssociatedPublicIP struct { + } `json:"AssociatedPublicIp"` + Primary bool `json:"Primary"` + } `json:"PrivateIpSet"` + } `json:"PrivateIpSets"` + } `json:"NetworkInterfaceSet"` + } `json:"NetworkInterfaceSets"` + } + var resp DescribeNetworkInterfacesResponse + if err := body.Unmarshal(&resp); err != nil { + return nil, errors.Wrapf(err, "unmarshal DescribeNetworkInterfacesResponse: %s", body) + } + if got := len(resp.NetworkInterfaceSets.NetworkInterfaceSet); got != 1 { + return nil, errors.Errorf("got %d element(s) in interface set, expect 1", got) + } + var ( + ipAddrs []string + networkInterface = resp.NetworkInterfaceSets.NetworkInterfaceSet[0] + ) + if got := networkInterface.NetworkInterfaceID; got != selfId { + return nil, errors.Errorf("got interface data for %s, expect %s", got, selfId) + } + for _, privateIP := range networkInterface.PrivateIPSets.PrivateIPSet { + if !privateIP.Primary { + ipAddrs = append(ipAddrs, privateIP.PrivateIPAddress) + } + } + return ipAddrs, nil +} + +func (self *SInstanceNic) ipAddrsParams(ipAddrs []string) map[string]string { + region := self.instance.host.zone.region + params := map[string]string{ + "RegionId": region.GetId(), + "NetworkInterfaceId": self.mustGetId(), + } + for i, ipAddr := range ipAddrs { + k := fmt.Sprintf("PrivateIpAddress.%d", i+1) + params[k] = ipAddr + } + return params +} + +func (self *SInstanceNic) AssignAddress(ipAddrs []string) error { + var ( + selfId = self.mustGetId() + instance = self.instance + zone = instance.host.zone + region = zone.region + ) + ecsClient, err := region.getEcsClient() + if err != nil { + return err + } + request := ecs.CreateAssignPrivateIpAddressesRequest() + request.Scheme = "https" + + request.NetworkInterfaceId = selfId + request.PrivateIpAddress = &ipAddrs + resp, err := ecsClient.AssignPrivateIpAddresses(request) + if err != nil { + return errors.Wrapf(err, "AssignPrivateIpAddresses") + } + + allocated := resp.AssignedPrivateIpAddressesSet.PrivateIpSet.PrivateIpAddress + if len(allocated) != len(ipAddrs) { + return errors.Errorf("AssignAddress want %d addresses, got %d", len(ipAddrs), len(allocated)) + } + for i := 0; i < len(ipAddrs); i++ { + ip0 := ipAddrs[i] + ip1 := allocated[i] + if ip0 != ip1 { + return errors.Errorf("AssignAddress address %d does not match: want %s, got %s", i, ip0, ip1) + } + } + return nil +} + +func (self *SInstanceNic) UnassignAddress(ipAddrs []string) error { + var ( + selfId = self.mustGetId() + instance = self.instance + zone = instance.host.zone + region = zone.region + ) + ecsClient, err := region.getEcsClient() + if err != nil { + return err + } + request := ecs.CreateUnassignPrivateIpAddressesRequest() + request.Scheme = "https" + + request.NetworkInterfaceId = selfId + request.PrivateIpAddress = &ipAddrs + resp, err := ecsClient.UnassignPrivateIpAddresses(request) + if err != nil { + if resp.GetHttpStatus() == 404 { + return nil + } + return err + } + return nil +} diff --git a/pkg/multicloud/aliyun/region.go b/pkg/multicloud/aliyun/region.go index c40c601db6..1434be3e05 100644 --- a/pkg/multicloud/aliyun/region.go +++ b/pkg/multicloud/aliyun/region.go @@ -20,6 +20,7 @@ import ( "time" "github.com/aliyun/alibaba-cloud-sdk-go/sdk" + "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" "github.com/aliyun/aliyun-oss-go-sdk/oss" "yunion.io/x/jsonutils" @@ -83,6 +84,17 @@ func (self *SRegion) getSdkClient() (*sdk.Client, error) { return self.sdkClient, nil } +func (self *SRegion) getEcsClient() (*ecs.Client, error) { + sdkClient, err := self.getSdkClient() + if err != nil { + return nil, errors.Wrap(err, "getSdkClient") + } + ecsClient := &ecs.Client{ + Client: *sdkClient, + } + return ecsClient, nil +} + func (self *SRegion) getOSSExternalDomain() string { return getOSSExternalDomain(self.RegionId) }