diff --git a/cmd/climc/shell/compute/cloudaccounts.go b/cmd/climc/shell/compute/cloudaccounts.go index af1b85750c..b49c6700e4 100644 --- a/cmd/climc/shell/compute/cloudaccounts.go +++ b/cmd/climc/shell/compute/cloudaccounts.go @@ -44,6 +44,7 @@ func init() { cmd.CreateWithKeyword("create-xsky", &options.SXskyCloudAccountCreateOptions{}) cmd.CreateWithKeyword("create-ctyun", &options.SCtyunCloudAccountCreateOptions{}) cmd.CreateWithKeyword("create-apsara", &options.SApsaraCloudAccountCreateOptions{}) + cmd.CreateWithKeyword("create-ecloud", &options.SEcloudCloudAccountCreateOptions{}) cmd.UpdateWithKeyword("update-vmware", &options.SVMwareCloudAccountUpdateOptions{}) cmd.UpdateWithKeyword("update-aliyun", &options.SAliyunCloudAccountUpdateOptions{}) diff --git a/cmd/ecloudcli/main.go b/cmd/ecloudcli/main.go new file mode 100644 index 0000000000..042a3848ba --- /dev/null +++ b/cmd/ecloudcli/main.go @@ -0,0 +1,127 @@ +package main + +import ( + "fmt" + "os" + + "yunion.io/x/structarg" + + "yunion.io/x/onecloud/pkg/multicloud/ecloud" + _ "yunion.io/x/onecloud/pkg/multicloud/ecloud/shell" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +type Options struct { + Help bool `help:"Show help" default:"false"` + Debug bool `help:"Show debug" default:"false"` + AccessKey string `help:"Access key" default:"$ECLOUD_ACCESS_KEY"` + AccessSecret string `help:"Secret" default:"$ECLOUD_ACCESS_SECRET"` + RegionId string `help:"RegionId" default:"$ECLOUD_REGION"` + SUBCOMMAND string `help:"ecloudcli subcommand" subcommand:"true"` +} + +func getSubcommandParser() (*structarg.ArgumentParser, error) { + parse, e := structarg.NewArgumentParser(&Options{}, + "ecloudcli", + "Command-line interface to ecloud API.", + `See "ecloud help COMMAND" for help on a specific command.`) + + if e != nil { + return nil, e + } + + subcmd := parse.GetSubcommand() + if subcmd == nil { + return nil, fmt.Errorf("No subcommand argument.") + } + type HelpOptions struct { + SUBCOMMAND string `help:"sub-command name"` + } + shellutils.R(&HelpOptions{}, "help", "Show help of a subcommand", func(args *HelpOptions) error { + helpstr, e := subcmd.SubHelpString(args.SUBCOMMAND) + if e != nil { + return e + } else { + fmt.Print(helpstr) + return nil + } + }) + for _, v := range shellutils.CommandTable { + _, e := subcmd.AddSubParser(v.Options, v.Command, v.Desc, v.Callback) + if e != nil { + return nil, e + } + } + return parse, nil +} + +func showErrorAndExit(e error) { + fmt.Fprintf(os.Stderr, "%s", e) + fmt.Fprintln(os.Stderr) + os.Exit(1) +} + +func newClient(options *Options) (*ecloud.SRegion, error) { + if len(options.AccessKey) == 0 { + return nil, fmt.Errorf("Missing access key") + } + + if len(options.AccessSecret) == 0 { + return nil, fmt.Errorf("Missing access secret") + } + + cli, err := ecloud.NewEcloudClient( + ecloud.NewEcloudClientConfig( + ecloud.NewRamRoleSigner(options.AccessKey, options.AccessSecret), + ).SetDebug(options.Debug), + ) + if err != nil { + return nil, err + } + + region, err := cli.GetRegionById(options.RegionId) + if err != nil { + return nil, err + } + + return region, nil +} + +func main() { + parser, e := getSubcommandParser() + if e != nil { + showErrorAndExit(e) + } + e = parser.ParseArgs(os.Args[1:], false) + options := parser.Options().(*Options) + + if options.Help { + fmt.Print(parser.HelpString()) + } else { + subcmd := parser.GetSubcommand() + subparser := subcmd.GetSubParser() + if e != nil { + if subparser != nil { + fmt.Print(subparser.Usage()) + } else { + fmt.Print(parser.Usage()) + } + showErrorAndExit(e) + } else { + suboptions := subparser.Options() + if options.SUBCOMMAND == "help" { + e = subcmd.Invoke(suboptions) + } else { + var region *ecloud.SRegion + region, e = newClient(options) + if e != nil { + showErrorAndExit(e) + } + e = subcmd.Invoke(region, suboptions) + } + if e != nil { + showErrorAndExit(e) + } + } + } +} diff --git a/pkg/apis/compute/cachedimages_const.go b/pkg/apis/compute/cachedimages_const.go index 6d59f2a137..713ba0320d 100644 --- a/pkg/apis/compute/cachedimages_const.go +++ b/pkg/apis/compute/cachedimages_const.go @@ -21,6 +21,7 @@ const ( CACHED_IMAGE_STATUS_ACTIVE = "active" CACHED_IMAGE_STATUS_DELETING = "deleting" CACHED_IMAGE_STATUS_CACHE_FAILED = "cache_fail" + CACHED_IMAGE_STATUS_UNKNOWN = "unknown" DOWNLOAD_SESSION_LENGTH = 3600 * 3 // 3 hour ) diff --git a/pkg/apis/compute/cloudaccount_const.go b/pkg/apis/compute/cloudaccount_const.go index 5723c46e63..035a785b4b 100644 --- a/pkg/apis/compute/cloudaccount_const.go +++ b/pkg/apis/compute/cloudaccount_const.go @@ -49,6 +49,7 @@ const ( CLOUD_PROVIDER_ZSTACK = "ZStack" CLOUD_PROVIDER_GOOGLE = "Google" CLOUD_PROVIDER_CTYUN = "Ctyun" + CLOUD_PROVIDER_ECLOUD = "Ecloud" CLOUD_PROVIDER_GENERICS3 = "S3" CLOUD_PROVIDER_CEPH = "Ceph" @@ -87,6 +88,7 @@ const ( CLOUD_ACCESS_ENV_ALIYUN_GLOBAL = CLOUD_PROVIDER_ALIYUN CLOUD_ACCESS_ENV_ALIYUN_FINANCE = CLOUD_PROVIDER_ALIYUN + "-fin" CLOUD_ACCESS_ENV_CTYUN_CHINA = CLOUD_PROVIDER_CTYUN + CLOUD_ACCESS_ENV_ECLOUD_CHINA = CLOUD_PROVIDER_ECLOUD ) var ( @@ -108,6 +110,7 @@ var ( CLOUD_PROVIDER_ZSTACK, CLOUD_PROVIDER_GOOGLE, CLOUD_PROVIDER_CTYUN, + CLOUD_PROVIDER_ECLOUD, } CLOUD_PROVIDER_HOST_TYPE_MAP = map[string][]string{ @@ -152,6 +155,9 @@ var ( CLOUD_PROVIDER_CTYUN: []string{ HOST_TYPE_CTYUN, }, + CLOUD_PROVIDER_ECLOUD: { + HOST_TYPE_ECLOUD, + }, } ) diff --git a/pkg/apis/compute/cloudregions_const.go b/pkg/apis/compute/cloudregions_const.go index 276f44ad65..db10a82304 100644 --- a/pkg/apis/compute/cloudregions_const.go +++ b/pkg/apis/compute/cloudregions_const.go @@ -21,43 +21,51 @@ const ( DEFAULT_REGION_ID = "default" // 中国 - CITY_QING_DAO = "Qingdao" //青岛 - CITY_BEI_JING = "Beijing" //北京 - CITY_ZHANG_JIA_KOU = "Zhangjiakou" //张家口 - CITY_HU_HE_HAO_TE = "Huhehaote" //呼和浩特 - CITY_HANG_ZHOU = "Hangzhou" //杭州 - CITY_SHANG_HAI = "Shanghai" //上海 - CITY_SHEN_ZHEN = "Shenzhen" //深圳 - CITY_HONG_KONG = "Hongkong" //香港 - CITY_NING_XIA = "Ningxia" //宁夏 - CITY_GUANG_ZHOU = "Guangzhou" //广州 - CITY_TAI_WAN = "Taiwan" //台湾 - CITY_GUI_YANG = "Guiyang" //贵阳 - CITY_TAIPEI = "Taipei" //台北市 - CITY_KAOHSIUNG = "Kaohsiung" //高雄市 - CITY_CHENG_DU = "Chengdu" //成都 - CITY_HE_YUAN = "HeYuan" //河源 - CITY_CHONG_QING = "Chongqing" //重庆 - CITY_LAN_ZHOU = "Lanzhou" //兰州 - CITY_TAI_YUAN = "Taiyuan" //太原 - CITY_TIAN_JIN = "Tianjin" //天津 - CITY_WU_LU_MU_QI = "Wulumuqi" //乌鲁木齐 - CITY_NAN_NING = "Nanning" //南宁 - CITY_ZHENG_ZHOU = "Zhengzhou" //郑州 - CITY_KUN_MING = "Kunming" //昆明 - CITY_XI_AN = "Xian" //西安 - CITY_HAI_KOU = "Haikou" //海口 - CITY_WU_HU = "Wuhu" //芜湖 - CITY_FU_ZHOU = "Fuzhou" //福州 - CITY_WU_HAN = "Wuhan" //武汉 - CITY_CHANG_SHA = "Changsha" //长沙 - CITY_SU_ZHOU = "Suzhou" //苏州 - CITY_BAO_DING = "Baoding" //保定 - CITY_NAN_JING = "Nanjing" //南京 - CITY_FO_SHAN = "Foshan" //佛山 - CITY_QUAN_ZHOU = "Quanzhou" //泉州 - CITY_NEI_MENG_GU = "Neimenggu" //内蒙古 - CITY_WU_LAN_CHA_BU = "Wulanchabu" //乌兰察布 + CITY_QING_DAO = "Qingdao" //青岛 + CITY_BEI_JING = "Beijing" //北京 + CITY_ZHANG_JIA_KOU = "Zhangjiakou" //张家口 + CITY_HU_HE_HAO_TE = "Huhehaote" //呼和浩特 + CITY_HANG_ZHOU = "Hangzhou" //杭州 + CITY_SHANG_HAI = "Shanghai" //上海 + CITY_SHEN_ZHEN = "Shenzhen" //深圳 + CITY_HONG_KONG = "Hongkong" //香港 + CITY_NING_XIA = "Ningxia" //宁夏 + CITY_GUANG_ZHOU = "Guangzhou" //广州 + CITY_TAI_WAN = "Taiwan" //台湾 + CITY_GUI_YANG = "Guiyang" //贵阳 + CITY_TAIPEI = "Taipei" //台北市 + CITY_KAOHSIUNG = "Kaohsiung" //高雄市 + CITY_CHENG_DU = "Chengdu" //成都 + CITY_HE_YUAN = "HeYuan" //河源 + CITY_CHONG_QING = "Chongqing" //重庆 + CITY_LAN_ZHOU = "Lanzhou" //兰州 + CITY_TAI_YUAN = "Taiyuan" //太原 + CITY_TIAN_JIN = "Tianjin" //天津 + CITY_WU_LU_MU_QI = "Wulumuqi" //乌鲁木齐 + CITY_NAN_NING = "Nanning" //南宁 + CITY_ZHENG_ZHOU = "Zhengzhou" //郑州 + CITY_KUN_MING = "Kunming" //昆明 + CITY_XI_AN = "Xian" //西安 + CITY_HAI_KOU = "Haikou" //海口 + CITY_WU_HU = "Wuhu" //芜湖 + CITY_FU_ZHOU = "Fuzhou" //福州 + CITY_WU_HAN = "Wuhan" //武汉 + CITY_CHANG_SHA = "Changsha" //长沙 + CITY_SU_ZHOU = "Suzhou" //苏州 + CITY_BAO_DING = "Baoding" //保定 + CITY_NAN_JING = "Nanjing" //南京 + CITY_FO_SHAN = "Foshan" //佛山 + CITY_QUAN_ZHOU = "Quanzhou" //泉州 + CITY_NEI_MENG_GU = "Neimenggu" //内蒙古 + CITY_WU_LAN_CHA_BU = "Wulanchabu" //乌兰察布 + CITY_JI_NAM = "JiNan" //济南 + CITY_CHANG_CHUN = "ChangChun" //长春 + CITY_XIANG_YANG = "XiangYang" //襄阳 + CITY_NAN_CHANG = "NanChang" //南昌 + CITY_SHEN_YANG = "ShenYang" //沈阳 + CITY_SHI_JIA_ZHUANG = "ShiJiaZhuang" //石家庄 + CITY_XIA_MEN = "XiaMen" //厦门 + CITY_HUAI_NAN = "HuaiNan" //淮南 // 日本 CITY_TOKYO = "Tokyo" //东京 diff --git a/pkg/apis/compute/disk_const.go b/pkg/apis/compute/disk_const.go index 27f3a5ca54..12b5d5ee27 100644 --- a/pkg/apis/compute/disk_const.go +++ b/pkg/apis/compute/disk_const.go @@ -35,8 +35,9 @@ const ( DISK_START_SAVE = "start_save" DISK_SAVING = "saving" - DISK_START_RESIZE = "start_resize" - DISK_RESIZING = "resizing" + DISK_START_RESIZE = "start_resize" + DISK_RESIZING = "resizing" + DISK_RESIZE_FAILED = "resize_failed" DISK_START_MIGRATE = "start_migrate" DISK_POST_MIGRATE = "post_migrate" diff --git a/pkg/apis/compute/guest_const.go b/pkg/apis/compute/guest_const.go index 4324121a8c..d8fc82671f 100644 --- a/pkg/apis/compute/guest_const.go +++ b/pkg/apis/compute/guest_const.go @@ -164,6 +164,7 @@ const ( HYPERVISOR_ZSTACK = "zstack" HYPERVISOR_GOOGLE = "google" HYPERVISOR_CTYUN = "ctyun" + HYPERVISOR_ECLOUD = "ecloud" // HYPERVISOR_DEFAULT = HYPERVISOR_KVM HYPERVISOR_DEFAULT = HYPERVISOR_KVM @@ -198,6 +199,7 @@ var HYPERVISORS = []string{ HYPERVISOR_ZSTACK, HYPERVISOR_GOOGLE, HYPERVISOR_CTYUN, + HYPERVISOR_ECLOUD, } var ONECLOUD_HYPERVISORS = []string{ @@ -215,6 +217,7 @@ var PUBLIC_CLOUD_HYPERVISORS = []string{ HYPERVISOR_UCLOUD, HYPERVISOR_GOOGLE, HYPERVISOR_CTYUN, + HYPERVISOR_ECLOUD, } var PRIVATE_CLOUD_HYPERVISORS = []string{ @@ -241,6 +244,7 @@ var HYPERVISOR_HOSTTYPE = map[string]string{ HYPERVISOR_ZSTACK: HOST_TYPE_ZSTACK, HYPERVISOR_GOOGLE: HOST_TYPE_GOOGLE, HYPERVISOR_CTYUN: HOST_TYPE_CTYUN, + HYPERVISOR_ECLOUD: HOST_TYPE_ECLOUD, } var HOSTTYPE_HYPERVISOR = map[string]string{ @@ -259,6 +263,7 @@ var HOSTTYPE_HYPERVISOR = map[string]string{ HOST_TYPE_ZSTACK: HYPERVISOR_ZSTACK, HOST_TYPE_GOOGLE: HYPERVISOR_GOOGLE, HOST_TYPE_CTYUN: HYPERVISOR_CTYUN, + HOST_TYPE_ECLOUD: HYPERVISOR_ECLOUD, } const ( diff --git a/pkg/apis/compute/host_const.go b/pkg/apis/compute/host_const.go index e0a9637a1d..e0b5d2cf5e 100644 --- a/pkg/apis/compute/host_const.go +++ b/pkg/apis/compute/host_const.go @@ -34,6 +34,7 @@ const ( HOST_TYPE_ZSTACK = "zstack" HOST_TYPE_GOOGLE = "google" HOST_TYPE_CTYUN = "ctyun" + HOST_TYPE_ECLOUD = "ecloud" HOST_TYPE_DEFAULT = HOST_TYPE_HYPERVISOR diff --git a/pkg/apis/compute/storage_const.go b/pkg/apis/compute/storage_const.go index d7381409e2..7aaddacf0f 100644 --- a/pkg/apis/compute/storage_const.go +++ b/pkg/apis/compute/storage_const.go @@ -94,6 +94,12 @@ const ( STORAGE_CTYUN_SSD = "SSD" // 超高IO云硬盘 STORAGE_CTYUN_SAS = "SAS" // 高IO云硬盘 STORAGE_CTYUN_SATA = "SATA" // 普通IO云硬盘 + + STORAGE_ECLOUD_CAPEBS = "capebs" // 容量盘 + STORAGE_ECLOUD_EBS = "ebs" // 性能盘 + STORAGE_ECLOUD_SSD = "ssd" // 高性能盘 + STORAGE_ECLOUD_SSDEBS = "ssdebs" // 性能优化盘 + STORAGE_ECLOUD_SYSTEM = "system" // 系统盘 ) const ( diff --git a/pkg/compute/guestdrivers/ecloud.go b/pkg/compute/guestdrivers/ecloud.go new file mode 100644 index 0000000000..3233c07499 --- /dev/null +++ b/pkg/compute/guestdrivers/ecloud.go @@ -0,0 +1,104 @@ +package guestdrivers + +import ( + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/rbacutils" +) + +type SEcloudGuestDriver struct { + SManagedVirtualizedGuestDriver +} + +func (self *SEcloudGuestDriver) GetHypervisor() string { + return api.HYPERVISOR_ECLOUD +} + +func (self *SEcloudGuestDriver) GetProvider() string { + return api.CLOUD_PROVIDER_ECLOUD +} + +func (self *SEcloudGuestDriver) GetInstanceCapability() cloudprovider.SInstanceCapability { + return cloudprovider.SInstanceCapability{ + Hypervisor: self.GetHypervisor(), + Provider: self.GetProvider(), + DefaultAccount: cloudprovider.SDefaultAccount{ + Linux: cloudprovider.SOsDefaultAccount{ + DefaultAccount: api.VM_DEFAULT_LINUX_LOGIN_USER, + Changeable: false, + }, + Windows: cloudprovider.SOsDefaultAccount{ + DefaultAccount: api.VM_DEFAULT_WINDOWS_LOGIN_USER, + Changeable: false, + }, + }, + } +} + +func (self *SEcloudGuestDriver) GetComputeQuotaKeys(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider, brand string) models.SComputeResourceKeys { + keys := models.SComputeResourceKeys{} + keys.SBaseProjectQuotaKeys = quotas.OwnerIdProjectQuotaKeys(scope, ownerId) + keys.CloudEnv = api.CLOUD_ENV_PUBLIC_CLOUD + keys.Provider = api.CLOUD_PROVIDER_ECLOUD + keys.Brand = api.CLOUD_PROVIDER_ECLOUD + keys.Hypervisor = api.HYPERVISOR_ECLOUD + return keys +} + +func (self *SEcloudGuestDriver) GetDefaultSysDiskBackend() string { + return api.STORAGE_ECLOUD_SYSTEM +} + +func (self *SEcloudGuestDriver) GetDeployStatus() ([]string, error) { + return []string{api.VM_READY, api.VM_RUNNING}, nil +} + +func (self *SEcloudGuestDriver) GetMinimalSysDiskSizeGb() int { + return 10 +} + +func (self *SEcloudGuestDriver) GetGuestInitialStateAfterCreate() string { + return api.VM_RUNNING +} + +func (self *SEcloudGuestDriver) GetDetachDiskStatus() ([]string, error) { + return []string{api.VM_READY, api.VM_RUNNING}, nil +} + +func (self *SEcloudGuestDriver) GetAttachDiskStatus() ([]string, error) { + return []string{api.VM_READY, api.VM_RUNNING}, nil +} + +func (self *SEcloudGuestDriver) GetChangeConfigStatus(guest *models.SGuest) ([]string, error) { + return []string{api.VM_READY}, nil +} + +func (self *SEcloudGuestDriver) GetRebuildRootStatus() ([]string, error) { + return []string{api.VM_READY, api.VM_RUNNING}, nil +} + +func (self *SEcloudGuestDriver) GetGuestInitialStateAfterRebuild() string { + return api.VM_RUNNING +} + +func (self *SEcloudGuestDriver) ValidateResizeDisk(guest *models.SGuest, disk *models.SDisk, storage *models.SStorage) error { + return errors.ErrNotImplemented +} + +func (self *SEcloudGuestDriver) GetLinuxDefaultAccount(desc cloudprovider.SManagedVMCreateConfig) string { + if desc.OsType == "Windows" { + return "Administrator" + } + + return "root" +} + +func init() { + driver := SEcloudGuestDriver{} + models.RegisterGuestDriver(&driver) +} diff --git a/pkg/compute/hostdrivers/ecloud.go b/pkg/compute/hostdrivers/ecloud.go new file mode 100644 index 0000000000..7bebe7e278 --- /dev/null +++ b/pkg/compute/hostdrivers/ecloud.go @@ -0,0 +1,37 @@ +// 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 hostdrivers + +import ( + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/compute/models" +) + +type SEcloudHostDriver struct { + SManagedVirtualizationHostDriver +} + +func init() { + driver := SEcloudHostDriver{} + models.RegisterHostDriver(&driver) +} + +func (d *SEcloudHostDriver) GetHostType() string { + return api.HOST_TYPE_ECLOUD +} + +func (d *SEcloudHostDriver) GetHypervisor() string { + return api.HYPERVISOR_ECLOUD +} diff --git a/pkg/compute/regiondrivers/ecloud.go b/pkg/compute/regiondrivers/ecloud.go new file mode 100644 index 0000000000..17d780bf4f --- /dev/null +++ b/pkg/compute/regiondrivers/ecloud.go @@ -0,0 +1,19 @@ +package regiondrivers + +import ( + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/compute/models" +) + +type SEcloudRegionDriver struct { + SManagedVirtualizationRegionDriver +} + +func init() { + driver := SEcloudRegionDriver{} + models.RegisterRegionDriver(&driver) +} + +func (self *SEcloudRegionDriver) GetProvider() string { + return api.CLOUD_PROVIDER_ECLOUD +} diff --git a/pkg/mcclient/options/cloudaccounts.go b/pkg/mcclient/options/cloudaccounts.go index 48713537bc..f84ea27b2f 100644 --- a/pkg/mcclient/options/cloudaccounts.go +++ b/pkg/mcclient/options/cloudaccounts.go @@ -337,6 +337,17 @@ func (opts *SCtyunCloudAccountCreateOptions) Params() (jsonutils.JSONObject, err return params, nil } +type SEcloudCloudAccountCreateOptions struct { + SCloudAccountCreateBaseOptions + SAccessKeyCredentialWithEnvironment +} + +func (opts *SEcloudCloudAccountCreateOptions) Params() (jsonutils.JSONObject, error) { + params := jsonutils.Marshal(opts) + params.(*jsonutils.JSONDict).Add(jsonutils.NewString("Ecloud"), "provider") + return params, nil +} + // update credential options type SCloudAccountIdOptions struct { diff --git a/pkg/multicloud/ecloud/client.go b/pkg/multicloud/ecloud/client.go new file mode 100644 index 0000000000..6a926e9d70 --- /dev/null +++ b/pkg/multicloud/ecloud/client.go @@ -0,0 +1,324 @@ +// 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 ecloud + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/hex" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "os" + "strings" + + "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/util/httputils" +) + +const ( + CLOUD_PROVIDER_ECLOUD = api.CLOUD_PROVIDER_ECLOUD + CLOUD_PROVIDER_ECLOUD_CN = "移动云" + CLOUD_PROVIDER_ECLOUD_EN = "Ecloud" + CLOUD_API_VERSION = "2016-12-05" + + ECLOUD_DEFAULT_REGION = "beijing-1" +) + +type SEcloudClientConfig struct { + cpcfg cloudprovider.ProviderConfig + signer ISigner + + debug bool +} + +func NewEcloudClientConfig(signer ISigner) *SEcloudClientConfig { + cfg := &SEcloudClientConfig{ + signer: signer, + } + return cfg +} + +func (cfg *SEcloudClientConfig) SetCloudproviderConfig(cpcfg cloudprovider.ProviderConfig) *SEcloudClientConfig { + cfg.cpcfg = cpcfg + return cfg +} + +func (cfg *SEcloudClientConfig) SetDebug(debug bool) *SEcloudClientConfig { + cfg.debug = debug + return cfg +} + +type SEcloudClient struct { + *SEcloudClientConfig + + httpClient *http.Client + iregions []cloudprovider.ICloudRegion +} + +func NewEcloudClient(cfg *SEcloudClientConfig) (*SEcloudClient, error) { + httpClient := cfg.cpcfg.AdaptiveTimeoutHttpClient() + return &SEcloudClient{ + SEcloudClientConfig: cfg, + httpClient: httpClient, + }, nil +} + +func (self *SEcloudClient) GetAccessEnv() string { + return api.CLOUD_ACCESS_ENV_ECLOUD_CHINA +} + +func (ec *SEcloudClient) fetchRegions() { + regions := make([]SRegion, 0, len(regionList)) + for id, name := range regionList { + region := SRegion{} + region.ID = id + region.Name = name + region.client = ec + regions = append(regions, region) + } + iregions := make([]cloudprovider.ICloudRegion, len(regions)) + for i := range iregions { + iregions[i] = ®ions[i] + } + ec.iregions = iregions + return +} + +func (ec *SEcloudClient) GetIRegions() []cloudprovider.ICloudRegion { + if ec.iregions == nil { + ec.fetchRegions() + } + return ec.iregions +} + +func (ec *SEcloudClient) GetIRegionById(id string) (cloudprovider.ICloudRegion, error) { + iregions := ec.GetIRegions() + for i := range iregions { + if iregions[i].GetGlobalId() == id { + return iregions[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (ec *SEcloudClient) GetRegionById(id string) (*SRegion, error) { + iregions := ec.GetIRegions() + for i := range iregions { + if iregions[i].GetId() == id { + return iregions[i].(*SRegion), nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (ec *SEcloudClient) GetCapabilities() []string { + caps := []string{ + cloudprovider.CLOUD_CAPABILITY_COMPUTE, + cloudprovider.CLOUD_CAPABILITY_NETWORK, + } + return caps +} + +func (ec *SEcloudClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) { + subAccount := cloudprovider.SSubAccount{} + subAccount.Name = ec.cpcfg.Name + subAccount.Account = ec.signer.GetAccessKeyId() + subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL + return []cloudprovider.SSubAccount{subAccount}, nil +} + +func (ec *SEcloudClient) GetAccountId() string { + return ec.signer.GetAccessKeyId() +} + +func (ec *SEcloudClient) GetCloudRegionExternalIdPrefix() string { + return CLOUD_PROVIDER_ECLOUD +} + +func (ec *SEcloudClient) completeSingParams(request IRequest) (err error) { + queryParams := request.GetQueryParams() + queryParams["AccessKey"] = ec.signer.GetAccessKeyId() + queryParams["Version"] = request.GetVersion() + queryParams["Timestamp"] = request.GetTimestamp() + queryParams["SignatureMethod"] = ec.signer.GetName() + queryParams["SignatureVersion"] = ec.signer.GetVersion() + queryParams["SignatureNonce"] = ec.signer.GetNonce() + return +} + +func (ec *SEcloudClient) buildStringToSign(request IRequest) string { + signParams := request.GetQueryParams() + queryString := getUrlFormedMap(signParams) + queryString = strings.Replace(queryString, "+", "%20", -1) + queryString = strings.Replace(queryString, "*", "%2A", -1) + queryString = strings.Replace(queryString, "%7E", "~", -1) + shaString := sha256.Sum256([]byte(queryString)) + summaryQuery := hex.EncodeToString(shaString[:]) + serverPath := strings.Replace(request.GetServerPath(), "/", "%2F", -1) + return fmt.Sprintf("%s\n%s\n%s", request.GetMethod(), serverPath, summaryQuery) +} + +func (ec *SEcloudClient) doGet(ctx context.Context, r IRequest, result interface{}) error { + r.SetMethod("GET") + data, err := ec.request(ctx, r) + if err != nil { + return err + } + return data.Unmarshal(result) +} + +func (ec *SEcloudClient) doList(ctx context.Context, r IRequest, result interface{}) error { + r.SetMethod("GET") + // TODO Paging query + data, err := ec.request(ctx, r) + if err != nil { + return err + } + var ( + datas *jsonutils.JSONArray + ok bool + ) + + if datas, ok = data.(*jsonutils.JSONArray); !ok { + if !data.Contains("content") { + return ErrMissKey{ + Key: "content", + Jo: data, + } + } + content, _ := data.Get("content") + datas, ok = content.(*jsonutils.JSONArray) + if !ok { + return fmt.Errorf("The return result should be an array, but:\n%s", content) + } + } + return datas.Unmarshal(result) +} + +func (ec *SEcloudClient) request(ctx context.Context, r IRequest) (jsonutils.JSONObject, error) { + // sign + ec.completeSingParams(r) + signature := ec.signer.Sign(ec.buildStringToSign(r), "BC_SIGNATURE&") + query := r.GetQueryParams() + query["Signature"] = signature + header := r.GetHeaders() + header["Content-Type"] = "application/json" + var urlStr string + port := r.GetPort() + if len(port) > 0 { + urlStr = fmt.Sprintf("%s://%s:%s%s", r.GetScheme(), r.GetEndpoint(), port, r.GetServerPath()) + } else { + urlStr = fmt.Sprintf("%s://%s%s", r.GetScheme(), r.GetEndpoint(), r.GetServerPath()) + } + queryString := getUrlFromedMapUnescaped(r.GetQueryParams()) + if len(queryString) > 0 { + urlStr = urlStr + "?" + queryString + } + resp, err := httputils.Request( + ec.httpClient, + ctx, + httputils.THttpMethod(r.GetMethod()), + urlStr, + convertHeader(header), + r.GetBodyReader(), + ec.debug, + ) + defer httputils.CloseResponse(resp) + if err != nil { + return nil, err + } + rbody, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, errors.Wrap(err, "unable to read body of response") + } + if ec.debug { + fmt.Fprintf(os.Stderr, "Response body: %s\n", string(rbody)) + } + rbody = bytes.TrimSpace(rbody) + + var jrbody jsonutils.JSONObject + if len(rbody) > 0 && (rbody[0] == '{' || rbody[0] == '[') { + var err error + jrbody, err = jsonutils.Parse(rbody) + if err != nil { + return nil, errors.Wrapf(err, "unable to parsing json: %s", rbody) + } + } + if !jrbody.Contains("state") { + return nil, ErrMissKey{ + Key: "state", + Jo: jrbody, + } + } + state, _ := jrbody.GetString("state") + switch state { + case "OK": + if !jrbody.Contains("body") { + return nil, ErrMissKey{ + Key: "body", + Jo: jrbody, + } + } + body, _ := jrbody.Get("body") + return body, nil + default: + if jrbody.Contains("errorMessage") { + msg, _ := jrbody.GetString("errorMessage") + return nil, &httputils.JSONClientError{Code: 400, Details: msg} + } + return nil, &httputils.JSONClientError{Code: 400, Details: jrbody.String()} + } +} + +type ErrMissKey struct { + Key string + Jo jsonutils.JSONObject +} + +func (mk ErrMissKey) Error() string { + return fmt.Sprintf("The response body should contain the %q key, but it doesn't. It is:\n%s", mk.Key, mk.Jo) +} + +func convertHeader(mh map[string]string) http.Header { + header := http.Header{} + for k, v := range mh { + header.Add(k, v) + } + return header +} + +func getUrlFromedMapUnescaped(source map[string]string) string { + kvs := make([]string, 0, len(source)) + for k, v := range source { + kvs = append(kvs, fmt.Sprintf("%s=%s", k, v)) + } + return strings.Join(kvs, "&") +} + +func getUrlFormedMap(source map[string]string) (urlEncoded string) { + urlEncoder := url.Values{} + for key, value := range source { + urlEncoder.Add(key, value) + } + urlEncoded = urlEncoder.Encode() + return +} diff --git a/pkg/multicloud/ecloud/client_test.go b/pkg/multicloud/ecloud/client_test.go new file mode 100644 index 0000000000..78494bdae1 --- /dev/null +++ b/pkg/multicloud/ecloud/client_test.go @@ -0,0 +1,57 @@ +// 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 ecloud + +import ( + "testing" +) + +type signerMock struct { + SRamRoleSigner +} + +func (s *signerMock) GetNonce() string { + return "9d81ffbeaaf7477390db5df577bb3299" +} + +type requestMock struct { + SBaseRequest +} + +func (r *requestMock) GetTimestamp() string { + return "2017-01-11T15:15:11Z" +} + +func TestBuildStringToSing(t *testing.T) { + signer := &signerMock{SRamRoleSigner: *NewRamRoleSigner("testid", "testsecret")} + client, _ := NewEcloudClient(NewEcloudClientConfig(signer)) + request := &requestMock{ + SBaseRequest: SBaseRequest{ + Method: "GET", + ServerPath: "/api/keypair", + QueryParams: map[string]string{}, + Headers: map[string]string{}, + Content: []byte{}, + }, + } + client.completeSingParams(request) + stringToSign := client.buildStringToSign(request) + want := `GET +%2Fapi%2Fkeypair +25fb697a06bc8a16a0a2549460f5cba35aded3818e15f088e4e17cd394aa07af` + if stringToSign != want { + t.Fatalf("want:\n%s, get:\n%s", want, stringToSign) + } +} diff --git a/pkg/multicloud/ecloud/create_time.go b/pkg/multicloud/ecloud/create_time.go new file mode 100644 index 0000000000..9db1006ee7 --- /dev/null +++ b/pkg/multicloud/ecloud/create_time.go @@ -0,0 +1,26 @@ +// 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 ecloud + +import "time" + +type SCreateTime struct { + CreatedTime string +} + +func (c *SCreateTime) GetCreateAt() time.Time { + t, _ := time.Parse("2006-01-02 15:04:05", c.CreatedTime) + return t +} diff --git a/pkg/multicloud/ecloud/disk.go b/pkg/multicloud/ecloud/disk.go new file mode 100644 index 0000000000..d6e2fb0688 --- /dev/null +++ b/pkg/multicloud/ecloud/disk.go @@ -0,0 +1,247 @@ +// 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 ecloud + +import ( + "context" + "fmt" + "time" + + billing_api "yunion.io/x/onecloud/pkg/apis/billing" + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SDisk struct { + storage *SStorage + // TODO instance + + multicloud.SDisk + multicloud.SBillingBase + SZoneRegionBase + SCreateTime + + ManualAttr SDiskManualAttr + + // 硬盘可挂载主机类型 + AttachServerTypes []string + AvailabilityZone string + BackupId string + Description string + ID string + IsDelete bool + IsShare bool + // 磁盘所在集群的ID + Metadata string + Name string + OperationFlag string + // 硬盘挂在主机ID列表 + ServerId []string + SizeGB int `json:"size"` + SourceVolumeId string + Status string + Type string + VolumeType string + Iscsi bool + ProductType string +} + +type SDiskManualAttr struct { + IsVirtual bool + TempalteId string +} + +func (d *SDisk) GetBillingType() string { + return billing_api.BILLING_TYPE_POSTPAID +} + +func (d *SDisk) GetExpiredAt() time.Time { + return time.Time{} +} + +func (d *SDisk) GetId() string { + return d.ID +} + +func (d *SDisk) GetName() string { + return d.Name +} + +func (d *SDisk) GetGlobalId() string { + return d.ID +} + +func (d *SDisk) GetStatus() string { + if d.IsDelete { + // TODO + return "" + } + switch d.Status { + case "available", "in-use": + return api.DISK_READY + case "attaching": + return api.DISK_ATTACHING + case "backing_up": + return api.DISK_BACKUP_STARTALLOC + case "creating", "downloading": + return api.DISK_ALLOCATING + case "deleting": + return api.DISK_DEALLOC + case "uploading": + return api.DISK_SAVING + case "error": + return api.DISK_ALLOC_FAILED + case "error_deleting": + return api.DISK_DEALLOC_FAILED + case "restoring_backup": + return api.DISK_REBUILD + case "detaching": + return api.DISK_DETACHING + case "extending": + return api.DISK_RESIZING + case "error_extending": + return api.DISK_RESIZE_FAILED + case "error_restoring", "unrecognized": + return api.DISK_UNKNOWN + default: + return api.DISK_UNKNOWN + } +} + +func (d *SDisk) Refresh() error { + return nil +} + +func (d *SDisk) IsEmulated() bool { + return false +} + +func (s *SDisk) GetSysTags() map[string]string { + data := map[string]string{} + data["hypervisor"] = api.HYPERVISOR_ECLOUD + return data +} + +func (s *SDisk) GetProjectId() string { + return "" +} + +func (s *SDisk) GetIStorage() (cloudprovider.ICloudStorage, error) { + return s.storage, nil +} + +func (s *SDisk) GetDiskFormat() string { + return "vhd" +} + +func (s *SDisk) GetDiskSizeMB() int { + return s.SizeGB * 1024 +} + +func (s *SDisk) GetIsAutoDelete() bool { + if s.GetDiskType() == api.DISK_TYPE_SYS { + return true + } + return false +} + +func (s *SDisk) GetTemplateId() string { + return s.ManualAttr.TempalteId +} + +func (s *SDisk) GetDiskType() string { + if s.ManualAttr.IsVirtual { + return api.DISK_TYPE_SYS + } + return api.DISK_TYPE_DATA +} + +func (s *SDisk) GetFsFormat() string { + return "" +} + +func (s *SDisk) GetIsNonPersistent() bool { + return false +} + +func (s *SDisk) GetDriver() string { + return "scsi" +} + +func (s *SDisk) GetCacheMode() string { + return "none" +} + +func (s *SDisk) GetMountpoint() string { + return "" +} + +func (s *SDisk) GetAccessPath() string { + return "" +} + +func (s *SDisk) Delete(ctx context.Context) error { + return cloudprovider.ErrNotImplemented +} + +func (s *SDisk) CreateISnapshot(ctx context.Context, name string, desc string) (cloudprovider.ICloudSnapshot, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (s *SDisk) GetISnapshot(id string) (cloudprovider.ICloudSnapshot, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (s *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (s *SDisk) GetExtSnapshotPolicyIds() ([]string, error) { + return []string{}, cloudprovider.ErrNotImplemented +} + +func (s *SDisk) Resize(ctx context.Context, newSizeMB int64) error { + return cloudprovider.ErrNotImplemented +} + +func (s *SDisk) Reset(ctx context.Context, snapshotId string) (string, error) { + return "", cloudprovider.ErrNotImplemented +} + +func (s *SDisk) Rebuild(ctx context.Context) error { + return cloudprovider.ErrNotImplemented +} + +func (s *SRegion) GetDisks() ([]SDisk, error) { + request := NewNovaRequest(NewApiRequest(s.ID, "/api/v2/volume/volume/volume/list/with/server", nil, nil)) + disks := make([]SDisk, 0, 5) + err := s.client.doList(context.Background(), request, &disks) + if err != nil { + return nil, err + } + return disks, nil +} + +func (s *SRegion) GetDisk(id string) (*SDisk, error) { + // TODO + request := NewNovaRequest(NewApiRequest(s.ID, fmt.Sprintf("/api/v2/volume/volume/volumeDetail/%s", id), nil, nil)) + var disk SDisk + err := s.client.doGet(context.Background(), request, &disk) + if err != nil { + return nil, err + } + return &disk, nil +} diff --git a/pkg/multicloud/ecloud/doc.go b/pkg/multicloud/ecloud/doc.go new file mode 100644 index 0000000000..90f7091b10 --- /dev/null +++ b/pkg/multicloud/ecloud/doc.go @@ -0,0 +1,15 @@ +// 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 ecloud // import "yunion.io/x/onecloud/pkg/multicloud/ecloud" diff --git a/pkg/multicloud/ecloud/eip.go b/pkg/multicloud/ecloud/eip.go new file mode 100644 index 0000000000..9a57131709 --- /dev/null +++ b/pkg/multicloud/ecloud/eip.go @@ -0,0 +1,170 @@ +// 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 ecloud + +import ( + "context" + "fmt" + "time" + + billing_api "yunion.io/x/onecloud/pkg/apis/billing" + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SEip struct { + region *SRegion + multicloud.SEipBase + + BandWidthMbSize int + BandWidthType string + BindType string + // 使用,未使用 + Bound bool + // bandwidthCharge, trafficCharge + ChargeModeEnum string + CreateTime time.Time + Frozen bool + // 备案状态 + IcpStatus string + Id string + IpType string + Ipv6 string + Name string //公网IPv4地址 + NicName string + PortNetworkId string + Region string + ResourceId string + RouterId string + // BINDING, UNBOUND, FROZEN + Status string +} + +func (e *SEip) GetId() string { + return e.Id +} + +func (e *SEip) GetName() string { + return e.Name +} + +func (e *SEip) GetGlobalId() string { + return e.Id +} + +func (e *SEip) GetStatus() string { + switch e.Status { + case "BINDING", "UNBOUND": + return api.EIP_STATUS_READY + default: + return api.EIP_STATUS_UNKNOWN + } +} + +func (e *SEip) Refresh() error { + return cloudprovider.ErrNotImplemented +} + +func (e *SEip) IsEmulated() bool { + return false +} + +func (e *SEip) GetIpAddr() string { + return e.Name +} + +func (e *SEip) GetMode() string { + return api.EIP_MODE_STANDALONE_EIP +} + +func (e *SEip) GetAssociationType() string { + switch e.BindType { + case "vm": + return api.EIP_ASSOCIATE_TYPE_SERVER + case "snat", "dnat": + return api.EIP_ASSOCIATE_TYPE_NAT_GATEWAY + case "elb": + return api.EIP_ASSOCIATE_TYPE_LOADBALANCER + default: + return "unsupported" + } +} + +func (e *SEip) GetAssociationExternalId() string { + return e.ResourceId +} + +func (e *SEip) GetBillingType() string { + return billing_api.BILLING_TYPE_POSTPAID +} + +func (e *SEip) GetCreatedAt() time.Time { + return e.CreateTime +} + +func (e *SEip) GetExpiredAt() time.Time { + return time.Time{} +} + +func (e *SEip) Delete() error { + return cloudprovider.ErrNotImplemented +} + +func (e *SEip) GetBandwidth() int { + return e.BandWidthMbSize +} + +func (e *SEip) GetINetworkId() string { + return e.PortNetworkId +} + +func (e *SEip) GetInternetChargeType() string { + switch e.ChargeModeEnum { + // bandwidthCharge, trafficCharge + case "trafficCharge": + return api.EIP_CHARGE_TYPE_BY_TRAFFIC + case "bandwidthCharge": + return api.EIP_CHARGE_TYPE_BY_BANDWIDTH + default: + return "unknown" + } +} + +func (e *SEip) Associate(conf *cloudprovider.AssociateConfig) error { + return cloudprovider.ErrNotImplemented +} + +func (e *SEip) Dissociate() error { + return cloudprovider.ErrNotImplemented +} + +func (e *SEip) ChangeBandwidth(bw int) error { + return cloudprovider.ErrNotImplemented +} + +func (e *SEip) GetProjectId() string { + return "" +} + +func (r *SRegion) GetEipById(id string) (*SEip, error) { + var eip SEip + request := NewConsoleRequest(r.ID, fmt.Sprintf("/api/v2/floatingIp/getRespWithBw/%s", id), nil, nil) + err := r.client.doGet(context.Background(), request, &eip) + if err != nil { + return nil, err + } + return &eip, nil +} diff --git a/pkg/multicloud/ecloud/host.go b/pkg/multicloud/ecloud/host.go new file mode 100644 index 0000000000..b58f2f82b2 --- /dev/null +++ b/pkg/multicloud/ecloud/host.go @@ -0,0 +1,172 @@ +// 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 ecloud + +import ( + "fmt" + + "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 SHost struct { + multicloud.SHostBase + zone *SZone +} + +func (h *SHost) GetId() string { + return fmt.Sprintf("%s-%s", h.zone.region.client.cpcfg.Id, h.zone.GetId()) +} + +func (h *SHost) GetName() string { + return fmt.Sprintf("%s-%s", h.zone.region.client.cpcfg.Name, h.zone.GetId()) +} + +func (h *SHost) GetGlobalId() string { + return h.GetId() +} + +func (h *SHost) GetStatus() string { + return api.HOST_STATUS_RUNNING +} + +func (h *SHost) Refresh() error { + return nil +} + +func (h *SHost) IsEmulated() bool { + return true +} + +func (h *SHost) GetIVMs() ([]cloudprovider.ICloudVM, error) { + zoneRegion := h.zone.Region + vms, err := h.zone.region.GetInstances(zoneRegion) + if err != nil { + return nil, errors.Wrap(err, "SHost.GetVMs") + } + ivms := make([]cloudprovider.ICloudVM, len(vms)) + for i := range vms { + vms[i].host = h + ivms[i] = &vms[i] + } + return ivms, nil +} + +func (h *SHost) GetIVMById(id string) (cloudprovider.ICloudVM, error) { + vm, err := h.zone.region.GetInstanceById(id) + if err != nil { + return nil, err + } + vm.host = h + return vm, nil +} + +func (h *SHost) GetIWires() ([]cloudprovider.ICloudWire, error) { + return nil, nil +} + +func (h *SHost) GetIStorages() ([]cloudprovider.ICloudStorage, error) { + return h.zone.GetIStorages() +} + +func (h *SHost) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) { + return h.zone.GetIStorageById(id) +} + +func (h *SHost) GetEnabled() bool { + return true +} + +func (h *SHost) GetHostStatus() string { + return api.HOST_ONLINE +} + +func (h *SHost) GetAccessIp() string { + return "" +} + +func (h *SHost) GetAccessMac() string { + return "" +} + +func (h *SHost) GetSysInfo() jsonutils.JSONObject { + info := jsonutils.NewDict() + info.Add(jsonutils.NewString(api.CLOUD_PROVIDER_ECLOUD), "manufacture") + return info +} + +func (h *SHost) GetSN() string { + return "" +} + +func (h *SHost) GetCpuCount() int { + return 0 +} + +func (h *SHost) GetNodeCount() int8 { + return 0 +} + +func (h *SHost) GetCpuDesc() string { + return "" +} + +func (h *SHost) GetCpuMhz() int { + return 0 +} + +func (h *SHost) GetMemSizeMB() int { + return 0 +} + +func (h *SHost) GetStorageSizeMB() int { + return 0 +} + +func (h *SHost) GetStorageType() string { + return api.DISK_TYPE_HYBRID +} + +func (h *SHost) GetHostType() string { + return api.HOST_TYPE_ECLOUD +} + +func (h *SHost) GetIsMaintenance() bool { + return false +} + +func (h *SHost) GetVersion() string { + return CLOUD_API_VERSION +} + +func (h *SHost) CreateVM(desc *cloudprovider.SManagedVMCreateConfig) (cloudprovider.ICloudVM, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (h *SHost) GetIHostNics() ([]cloudprovider.ICloudHostNetInterface, error) { + return nil, cloudprovider.ErrNotSupported +} + +func (h *SRegion) GetVMs() ([]SInstance, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (h *SRegion) GetVMById(vmId string) (*SInstance, error) { + return nil, cloudprovider.ErrNotImplemented +} diff --git a/pkg/multicloud/ecloud/image.go b/pkg/multicloud/ecloud/image.go new file mode 100644 index 0000000000..397597d150 --- /dev/null +++ b/pkg/multicloud/ecloud/image.go @@ -0,0 +1,194 @@ +// 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 ecloud + +import ( + "context" + "fmt" + "time" + + "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" + "yunion.io/x/onecloud/pkg/util/imagetools" +) + +type SImage struct { + multicloud.SImageBase + SZoneRegionBase + + storageCache *SStoragecache + imgInfo *imagetools.ImageInfo + + ImageId string + ServerId string + ImageAlias string + Name string + Url string + SrourceImageId string + Status string + SizeMb int `json:"size"` + IsPublic int + CreateTime time.Time + Note string + OsType string + MinDiskGB int `json:"minDisk"` + ImageType string + PublicImageType string + BackupType string + BackupWay string + SnapshotId string + OsName string +} + +func (r *SRegion) GetImage(imageId string) (*SImage, error) { + request := NewNovaRequest(NewApiRequest(r.ID, fmt.Sprintf("/api/v2/image/%s", imageId), nil, nil)) + var image SImage + err := r.client.doGet(context.Background(), request, &image) + if err != nil { + return nil, err + } + return &image, nil +} + +func (r *SRegion) GetImages(isPublic bool) ([]SImage, error) { + if isPublic { + return nil, cloudprovider.ErrNotImplemented + } + request := NewNovaRequest(NewApiRequest(r.ID, "/api/v2/image", nil, nil)) + images := make([]SImage, 0, 5) + err := r.client.doList(context.Background(), request, &images) + if err != nil { + return nil, err + } + return images, nil +} + +func (i *SImage) GetCreatedAt() time.Time { + return i.CreateTime +} + +func (i *SImage) GetId() string { + return i.ImageId +} + +func (i *SImage) GetName() string { + return i.Name +} + +func (i *SImage) GetGlobalId() string { + return i.ImageId +} + +func (i *SImage) GetStatus() string { + switch i.Status { + case "active": + return api.CACHED_IMAGE_STATUS_ACTIVE + case "queued": + return api.CACHED_IMAGE_STATUS_INIT + case "saving": + return api.CACHED_IMAGE_STATUS_SAVING + case "caching": + return api.CACHED_IMAGE_STATUS_CACHING + case "pending_delete": + return api.CACHED_IMAGE_STATUS_DELETING + default: + return api.CACHED_IMAGE_STATUS_UNKNOWN + } +} + +func (i *SImage) Refresh() error { + new, err := i.storageCache.region.GetImage(i.GetId()) + if err != nil { + return err + } + return jsonutils.Update(i, new) +} + +func (i *SImage) IsEmulated() bool { + return false +} + +func (i *SImage) GetSysTags() map[string]string { + data := map[string]string{} + + if osType := i.GetOsType(); len(osType) > 0 { + data["os_type"] = osType + } + if len(i.OsName) > 0 { + data["os_name"] = i.OsName + } + if osDis := i.GetOsDist(); len(osDis) > 0 { + data["os_distribution"] = osDis + } + return data +} + +func (i *SImage) GetOsType() string { + return i.OsType +} + +func (i *SImage) GetOsDist() string { + return i.OsName +} + +func (i *SImage) GetOsVersion() string { + return "" +} + +func (i *SImage) GetOsArch() string { + return "" +} + +func (i *SImage) GetMinOsDiskSizeGb() int { + return i.MinDiskGB +} + +func (i *SImage) GetMinRamSizeMb() int { + return 0 +} + +func (i *SImage) GetImageFormat() string { + return "" +} + +func (self *SImage) Delete(ctx context.Context) error { + return cloudprovider.ErrNotSupported +} + +func (self *SImage) GetIStoragecache() cloudprovider.ICloudStoragecache { + return nil +} + +func (self *SImage) GetSizeByte() int64 { + return int64(self.SizeMb) * 1024 * 1024 +} + +func (self *SImage) GetImageType() cloudprovider.TImageType { + if self.IsPublic == 1 { + return cloudprovider.ImageTypeSystem + } + return cloudprovider.ImageTypeCustomized +} + +func (self *SImage) GetImageStatus() string { + return cloudprovider.IMAGE_STATUS_ACTIVE +} + +func (i *SImage) UEFI() bool { + return false +} diff --git a/pkg/multicloud/ecloud/instance.go b/pkg/multicloud/ecloud/instance.go new file mode 100644 index 0000000000..c8ea95438a --- /dev/null +++ b/pkg/multicloud/ecloud/instance.go @@ -0,0 +1,508 @@ +// 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 ecloud + +import ( + "context" + "fmt" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/sets" + + billing_api "yunion.io/x/onecloud/pkg/apis/billing" + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" + "yunion.io/x/onecloud/pkg/util/billing" +) + +type SNovaRequest struct { + SApiRequest +} + +func NewNovaRequest(ar *SApiRequest) *SNovaRequest { + return &SNovaRequest{ + SApiRequest: *ar, + } +} + +func (nr *SNovaRequest) GetPort() string { + if nr.RegionId == "guangzhou-2" { + return "" + } + return nr.SApiRequest.GetPort() +} + +type SInstance struct { + multicloud.SInstanceBase + multicloud.SBillingBase + SZoneRegionBase + SCreateTime + + nicComplete bool + host *SHost + image *SImage + + sysDisk cloudprovider.ICloudDisk + dataDisks []cloudprovider.ICloudDisk + + Id string + Name string + Vcpu int + Vmemory int + KeyName string + ImageRef string + ImageName string + ImageOsType string + FlavorRef string + SystemDiskSizeGB int `json:"vdisk"` + SystemDiskId string + ServerType string + ServerVmType string + EcStatus string + BootVolumeType string + Deleted int + Visible bool + Region string + PortDetail []SInstanceNic +} + +func (i *SInstance) GetBillingType() string { + return billing_api.BILLING_TYPE_POSTPAID +} + +func (i *SInstance) GetExpiredAt() time.Time { + return time.Time{} +} + +func (i *SInstance) GetId() string { + return i.Id +} + +func (i *SInstance) GetName() string { + return i.Name +} + +func (i *SInstance) GetGlobalId() string { + return i.GetId() +} + +func (i *SInstance) GetStatus() string { + switch i.EcStatus { + case "active": + return api.VM_RUNNING + case "suspended", "paused": + return api.VM_SUSPEND + case "build", "rebuild", "resize", "verify_resize", "revert_resize", "password": + return api.VM_STARTING + case "reboot", "hard_reboot": + return api.VM_STOPPING + case "stopped", "shutoff": + return api.VM_READY + case "migrating": + return api.VM_MIGRATING + case "backuping": + return api.VM_BACKUP_CREATING + default: + return api.VM_UNKNOWN + } +} + +func (i *SInstance) Refresh() error { + // TODO + return nil +} + +func (i *SInstance) IsEmulated() bool { + return false +} + +func (self *SInstance) GetBootOrder() string { + return "dcn" +} + +func (self *SInstance) GetVga() string { + return "std" +} + +func (self *SInstance) GetVdi() string { + return "vnc" +} + +func (i *SInstance) GetImage() (*SImage, error) { + if i.image != nil { + return i.image, nil + } + image, err := i.host.zone.region.GetImage(i.ImageRef) + if err != nil { + return nil, err + } + i.image = image + return i.image, nil +} + +func (i *SInstance) GetOSType() string { + return i.ImageOsType +} + +func (i *SInstance) GetOSName() string { + image, err := i.GetImage() + if err != nil { + return "" + } + return image.OsName +} + +func (i *SInstance) GetBios() string { + return "BIOS" +} + +func (i *SInstance) GetMachine() string { + return "pc" +} + +func (i *SInstance) GetInstanceType() string { + return i.FlavorRef +} + +func (self *SInstance) GetSysTags() map[string]string { + data := map[string]string{} + // TODO + lowerOs := self.GetOSType() + priceKey := fmt.Sprintf("%s::%s::%s", self.host.zone.region.GetId(), self.GetInstanceType(), lowerOs) + data["price_key"] = priceKey + data["zone_ext_id"] = self.host.zone.GetGlobalId() + + image, _ := self.GetImage() + if image != nil { + meta := image.GetSysTags() + for k, v := range meta { + data[k] = v + } + } + + return data +} + +func (in *SInstance) GetProjectId() string { + return "" +} + +func (in *SInstance) GetIHost() cloudprovider.ICloudHost { + return in.host +} + +func (in *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) { + if in.sysDisk == nil { + in.fetchSysDisk() + } + if in.dataDisks == nil { + err := in.fetchDataDisks() + if err != nil { + return nil, err + } + } + return append([]cloudprovider.ICloudDisk{in.sysDisk}, in.dataDisks...), nil +} + +func (in *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) { + if !in.nicComplete { + err := in.makeNicComplete() + if err != nil { + return nil, errors.Wrap(err, "unable to make nics complete") + } + in.nicComplete = true + } + inics := make([]cloudprovider.ICloudNic, len(in.PortDetail)) + for i := range in.PortDetail { + in.PortDetail[i].instance = in + inics[i] = &in.PortDetail[i] + } + return inics, nil +} + +func (in *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) { + if !in.nicComplete { + err := in.makeNicComplete() + if err != nil { + return nil, errors.Wrap(err, "unable to make nics complete") + } + in.nicComplete = true + } + var eipId string + for i := range in.PortDetail { + if len(in.PortDetail[i].IpId) > 0 { + eipId = in.PortDetail[i].IpId + break + } + } + if len(eipId) == 0 { + return nil, nil + } + return in.host.zone.region.GetEipById(eipId) +} + +func (in *SInstance) GetSecurityGroupIds() ([]string, error) { + if !in.nicComplete { + err := in.makeNicComplete() + if err != nil { + return nil, errors.Wrap(err, "unable to make nics complete") + } + in.nicComplete = true + } + ret := sets.NewString() + for i := range in.PortDetail { + for _, group := range in.PortDetail[i].SecurityGroups { + ret.Insert(group.Id) + } + } + return ret.UnsortedList(), nil +} + +func (in *SInstance) GetVcpuCount() int { + return in.Vcpu +} + +func (in *SInstance) GetVmemSizeMB() int { + return in.Vmemory +} + +func (in *SInstance) AssignSecurityGroup(id string) error { + return cloudprovider.ErrNotImplemented +} + +func (in *SInstance) SetSecurityGroups(ids []string) error { + return cloudprovider.ErrNotImplemented +} + +func (in *SInstance) GetHypervisor() string { + return api.HYPERVISOR_ECLOUD +} + +func (in *SInstance) StartVM(ctx context.Context) error { + return cloudprovider.ErrNotImplemented +} + +func (self *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error { + return cloudprovider.ErrNotImplemented +} + +func (self *SInstance) DeleteVM(ctx context.Context) error { + return cloudprovider.ErrNotImplemented +} + +func (self *SInstance) UpdateVM(ctx context.Context, name string) error { + return cloudprovider.ErrNotSupported +} + +func (self *SInstance) UpdateUserData(userData string) error { + return cloudprovider.ErrNotSupported +} + +func (self *SInstance) RebuildRoot(ctx context.Context, config *cloudprovider.SManagedVMRebuildRootConfig) (string, error) { + return "", cloudprovider.ErrNotImplemented +} + +func (self *SInstance) DeployVM(ctx context.Context, name string, username string, password string, publicKey string, deleteKeypair bool, description string) error { + return cloudprovider.ErrNotImplemented +} + +func (in *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedVMChangeConfig) error { + return errors.ErrNotImplemented +} + +func (in *SInstance) GetVNCInfo() (jsonutils.JSONObject, error) { + url, err := in.host.zone.region.GetInstanceVNCUrl(in.GetId()) + if err != nil { + return nil, err + } + ret := jsonutils.NewDict() + ret.Add(jsonutils.NewString(url), "url") + ret.Add(jsonutils.NewString("ecloud"), "protocol") + ret.Add(jsonutils.NewString(in.GetId()), "instance_id") + return ret, nil +} + +func (in *SInstance) AttachDisk(ctx context.Context, diskId string) error { + return cloudprovider.ErrNotImplemented +} + +func (in *SInstance) DetachDisk(ctx context.Context, diskId string) error { + return cloudprovider.ErrNotImplemented +} + +func (self *SInstance) CreateDisk(ctx context.Context, sizeMb int, uuid string, driver string) error { + return cloudprovider.ErrNotImplemented +} + +func (self *SInstance) Renew(bc billing.SBillingCycle) error { + return cloudprovider.ErrNotImplemented +} + +func (self *SInstance) GetError() error { + return nil +} + +func (in *SInstance) fetchSysDisk() { + storage, _ := in.host.zone.getStorageByType(api.STORAGE_ECLOUD_SYSTEM) + disk := SDisk{ + storage: storage, + ManualAttr: SDiskManualAttr{ + IsVirtual: true, + TempalteId: in.ImageRef, + }, + SCreateTime: in.SCreateTime, + SZoneRegionBase: in.SZoneRegionBase, + ServerId: []string{in.Id}, + IsShare: false, + IsDelete: false, + SizeGB: in.SystemDiskSizeGB, + ID: in.SystemDiskId, + Name: fmt.Sprintf("%s-root", in.Name), + Status: "in-use", + Type: api.STORAGE_ECLOUD_SYSTEM, + } + in.sysDisk = &disk + return +} + +func (in *SInstance) fetchDataDisks() error { + request := NewNovaRequest(NewApiRequest(in.host.zone.region.ID, "/api/v2/volume/volume/mount/list", + map[string]string{"serverId": in.Id}, nil)) + disks := make([]SDisk, 0, 5) + err := in.host.zone.region.client.doList(context.Background(), request, &disks) + if err != nil { + return err + } + idisks := make([]cloudprovider.ICloudDisk, len(disks)) + for i := range idisks { + storageType := disks[i].Type + storage, err := in.host.zone.getStorageByType(storageType) + if err != nil { + return errors.Wrapf(err, "unable to fetch storage with stoageType %s", storageType) + } + disks[i].storage = storage + idisks[i] = &disks[i] + } + in.dataDisks = idisks + return nil +} + +func (in *SInstance) makeNicComplete() error { + routerIds := sets.NewString() + nics := make(map[string]*SInstanceNic, len(in.PortDetail)) + for i := range in.PortDetail { + nic := &in.PortDetail[i] + routerIds.Insert(nic.RouterId) + nics[nic.PortId] = nic + } + for _, routerId := range routerIds.UnsortedList() { + request := NewConsoleRequest(in.host.zone.region.ID, fmt.Sprintf("/api/vpc/%s/nic", routerId), + map[string]string{ + "resourceId": in.Id, + }, nil, + ) + completeNics := make([]SInstanceNic, 0, len(nics)/2) + err := in.host.zone.region.client.doList(context.Background(), request, &completeNics) + if err != nil { + return errors.Wrapf(err, "unable to get nics with instance %s in vpc %s", in.Id, routerId) + } + for i := range completeNics { + id := completeNics[i].Id + nic, ok := nics[id] + if !ok { + continue + } + nic.SInstanceNicDetail = completeNics[i].SInstanceNicDetail + } + } + return nil +} + +func (r *SRegion) findHost(zoneRegion string) (*SHost, error) { + zone, err := r.FindZone(zoneRegion) + if err != nil { + return nil, err + } + return &SHost{ + zone: zone, + }, nil +} + +func (r *SRegion) GetInstancesWithHost(zoneRegion string) ([]SInstance, error) { + instances, err := r.GetInstances(zoneRegion) + if err != nil { + return nil, err + } + for i := range instances { + host, _ := r.findHost(instances[i].Region) + instances[i].host = host + } + return instances, nil +} + +func (r *SRegion) GetInstances(zoneRegion string) ([]SInstance, error) { + return r.getInstances(zoneRegion, "") +} + +func (r *SRegion) getInstances(zoneRegion string, serverId string) ([]SInstance, error) { + query := map[string]string{ + "serverTypes": "VM", + "productTypes": "NORMAL,AUTOSCALING,VO,CDN,PAAS_MASTER,PAAS_SLAVE,VCPE,EMR,LOGAUDIT", + //"productTypes": "NORMAL", + "visible": "true", + } + if len(serverId) > 0 { + query["serverId"] = serverId + } + if len(zoneRegion) > 0 { + query["region"] = zoneRegion + } + request := NewNovaRequest(NewApiRequest(r.ID, "/api/v2/server/web/with/network", query, nil)) + var instances []SInstance + err := r.client.doList(context.Background(), request, &instances) + if err != nil { + return nil, err + } + return instances, nil +} + +func (r *SRegion) GetInstanceById(id string) (*SInstance, error) { + instances, err := r.getInstances("", id) + if err != nil { + return nil, err + } + if len(instances) == 0 { + return nil, cloudprovider.ErrNotFound + } + instance := &instances[0] + host, err := r.findHost(instance.Region) + if err == nil { + instance.host = host + } + return instance, nil +} + +func (r *SRegion) GetInstanceVNCUrl(instanceId string) (string, error) { + request := NewNovaRequest(NewApiRequest(r.ID, fmt.Sprintf("/api/server/%s/vnc", instanceId), nil, nil)) + var url string + err := r.client.doGet(context.Background(), request, &url) + if err != nil { + return "", err + } + return url, nil +} diff --git a/pkg/multicloud/ecloud/instancenic.go b/pkg/multicloud/ecloud/instancenic.go new file mode 100644 index 0000000000..ea8e4a2a87 --- /dev/null +++ b/pkg/multicloud/ecloud/instancenic.go @@ -0,0 +1,103 @@ +// 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 ecloud + +import ( + "time" + + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SInstanceNic struct { + cloudprovider.DummyICloudNic + SInstanceNicDetail + + instance *SInstance + + Id string + PrivateIp string + FipAddress string + FipBandwidthSize int + PortId string + PortName string + FixedIpDetails []SFixedIpDetail + RouterId string +} + +type SInstanceNicDetail struct { + MacAddress string + SecurityGroups []SSecurityGroupRef + Status int + ResourceId string + CreaetTime time.Time + PublicIp string + IpId string + NetworkId string +} + +type SSecurityGroupRef struct { + Id string + Name string +} + +type SFixedIpDetail struct { + IpAddress string + IpVersion string + PublicIp string + BandWidthSize int + BandWidthType string + SubnetId string + SubnetName string +} + +func (in *SInstanceNic) GetIP() string { + return in.PrivateIp +} + +func (in *SInstanceNic) GetMAC() string { + return in.MacAddress +} + +func (in *SInstanceNic) GetId() string { + return in.PortId +} + +func (in *SInstanceNic) GetDriver() string { + return "virtio" +} + +func (in *SInstanceNic) InClassicNetwork() bool { + return false +} + +func (in *SInstanceNic) GetINetwork() cloudprovider.ICloudNetwork { + net, err := in.instance.host.zone.region.GetNetworkById("", "", in.NetworkId) + if err != nil { + log.Errorf("unable to get Network %s", in.NetworkId) + return nil + } + vpc, err := in.instance.host.zone.region.getVpcByRouterId(in.RouterId) + if err != nil { + log.Errorf("unable to get Vpc by routerId %s", in.RouterId) + } + wire := &SWire{ + vpc: vpc, + zone: in.instance.host.zone, + } + net.wire = wire + return net +} diff --git a/pkg/multicloud/ecloud/latitud_and_longitude.go b/pkg/multicloud/ecloud/latitud_and_longitude.go new file mode 100644 index 0000000000..6797926571 --- /dev/null +++ b/pkg/multicloud/ecloud/latitud_and_longitude.go @@ -0,0 +1,51 @@ +// 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 ecloud + +import ( + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +var LatitudeAndLongitude = map[string]cloudprovider.SGeographicInfo{ + "guangzhou-2": {Latitude: 23.129110, Longitude: 113.264381, City: api.CITY_GUANG_ZHOU, CountryCode: api.COUNTRY_CODE_CN}, + "beijing-1": {Latitude: 39.904202, Longitude: 116.407394, City: api.CITY_BEI_JING, CountryCode: api.COUNTRY_CODE_CN}, + "hunan-1": {Latitude: 28.2277765095, Longitude: 112.9388453666, City: api.CITY_CHANG_SHA, CountryCode: api.COUNTRY_CODE_CN}, + "wuxi-1": {Latitude: 31.2983479333, Longitude: 120.5831894861, City: api.CITY_SU_ZHOU, CountryCode: api.COUNTRY_CODE_CN}, + "dongguan-1": {Latitude: 23.129110, Longitude: 113.264381, City: api.CITY_GUANG_ZHOU, CountryCode: api.COUNTRY_CODE_CN}, + "yaan-1": {Latitude: 30.572815, Longitude: 104.066803, City: api.CITY_CHENG_DU, CountryCode: api.COUNTRY_CODE_CN}, + "zhengzhou-1": {Latitude: 34.7533581487, Longitude: 113.6313915479, City: api.CITY_ZHENG_ZHOU, CountryCode: api.COUNTRY_CODE_CN}, + "beijing-2": {Latitude: 39.904202, Longitude: 116.407394, City: api.CITY_BEI_JING, CountryCode: api.COUNTRY_CODE_CN}, + "zhuzhou-1": {Latitude: 28.2277765095, Longitude: 112.9388453666, City: api.CITY_CHANG_SHA, CountryCode: api.COUNTRY_CODE_CN}, + "jinan-1": {Latitude: 36.64889911073425, Longitude: 117.11905617575435, City: api.CITY_JI_NAM, CountryCode: api.COUNTRY_CODE_CN}, + "xian-1": {Latitude: 34.3412614674, Longitude: 108.9398165260, City: api.CITY_XI_AN, CountryCode: api.COUNTRY_CODE_CN}, + "shanghai-1": {Latitude: 31.210344, Longitude: 121.455364, City: api.CITY_SHANG_HAI, CountryCode: api.COUNTRY_CODE_CN}, + "chongqing-1": {Latitude: 29.431585, Longitude: 106.912254, City: api.CITY_CHONG_QING, CountryCode: api.COUNTRY_CODE_CN}, + "ningbo-1": {Latitude: 30.274084, Longitude: 120.155067, City: api.CITY_HANG_ZHOU, CountryCode: api.COUNTRY_CODE_CN}, + "tianjin-1": {Latitude: 39.0850853357, Longitude: 117.1993482089, City: api.CITY_TIAN_JIN, CountryCode: api.COUNTRY_CODE_CN}, + "jilin-1": {Latitude: 43.87120919729674, Longitude: 125.3111129463539, City: api.CITY_CHANG_CHUN, CountryCode: api.COUNTRY_CODE_CN}, + "hubei-1": {Latitude: 32.009075721852206, Longitude: 112.13485327119795, City: api.CITY_XIANG_YANG, CountryCode: api.COUNTRY_CODE_CN}, + "jiangxi-1": {Latitude: 28.66278309381472, Longitude: 115.82816199879247, City: api.CITY_NAN_CHANG, CountryCode: api.COUNTRY_CODE_CN}, + "gansu-1": {Latitude: 36.0613769373, Longitude: 103.8341600069, City: api.CITY_LAN_ZHOU, CountryCode: api.COUNTRY_CODE_CN}, + "shanxi-1": {Latitude: 37.8705857132, Longitude: 112.5506634865, City: api.CITY_TAI_YUAN, CountryCode: api.COUNTRY_CODE_CN}, + "liaoning-1": {Latitude: 41.78937667917192, Longitude: 123.43099727316815, City: api.CITY_SHEN_YANG, CountryCode: api.COUNTRY_CODE_CN}, + "yunnan-2": {Latitude: 24.8796595146, Longitude: 102.8332118852, City: api.CITY_KUN_MING, CountryCode: api.COUNTRY_CODE_CN}, + "hebei-1": {Latitude: 38.044044256466684, Longitude: 114.50225031469532, City: api.CITY_SHI_JIA_ZHUANG, CountryCode: api.COUNTRY_CODE_CN}, + "fujian-1": {Latitude: 24.478556505708365, Longitude: 118.0875755539503, City: api.CITY_XIA_MEN, CountryCode: api.COUNTRY_CODE_CN}, + "guangxi-1": {Latitude: 22.8167372565, Longitude: 108.3669005333, City: api.CITY_NAN_NING, CountryCode: api.COUNTRY_CODE_CN}, + "anhui-1": {Latitude: 32.62657438299575, Longitude: 116.99779954519057, City: api.CITY_HUAI_NAN, CountryCode: api.COUNTRY_CODE_CN}, + "huhehaote-1": {Latitude: 40.842358, Longitude: 111.749992, City: api.CITY_HU_HE_HAO_TE, CountryCode: api.COUNTRY_CODE_CN}, + "guiyang-1": {Latitude: 26.6470035286, Longitude: 106.6302113880, City: api.CITY_GUI_YANG, CountryCode: api.COUNTRY_CODE_CN}, +} diff --git a/pkg/multicloud/ecloud/network.go b/pkg/multicloud/ecloud/network.go new file mode 100644 index 0000000000..1de05c3155 --- /dev/null +++ b/pkg/multicloud/ecloud/network.go @@ -0,0 +1,189 @@ +// 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 ecloud + +import ( + "context" + + "yunion.io/x/pkg/util/netutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" + "yunion.io/x/onecloud/pkg/util/rbacutils" +) + +type SNetwork struct { + multicloud.SResourceBase + SZoneRegionBase + + wire *SWire + + Id string + Name string + Shared bool + Enabled bool + EcStatus string + Subnets []SSubnet +} + +type SSubnet struct { + Id string + Name string + NetworkId string + Region string + GatewayIp string + EnableDHCP bool + Cidr string + IpVersion string +} + +func (n *SNetwork) GetId() string { + return n.Id +} + +func (n *SNetwork) GetName() string { + return n.Name +} + +func (n *SNetwork) GetGlobalId() string { + return n.Id +} + +func (n *SNetwork) GetStatus() string { + switch n.EcStatus { + case "ACTIVE": + return api.NETWORK_STATUS_AVAILABLE + case "DOWN", "BUILD", "ERROR": + return api.NETWORK_STATUS_UNAVAILABLE + case "PENDING_DELETE": + return api.NETWORK_STATUS_DELETING + case "PENDING_CREATE", "PENDING_UPDATE": + return api.NETWORK_STATUS_PENDING + default: + return api.NETWORK_STATUS_UNKNOWN + } +} + +func (n *SNetwork) Refresh() error { + return nil + // nn, err := n.wire.vpc.region.GetNetworkById(n.wire.vpc.RouterId, n.wire.zone.Region, n.GetId()) + // if err != nil { + // return err + // } + // return jsonutils.Update(n, nn) +} + +func (n *SNetwork) IsEmulated() bool { + return false +} + +func (n *SNetwork) GetProjectId() string { + return "" +} + +func (n *SNetwork) GetIWire() cloudprovider.ICloudWire { + return n.wire +} + +func (n *SNetwork) GetIpStart() string { + pref, _ := netutils.NewIPV4Prefix(n.Cidr()) + startIp := pref.Address.NetAddr(pref.MaskLen) // 0 + startIp = startIp.StepUp() // 1 + startIp = startIp.StepUp() // 2 + return startIp.String() +} + +func (n *SNetwork) GetIpEnd() string { + pref, _ := netutils.NewIPV4Prefix(n.Cidr()) + endIp := pref.Address.BroadcastAddr(pref.MaskLen) // 255 + endIp = endIp.StepDown() // 254 + return endIp.String() +} + +func (n *SNetwork) Cidr() string { + return n.Subnets[0].Cidr +} + +func (n *SNetwork) GetIpMask() int8 { + pref, _ := netutils.NewIPV4Prefix(n.Cidr()) + return pref.MaskLen +} + +func (n *SNetwork) GetGateway() string { + return n.Subnets[0].GatewayIp +} + +func (self *SNetwork) GetServerType() string { + return api.NETWORK_TYPE_GUEST +} + +func (self *SNetwork) GetIsPublic() bool { + return true +} + +func (self *SNetwork) GetPublicScope() rbacutils.TRbacScope { + return rbacutils.ScopeDomain +} + +func (self *SNetwork) Delete() error { + return cloudprovider.ErrNotImplemented +} + +func (self *SNetwork) GetAllocTimeoutSeconds() int { + return 120 +} + +func (r *SRegion) GetNetworks(routerId, zoneRegion string) ([]SNetwork, error) { + queryParams := map[string]string{ + "networkTypeEnum": "VM", + } + if len(routerId) > 0 { + queryParams["routerId"] = routerId + } + if len(zoneRegion) > 0 { + queryParams["region"] = zoneRegion + } + request := NewConsoleRequest(r.ID, "/api/v2/netcenter/network/NetworkResps", queryParams, nil) + networks := make([]SNetwork, 0) + err := r.client.doList(context.Background(), request, &networks) + if err != nil { + return nil, err + } + return networks, nil +} + +func (r *SRegion) GetNetworkById(routerId, zoneRegion, netId string) (*SNetwork, error) { + queryParams := map[string]string{ + "rangeInNetworkIds": netId, + "networkTypeEnum": "VM", + } + if len(routerId) > 0 { + queryParams["routerId"] = routerId + } + if len(zoneRegion) > 0 { + queryParams["region"] = zoneRegion + } + request := NewConsoleRequest(r.ID, "/api/v2/netcenter/network/NetworkResps", queryParams, nil) + networks := make([]SNetwork, 0, 1) + err := r.client.doList(context.Background(), request, &networks) + if err != nil { + return nil, err + } + if len(networks) == 0 { + return nil, cloudprovider.ErrNotFound + } + return &networks[0], nil +} diff --git a/pkg/multicloud/ecloud/provider/doc.go b/pkg/multicloud/ecloud/provider/doc.go new file mode 100644 index 0000000000..aa1a44ede1 --- /dev/null +++ b/pkg/multicloud/ecloud/provider/doc.go @@ -0,0 +1,15 @@ +// 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 provider // import "yunion.io/x/onecloud/pkg/multicloud/ecloud/provider" diff --git a/pkg/multicloud/ecloud/provider/provider.go b/pkg/multicloud/ecloud/provider/provider.go new file mode 100644 index 0000000000..3399fedcd3 --- /dev/null +++ b/pkg/multicloud/ecloud/provider/provider.go @@ -0,0 +1,181 @@ +// 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 provider + +import ( + "context" + "strings" + + "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/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/multicloud/ecloud" +) + +type SEcloudProviderFactory struct { + cloudprovider.SPublicCloudBaseProviderFactory +} + +func (f *SEcloudProviderFactory) GetId() string { + return ecloud.CLOUD_PROVIDER_ECLOUD +} + +func (f *SEcloudProviderFactory) GetName() string { + return ecloud.CLOUD_PROVIDER_ECLOUD_CN +} + +func (f *SEcloudProviderFactory) IsSupportPrepaidResources() bool { + return true +} + +func (f *SEcloudProviderFactory) ValidateCreateCloudaccountData(ctx context.Context, userCred mcclient.TokenCredential, input cloudprovider.SCloudaccountCredential) (cloudprovider.SCloudaccount, error) { + output := cloudprovider.SCloudaccount{} + if len(input.AccessKeyId) == 0 { + return output, errors.Wrap(httperrors.ErrMissingParameter, "access_key_id") + } + if len(input.AccessKeySecret) == 0 { + return output, errors.Wrap(httperrors.ErrMissingParameter, "access_key_secret") + } + if len(input.Environment) == 0 { + return output, errors.Wrap(httperrors.ErrMissingParameter, "environment") + } + output.Account = input.AccessKeyId + output.Secret = input.AccessKeySecret + output.AccessUrl = input.Environment + return output, nil +} + +func (f *SEcloudProviderFactory) ValidateUpdateCloudaccountCredential(ctx context.Context, userCred mcclient.TokenCredential, input cloudprovider.SCloudaccountCredential, cloudaccount string) (cloudprovider.SCloudaccount, error) { + output := cloudprovider.SCloudaccount{} + if len(input.AccessKeyId) == 0 { + return output, errors.Wrap(httperrors.ErrMissingParameter, "access_key_id") + } + if len(input.AccessKeySecret) == 0 { + return output, errors.Wrap(httperrors.ErrMissingParameter, "access_key_secret") + } + output = cloudprovider.SCloudaccount{ + Account: input.AccessKeyId, + Secret: input.AccessKeySecret, + } + return output, nil +} + +func (f *SEcloudProviderFactory) GetProvider(cfg cloudprovider.ProviderConfig) (cloudprovider.ICloudProvider, error) { + segs := strings.Split(cfg.Account, "/") + account := cfg.Account + if len(segs) == 2 { + account = segs[0] + } + + client, err := ecloud.NewEcloudClient( + ecloud.NewEcloudClientConfig( + ecloud.NewRamRoleSigner(account, cfg.Secret), + ).SetCloudproviderConfig(cfg), + ) + if err != nil { + return nil, err + } + return &SEcloudProvider{ + SBaseProvider: cloudprovider.NewBaseProvider(f), + client: client, + }, nil +} + +func (f *SEcloudProviderFactory) GetClientRC(info cloudprovider.SProviderInfo) (map[string]string, error) { + return map[string]string{ + "ECLOUD_ACCESS_URL": info.Url, + "ECLOUD_ACCESS_KEY": info.Account, + "ECLOUD_SECRET": info.Secret, + "ECLOUD_REGION": ecloud.ECLOUD_DEFAULT_REGION, + }, nil +} + +func init() { + factory := SEcloudProviderFactory{} + cloudprovider.RegisterFactory(&factory) +} + +type SEcloudProvider struct { + cloudprovider.SBaseProvider + client *ecloud.SEcloudClient +} + +func (p *SEcloudProvider) GetSubAccounts() ([]cloudprovider.SSubAccount, error) { + return p.client.GetSubAccounts() +} + +func (p *SEcloudProvider) GetAccountId() string { + return p.client.GetAccountId() +} + +func (p *SEcloudProvider) GetIRegions() []cloudprovider.ICloudRegion { + return p.client.GetIRegions() +} + +func (p *SEcloudProvider) GetSysInfo() (jsonutils.JSONObject, error) { + iregions := p.client.GetIRegions() + info := jsonutils.NewDict() + info.Add(jsonutils.NewInt(int64(len(iregions))), "region_count") + info.Add(jsonutils.NewString(ecloud.CLOUD_API_VERSION), "api_version") + return info, nil +} + +func (p *SEcloudProvider) GetVersion() string { + return ecloud.CLOUD_API_VERSION +} + +func (p *SEcloudProvider) GetIRegionById(id string) (cloudprovider.ICloudRegion, error) { + iregion, err := p.client.GetIRegionById(id) + if err != nil { + return nil, err + } + if iregion == nil { + return nil, cloudprovider.ErrNotFound + } + return iregion, nil +} + +func (p *SEcloudProvider) GetBalance() (float64, string, error) { + return 0.0, api.CLOUD_PROVIDER_HEALTH_NORMAL, cloudprovider.ErrNotSupported +} + +func (p *SEcloudProvider) GetIProjects() ([]cloudprovider.ICloudProject, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (p *SEcloudProvider) GetStorageClasses(regionId string) []string { + // TODO + return nil +} + +func (p *SEcloudProvider) GetBucketCannedAcls(regionId string) []string { + return nil +} + +func (p *SEcloudProvider) GetObjectCannedAcls(regionId string) []string { + return nil +} + +func (p *SEcloudProvider) GetCloudRegionExternalIdPrefix() string { + return p.client.GetCloudRegionExternalIdPrefix() +} + +func (p *SEcloudProvider) GetCapabilities() []string { + return p.client.GetCapabilities() +} diff --git a/pkg/multicloud/ecloud/region.go b/pkg/multicloud/ecloud/region.go new file mode 100644 index 0000000000..6cd57f0ab1 --- /dev/null +++ b/pkg/multicloud/ecloud/region.go @@ -0,0 +1,355 @@ +// 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 ecloud + +import ( + "context" + "fmt" + + "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" +) + +var regionList = map[string]string{ + "guangzhou-2": "华南-广州2", + "beijing-1": "华北-北京1", + "hunan-1": "华中-长沙1", + "wuxi-1": "华东-苏州", + "dongguan-1": "华南-广州3", + "yaan-1": "西南-成都", + "zhengzhou-1": "华中-郑州", + "beijing-2": "华北-北京3", + "zhuzhou-1": "华中-长沙2", + "jinan-1": "华东-济南", + "xian-1": "西北-西安", + "shanghai-1": "华东-上海1", + "chongqing-1": "西南-重庆", + "ningbo-1": "华东-杭州", + "tianjin-1": "天津-天津", + "jilin-1": "吉林-长春", + "hubei-1": "湖北-襄阳", + "jiangxi-1": "江西-南昌", + "gansu-1": "甘肃-兰州", + "shanxi-1": "山西-太原", + "liaoning-1": "辽宁-沈阳", + "yunnan-2": "云南-昆明2", + "hebei-1": "河北-石家庄", + "fujian-1": "福建-厦门", + "guangxi-1": "广西-南宁", + "anhui-1": "安徽-淮南", + "huhehaote-1": "华北-呼和浩特", + "guiyang-1": "西南-贵阳", +} + +type SRegion struct { + cloudprovider.SFakeOnPremiseRegion + multicloud.SRegion + multicloud.SNoObjectStorageRegion + + client *SEcloudClient + storageCache *SStoragecache + + ID string `json:"id"` + Name string `json:"Name"` + + izones []cloudprovider.ICloudZone + ivpcs []cloudprovider.ICloudVpc +} + +func (r *SRegion) GetId() string { + return r.ID +} + +func (r *SRegion) GetName() string { + return r.Name +} + +func (r *SRegion) GetGlobalId() string { + return fmt.Sprintf("%s/%s", r.client.GetAccessEnv(), r.ID) +} + +func (r *SRegion) GetStatus() string { + return api.CLOUD_REGION_STATUS_INSERVER +} + +func (r *SRegion) Refresh() error { + // err := r.fetchZones() + // if err != nil { + // return err + // } + // return r.fetchVpcs() + return nil +} + +func (r *SRegion) IsEmulated() bool { + return false +} + +func (r *SRegion) GetI18n() cloudprovider.SModelI18nTable { + en := fmt.Sprintf("%s %s", CLOUD_PROVIDER_ECLOUD_EN, r.Name) + table := cloudprovider.SModelI18nTable{} + table["name"] = cloudprovider.NewSModelI18nEntry(r.GetName()).CN(r.GetName()).EN(en) + return table +} + +// GetLatitude() float32 +// GetLongitude() float32 +func (r *SRegion) GetGeographicInfo() cloudprovider.SGeographicInfo { + if info, ok := LatitudeAndLongitude[r.ID]; ok { + return info + } + return cloudprovider.SGeographicInfo{} +} + +func (r *SRegion) GetIZones() ([]cloudprovider.ICloudZone, error) { + if r.izones == nil { + err := r.fetchZones() + if err != nil { + return nil, err + } + } + return r.izones, nil +} + +func (r *SRegion) fetchZones() error { + request := NewNovaRequest(NewApiRequest(r.ID, "/api/v2/region", + map[string]string{"component": "NOVA"}, nil)) + zones := make([]SZone, 0) + err := r.client.doList(context.Background(), request, &zones) + if err != nil { + return err + } + izones := make([]cloudprovider.ICloudZone, len(zones)) + for i := range zones { + zones[i].region = r + zones[i].host = &SHost{ + zone: &zones[i], + } + izones[i] = &zones[i] + } + r.izones = izones + return nil +} + +func (r *SRegion) fetchVpcs() error { + vpcs, err := r.getVpcs() + if err != nil { + return err + } + ivpcs := make([]cloudprovider.ICloudVpc, len(vpcs)) + for i := range vpcs { + ivpcs[i] = &vpcs[i] + } + r.ivpcs = ivpcs + return nil +} + +func (r *SRegion) getVpcs() ([]SVpc, error) { + request := NewConsoleRequest(r.ID, "/api/v2/netcenter/vpc", nil, nil) + vpcs := make([]SVpc, 0) + err := r.client.doList(context.Background(), request, &vpcs) + if err != nil { + return nil, err + } + for i := range vpcs { + vpcs[i].region = r + } + return vpcs, err +} + +func (r *SRegion) getVpcById(id string) (*SVpc, error) { + request := NewConsoleRequest(r.ID, fmt.Sprintf("/api/v2/netcenter/vpc/%s", id), nil, nil) + vpc := SVpc{} + err := r.client.doGet(context.Background(), request, &vpc) + if err != nil { + return nil, err + } + vpc.region = r + return &vpc, err +} + +func (r *SRegion) getVpcByRouterId(id string) (*SVpc, error) { + request := NewConsoleRequest(r.ID, fmt.Sprintf("/api/v2/netcenter/vpc/router/%s", id), nil, nil) + vpc := SVpc{} + err := r.client.doGet(context.Background(), request, &vpc) + if err != nil { + return nil, err + } + vpc.region = r + return &vpc, err +} + +func (r *SRegion) GetIVpcs() ([]cloudprovider.ICloudVpc, error) { + if r.ivpcs == nil { + err := r.fetchVpcs() + if err != nil { + return nil, err + } + } + return r.ivpcs, nil +} + +func (r *SRegion) GetIEips() ([]cloudprovider.ICloudEIP, error) { + return nil, cloudprovider.ErrNotSupported +} + +func (r *SRegion) GetIVpcById(id string) (cloudprovider.ICloudVpc, error) { + vpc, err := r.getVpcById(id) + if err != nil { + return nil, err + } + vpc.region = r + return vpc, nil +} + +func (r *SRegion) GetIZoneById(id string) (cloudprovider.ICloudZone, error) { + izones, err := r.GetIZones() + if err != nil { + return nil, err + } + for i := 0; i < len(izones); i += 1 { + if izones[i].GetGlobalId() == id { + return izones[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (r *SRegion) GetIEipById(id string) (cloudprovider.ICloudEIP, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (r *SRegion) GetIVMById(id string) (cloudprovider.ICloudVM, error) { + vm, err := r.GetInstanceById(id) + if err != nil { + return nil, err + } + zone, err := r.FindZone(vm.Region) + if err != nil { + return nil, err + } + vm.host = &SHost{ + zone: zone, + } + return vm, nil +} + +func (r *SRegion) GetIDiskById(id string) (cloudprovider.ICloudDisk, error) { + return r.GetDisk(id) +} + +func (r *SRegion) GetIHosts() ([]cloudprovider.ICloudHost, error) { + izones, err := r.GetIZones() + if err != nil { + return nil, err + } + iHosts := make([]cloudprovider.ICloudHost, 0, len(izones)) + for i := range izones { + hosts, err := izones[i].GetIHosts() + if err != nil { + return nil, err + } + iHosts = append(iHosts, hosts...) + } + return iHosts, nil +} + +func (r *SRegion) GetIHostById(id string) (cloudprovider.ICloudHost, error) { + hosts, err := r.GetIHosts() + if err != nil { + return nil, err + } + for i := range hosts { + if hosts[i].GetGlobalId() == id { + return hosts[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (r *SRegion) GetIStorages() ([]cloudprovider.ICloudStorage, error) { + iStores := make([]cloudprovider.ICloudStorage, 0) + + izones, err := r.GetIZones() + if err != nil { + return nil, err + } + for i := 0; i < len(izones); i += 1 { + iZoneStores, err := izones[i].GetIStorages() + if err != nil { + return nil, err + } + iStores = append(iStores, iZoneStores...) + } + return iStores, nil +} + +func (r *SRegion) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) { + istores, err := r.GetIStorages() + if err != nil { + return nil, err + } + for i := range istores { + if istores[i].GetGlobalId() == id { + return istores[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (r *SRegion) GetIStoragecaches() ([]cloudprovider.ICloudStoragecache, error) { + sc := r.getStoragecache() + return []cloudprovider.ICloudStoragecache{sc}, nil +} + +func (r *SRegion) GetIStoragecacheById(id string) (cloudprovider.ICloudStoragecache, error) { + storageCache := r.getStoragecache() + if storageCache.GetGlobalId() == id { + return storageCache, nil + } + return nil, cloudprovider.ErrNotFound +} + +func (r *SRegion) GetProvider() string { + return api.CLOUD_PROVIDER_ECLOUD +} + +func (r *SRegion) GetCapabilities() []string { + return r.client.GetCapabilities() +} + +func (r *SRegion) GetClient() *SEcloudClient { + return r.client +} + +func (r *SRegion) FindZone(zoneRegion string) (*SZone, error) { + izones, err := r.GetIZones() + if err != nil { + return nil, errors.Wrap(err, "unable to GetZones") + } + findZone := func(zoneRegion string) *SZone { + for i := range izones { + zone := izones[i].(*SZone) + if zone.Region == zoneRegion { + return zone + } + } + return nil + } + return findZone(zoneRegion), nil +} diff --git a/pkg/multicloud/ecloud/request.go b/pkg/multicloud/ecloud/request.go new file mode 100644 index 0000000000..7195fb1206 --- /dev/null +++ b/pkg/multicloud/ecloud/request.go @@ -0,0 +1,227 @@ +// 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 ecloud + +import ( + "bytes" + "fmt" + "io" + "strings" + "time" + + "yunion.io/x/jsonutils" +) + +type IRequest interface { + GetScheme() string + GetMethod() string + SetMethod(string) + GetEndpoint() string + GetServerPath() string + GetPort() string + GetRegionId() string + GetHeaders() map[string]string + GetQueryParams() map[string]string + GetBodyReader() io.Reader + GetVersion() string + GetTimestamp() string + GetReadTimeout() time.Duration + GetConnectTimeout() time.Duration + GetHTTPSInsecure() bool + SetHTTPSInsecure(bool) + GetUserAgent() map[string]string +} + +type SJSONRequest struct { + SBaseRequest + Data jsonutils.JSONObject +} + +func NewJSONRequest(data jsonutils.JSONObject) *SJSONRequest { + jr := &SJSONRequest{ + Data: data, + SBaseRequest: *NewBaseRequest(), + } + headers := jr.GetHeaders() + headers["Content-Type"] = "application/json" + return jr +} + +func (jr *SJSONRequest) GetBodyReader() io.Reader { + if jr.Data == nil { + return nil + } + return strings.NewReader(jr.Data.String()) +} + +type SApiRequest struct { + SJSONRequest + RegionId string +} + +func NewApiRequest(regionId string, serverPath string, query map[string]string, data jsonutils.JSONObject) *SApiRequest { + r := SApiRequest{ + RegionId: regionId, + SJSONRequest: *NewJSONRequest(data), + } + r.ServerPath = serverPath + mergeMap(r.GetQueryParams(), query) + return &r +} + +func (rr *SApiRequest) GetScheme() string { + return "https" +} + +func (rr *SApiRequest) GetPort() string { + return "8443" +} + +func (rr *SApiRequest) GetEndpoint() string { + return fmt.Sprintf("api-%s.cmecloud.cn", rr.RegionId) +} + +type SConsoleRequest struct { + SJSONRequest + RegionId string +} + +func NewConsoleRequest(regionId string, serverPath string, query map[string]string, data jsonutils.JSONObject) *SConsoleRequest { + r := SConsoleRequest{ + RegionId: regionId, + SJSONRequest: *NewJSONRequest(data), + } + r.ServerPath = serverPath + mergeMap(r.GetQueryParams(), query) + return &r +} + +func (rr *SConsoleRequest) GetScheme() string { + return "https" +} + +func (rr *SConsoleRequest) GetPort() string { + return "8443" +} + +func (rr *SConsoleRequest) GetEndpoint() string { + return fmt.Sprintf("console-%s.cmecloud.cn", rr.RegionId) +} + +func mergeMap(m1, m2 map[string]string) { + if m2 == nil { + return + } + for k, v := range m2 { + m1[k] = v + } +} + +type SBaseRequest struct { + Scheme string + Method string + Endpoint string + ServerPath string + Port string + RegionId string + ReadTimeout time.Duration + ConnectTimeout time.Duration + isInsecure *bool + + QueryParams map[string]string + Headers map[string]string + Content []byte +} + +func NewBaseRequest() *SBaseRequest { + return &SBaseRequest{ + QueryParams: map[string]string{}, + Headers: map[string]string{}, + } +} + +func (br *SBaseRequest) GetScheme() string { + return br.Scheme +} + +func (br *SBaseRequest) GetMethod() string { + return br.Method +} + +func (br *SBaseRequest) SetMethod(method string) { + br.Method = method +} + +func (br *SBaseRequest) GetEndpoint() string { + return br.Endpoint +} + +func (br *SBaseRequest) GetServerPath() string { + return br.ServerPath +} + +func (br *SBaseRequest) GetPort() string { + return br.Port +} + +func (br *SBaseRequest) GetRegionId() string { + return br.RegionId +} + +func (br *SBaseRequest) GetHeaders() map[string]string { + return br.Headers +} + +func (br *SBaseRequest) GetQueryParams() map[string]string { + return br.QueryParams +} + +func (br *SBaseRequest) GetBodyReader() io.Reader { + if len(br.Content) > 0 { + return bytes.NewReader(br.Content) + } + return nil +} + +func (br *SBaseRequest) GetVersion() string { + return "2016-12-05" +} + +func (br *SBaseRequest) GetTimestamp() string { + return time.Now().Format("2006-01-02T15:04:05Z") +} + +func (br *SBaseRequest) GetReadTimeout() time.Duration { + return br.ReadTimeout +} + +func (br *SBaseRequest) GetConnectTimeout() time.Duration { + return br.ConnectTimeout +} + +func (br *SBaseRequest) GetHTTPSInsecure() bool { + if br.isInsecure == nil { + return false + } + return *br.isInsecure +} + +func (br *SBaseRequest) SetHTTPSInsecure(isInsecure bool) { + br.isInsecure = &isInsecure +} + +func (br *SBaseRequest) GetUserAgent() map[string]string { + return nil +} diff --git a/pkg/multicloud/ecloud/shell/disk.go b/pkg/multicloud/ecloud/shell/disk.go new file mode 100644 index 0000000000..cf3cea0316 --- /dev/null +++ b/pkg/multicloud/ecloud/shell/disk.go @@ -0,0 +1,33 @@ +// 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/ecloud" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type VDiskListOptions struct { + } + shellutils.R(&VDiskListOptions{}, "disk-list", "List disks", func(cli *ecloud.SRegion, args *VDiskListOptions) error { + disks, e := cli.GetDisks() + if e != nil { + return e + } + printList(disks, 0, 0, 0, nil) + return nil + }) +} diff --git a/pkg/multicloud/ecloud/shell/doc.go b/pkg/multicloud/ecloud/shell/doc.go new file mode 100644 index 0000000000..c505eaa625 --- /dev/null +++ b/pkg/multicloud/ecloud/shell/doc.go @@ -0,0 +1,15 @@ +// 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/ecloud/shell" diff --git a/pkg/multicloud/ecloud/shell/image.go b/pkg/multicloud/ecloud/shell/image.go new file mode 100644 index 0000000000..71344c8495 --- /dev/null +++ b/pkg/multicloud/ecloud/shell/image.go @@ -0,0 +1,34 @@ +// 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/ecloud" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type VImageListOptions struct { + Public bool + } + shellutils.R(&VImageListOptions{}, "image-list", "List images", func(cli *ecloud.SRegion, args *VImageListOptions) error { + images, e := cli.GetImages(args.Public) + if e != nil { + return e + } + printList(images, 0, 0, 0, nil) + return nil + }) +} diff --git a/pkg/multicloud/ecloud/shell/instance.go b/pkg/multicloud/ecloud/shell/instance.go new file mode 100644 index 0000000000..79e4b3bac2 --- /dev/null +++ b/pkg/multicloud/ecloud/shell/instance.go @@ -0,0 +1,68 @@ +// 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/ecloud" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type InstanceListOptions struct { + } + shellutils.R(&InstanceListOptions{}, "instance-list", "List intances", func(cli *ecloud.SRegion, args *InstanceListOptions) error { + instances, e := cli.GetInstancesWithHost("") + if e != nil { + return e + } + printList(instances, 0, 0, 0, []string{}) + return nil + }) + type InstanceShowOptions struct { + ID string + } + shellutils.R(&InstanceShowOptions{}, "instance-show", "Show intances", func(cli *ecloud.SRegion, args *InstanceShowOptions) error { + instance, e := cli.GetInstanceById(args.ID) + if e != nil { + return e + } + printObject(instance) + return nil + }) + shellutils.R(&InstanceShowOptions{}, "instance-nic-list", "List intance nics", func(cli *ecloud.SRegion, args *InstanceShowOptions) error { + instance, e := cli.GetInstanceById(args.ID) + if e != nil { + return e + } + nics, err := instance.GetINics() + if err != nil { + return err + } + printList(nics, 0, 0, 0, []string{}) + return nil + }) + shellutils.R(&InstanceShowOptions{}, "instance-disk-list", "List intance disks", func(cli *ecloud.SRegion, args *InstanceShowOptions) error { + instance, e := cli.GetInstanceById(args.ID) + if e != nil { + return e + } + disks, err := instance.GetIDisks() + if err != nil { + return err + } + printList(disks, 0, 0, 0, []string{}) + return nil + }) +} diff --git a/pkg/multicloud/ecloud/shell/network.go b/pkg/multicloud/ecloud/shell/network.go new file mode 100644 index 0000000000..51a9fc4339 --- /dev/null +++ b/pkg/multicloud/ecloud/shell/network.go @@ -0,0 +1,39 @@ +// 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/ecloud" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type VNetworkListOptions struct { + VpcId string `help:"Vpc ID"` + } + shellutils.R(&VNetworkListOptions{}, "subnet-list", "List subnets", func(cli *ecloud.SRegion, args *VNetworkListOptions) error { + ivpc, err := cli.GetIVpcById(args.VpcId) + if err != nil { + return err + } + vpc := ivpc.(*ecloud.SVpc) + networks, e := cli.GetNetworks(vpc.RouterId, "") + if e != nil { + return e + } + printList(networks, 0, 0, 0, nil) + return nil + }) +} diff --git a/pkg/multicloud/ecloud/shell/printutils.go b/pkg/multicloud/ecloud/shell/printutils.go new file mode 100644 index 0000000000..1b46ec704a --- /dev/null +++ b/pkg/multicloud/ecloud/shell/printutils.go @@ -0,0 +1,25 @@ +// 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/util/printutils" + +func printList(data interface{}, total, offset, limit int, columns []string) { + printutils.PrintInterfaceList(data, total, offset, limit, columns) +} + +func printObject(obj interface{}) { + printutils.PrintInterfaceObject(obj) +} diff --git a/pkg/multicloud/ecloud/shell/region.go b/pkg/multicloud/ecloud/shell/region.go new file mode 100644 index 0000000000..a4bc43d35d --- /dev/null +++ b/pkg/multicloud/ecloud/shell/region.go @@ -0,0 +1,38 @@ +// 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/ecloud" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type RegionListOptions struct { + } + shellutils.R(&RegionListOptions{}, "region-list", "List regions", func(cli *ecloud.SRegion, args *RegionListOptions) error { + regions := cli.GetClient().GetIRegions() + printList(regions, 0, 0, 0, nil) + return nil + }) + shellutils.R(&RegionListOptions{}, "zone-list", "List zones", func(cli *ecloud.SRegion, args *RegionListOptions) error { + zones, err := cli.GetIZones() + if err != nil { + return err + } + printList(zones, 0, 0, 0, nil) + return nil + }) +} diff --git a/pkg/multicloud/ecloud/shell/vpc.go b/pkg/multicloud/ecloud/shell/vpc.go new file mode 100644 index 0000000000..cb0b44461a --- /dev/null +++ b/pkg/multicloud/ecloud/shell/vpc.go @@ -0,0 +1,33 @@ +// 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/ecloud" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type VpcListOptions struct { + } + shellutils.R(&VpcListOptions{}, "vpc-list", "List vpcs", func(cli *ecloud.SRegion, args *VpcListOptions) error { + vpcs, err := cli.GetIVpcs() + if err != nil { + return err + } + printList(vpcs, 0, 0, 0, nil) + return nil + }) +} diff --git a/pkg/multicloud/ecloud/signer.go b/pkg/multicloud/ecloud/signer.go new file mode 100644 index 0000000000..d008224f54 --- /dev/null +++ b/pkg/multicloud/ecloud/signer.go @@ -0,0 +1,72 @@ +// 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 ecloud + +import ( + "crypto/hmac" + "crypto/sha1" + "encoding/hex" + + "yunion.io/x/pkg/util/stringutils" +) + +type ISigner interface { + GetName() string + GetVersion() string + GetAccessKeyId() string + GetNonce() string + Sign(stringToSign, secretPrefix string) string +} + +type SRamRoleSigner struct { + accessKeyId string + accessKeySecret string +} + +func NewRamRoleSigner(accessKeyId, accessKeySecret string) *SRamRoleSigner { + return &SRamRoleSigner{ + accessKeyId: accessKeyId, + accessKeySecret: accessKeySecret, + } +} + +func (s *SRamRoleSigner) GetName() string { + return "HmacSHA1" +} + +func (s *SRamRoleSigner) GetVersion() string { + return "V2.0" +} + +func (s *SRamRoleSigner) GetAccessKeyId() string { + return s.accessKeyId +} + +func (s *SRamRoleSigner) GetNonce() string { + return stringutils.UUID4() +} + +func (s *SRamRoleSigner) Sign(stringToSign, secretPrefix string) string { + secret := secretPrefix + s.accessKeySecret + return shaHmac1(stringToSign, secret) +} + +func shaHmac1(source, secret string) string { + key := []byte(secret) + hmac := hmac.New(sha1.New, key) + hmac.Write([]byte(source)) + signedBytes := hmac.Sum(nil) + return hex.EncodeToString(signedBytes) +} diff --git a/pkg/multicloud/ecloud/storage.go b/pkg/multicloud/ecloud/storage.go new file mode 100644 index 0000000000..adff255a36 --- /dev/null +++ b/pkg/multicloud/ecloud/storage.go @@ -0,0 +1,165 @@ +// 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 ecloud + +import ( + "fmt" + + "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +var storageTypes = []string{ + api.STORAGE_ECLOUD_CAPEBS, + api.STORAGE_ECLOUD_EBS, + api.STORAGE_ECLOUD_SSD, + api.STORAGE_ECLOUD_SSDEBS, + // special storage + api.STORAGE_ECLOUD_SYSTEM, +} + +type SStorage struct { + multicloud.SStorageBase + zone *SZone + storageType string +} + +func (s *SStorage) GetId() string { + return fmt.Sprintf("%s-%s-%s", s.zone.region.client.cpcfg.Id, s.zone.GetGlobalId(), s.storageType) +} + +func (s *SStorage) GetName() string { + return fmt.Sprintf("%s-%s-%s", s.zone.region.client.cpcfg.Name, s.zone.GetId(), s.storageType) +} + +func (s *SStorage) GetGlobalId() string { + return s.GetId() +} + +func (s *SStorage) GetStatus() string { + return api.STORAGE_ONLINE +} + +func (s *SStorage) Refresh() error { + return nil +} + +func (s *SStorage) IsEmulated() bool { + return true +} + +func (s *SStorage) GetIStoragecache() cloudprovider.ICloudStoragecache { + return s.zone.region.getStoragecache() +} + +func (s *SStorage) GetIZone() cloudprovider.ICloudZone { + return s.zone +} + +func (s *SStorage) GetIDisks() ([]cloudprovider.ICloudDisk, error) { + if s.storageType == api.STORAGE_ECLOUD_SYSTEM { + return nil, nil + } + disks, err := s.zone.region.GetDisks() + if err != nil { + return nil, err + } + + // 按storage type 过滤出disk + filtedDisks := make([]SDisk, 0) + for i := range disks { + disk := disks[i] + if disk.Type == s.storageType && disk.Region == s.zone.Region { + filtedDisks = append(filtedDisks, disk) + } + } + + idisks := make([]cloudprovider.ICloudDisk, len(filtedDisks)) + for i := 0; i < len(filtedDisks); i += 1 { + filtedDisks[i].storage = s + idisks[i] = &filtedDisks[i] + } + return idisks, nil +} + +func (s *SStorage) GetStorageType() string { + return s.storageType +} + +func (s *SStorage) GetMediumType() string { + if s.storageType == api.STORAGE_ECLOUD_SSD { + return api.DISK_TYPE_SSD + } else { + return api.DISK_TYPE_ROTATE + } +} + +func (s *SStorage) GetCapacityMB() int64 { + return 0 +} + +func (s *SStorage) GetCapacityUsedMB() int64 { + return 0 +} + +func (s *SStorage) GetStorageConf() jsonutils.JSONObject { + return jsonutils.NewDict() +} + +func (s *SStorage) GetEnabled() bool { + return true +} + +func (s *SStorage) CreateIDisk(conf *cloudprovider.DiskCreateConfig) (cloudprovider.ICloudDisk, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (s *SStorage) GetIDiskById(idStr string) (cloudprovider.ICloudDisk, error) { + if len(idStr) == 0 { + return nil, cloudprovider.ErrNotFound + } + + if disk, err := s.zone.region.GetDisk(idStr); err != nil { + return nil, err + } else { + disk.storage = s + return disk, nil + } +} + +func (s *SStorage) GetMountPoint() string { + return "" +} + +func (s *SStorage) IsSysDiskStore() bool { + return true +} + +func (s *SStorage) DisableSync() bool { + if s.storageType == api.STORAGE_ECLOUD_SYSTEM { + return true + } + return s.SStorageBase.DisableSync() +} + +func (s *SRegion) getStoragecache() *SStoragecache { + if s.storageCache == nil { + s.storageCache = &SStoragecache{region: s} + } + return s.storageCache +} diff --git a/pkg/multicloud/ecloud/storagecache.go b/pkg/multicloud/ecloud/storagecache.go new file mode 100644 index 0000000000..9f9b479220 --- /dev/null +++ b/pkg/multicloud/ecloud/storagecache.go @@ -0,0 +1,103 @@ +// 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 ecloud + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SStoragecache struct { + multicloud.SResourceBase + region *SRegion +} + +func (sc *SStoragecache) CreateIImage(snapshotId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) { + return nil, cloudprovider.ErrNotSupported +} + +func (sc *SStoragecache) DownloadImage(userCred mcclient.TokenCredential, imageId string, extId string, path string) (jsonutils.JSONObject, error) { + return nil, cloudprovider.ErrNotSupported +} + +func (sc *SStoragecache) UploadImage(ctx context.Context, userCred mcclient.TokenCredential, image *cloudprovider.SImageCreateOption, isForce bool) (string, error) { + img, err := sc.region.GetImage(image.ExternalId) + if err == nil { + return img.GetGlobalId(), nil + } + return "", err +} + +func (sc *SStoragecache) GetId() string { + return fmt.Sprintf("%s-%s", sc.region.client.cpcfg.Id, sc.region.GetId()) +} + +func (sc *SStoragecache) GetName() string { + return fmt.Sprintf("%s-%s", sc.region.client.cpcfg.Name, sc.region.GetId()) +} + +func (sc *SStoragecache) GetGlobalId() string { + return fmt.Sprintf("%s-%s", sc.region.client.cpcfg.Id, sc.region.GetGlobalId()) +} + +func (sc *SStoragecache) GetStatus() string { + return "available" +} + +func (sc *SStoragecache) Refresh() error { + return nil +} + +func (sc *SStoragecache) IsEmulated() bool { + return false +} + +func (sc *SStoragecache) GetICloudImages() ([]cloudprovider.ICloudImage, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (sc *SStoragecache) GetICustomizedCloudImages() ([]cloudprovider.ICloudImage, error) { + imagesSelf, err := sc.region.GetImages(false) + if err != nil { + return nil, errors.Wrapf(err, "GetImages") + } + ret := []cloudprovider.ICloudImage{} + for i := range imagesSelf { + imagesSelf[i].storageCache = sc + ret = append(ret, &imagesSelf[i]) + } + return ret, nil +} + +func (sc *SStoragecache) GetIImageById(extId string) (cloudprovider.ICloudImage, error) { + image, err := sc.region.GetImage(extId) + if err != nil { + return nil, errors.Wrap(err, "SStoragecache.GetIImageById.GetImage") + } + + image.storageCache = sc + return image, err +} + +func (sc *SStoragecache) GetPath() string { + return "" +} diff --git a/pkg/multicloud/ecloud/vpc.go b/pkg/multicloud/ecloud/vpc.go new file mode 100644 index 0000000000..06fa1b5b60 --- /dev/null +++ b/pkg/multicloud/ecloud/vpc.go @@ -0,0 +1,177 @@ +// 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 ecloud + +import ( + "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 SVpc struct { + multicloud.SVpc + + region *SRegion + iwires []cloudprovider.ICloudWire + + // wires + // secgroups + + Id string + Name string + Region string + EcStatus string + RouterId string + Scale string + UserId string + UserName string +} + +func (v *SVpc) GetId() string { + return v.Id +} + +func (v *SVpc) GetName() string { + return v.Name +} + +func (v *SVpc) GetGlobalId() string { + return v.GetId() +} + +func (v *SVpc) GetStatus() string { + switch v.EcStatus { + case "ACTIVE": + return api.VPC_STATUS_AVAILABLE + case "DOWN", "BUILD", "ERROR": + return api.VPC_STATUS_UNAVAILABLE + case "PENDING_DELETE": + return api.VPC_STATUS_DELETING + case "PENDING_CREATE", "PENDING_UPDATE": + return api.VPC_STATUS_PENDING + default: + return api.VPC_STATUS_UNKNOWN + } +} + +func (v *SVpc) Refresh() error { + n, err := v.region.getVpcById(v.Id) + if err != nil { + return err + } + return jsonutils.Update(v, n) + // TODO? v.fetchWires() +} + +func (v *SVpc) IsEmulated() bool { + return true +} + +func (v *SVpc) GetRegion() cloudprovider.ICloudRegion { + return v.region +} + +func (v *SVpc) GetIsDefault() bool { + return false +} + +func (v *SVpc) GetCidrBlock() string { + return "" +} + +func (v *SVpc) GetIWires() ([]cloudprovider.ICloudWire, error) { + if v.iwires == nil { + err := v.fetchWires() + if err != nil { + return nil, err + } + } + return v.iwires, nil +} + +func (v *SVpc) fetchWires() error { + networks, err := v.region.GetNetworks(v.RouterId, "") + if err != nil { + return err + } + izones, err := v.region.GetIZones() + if err != nil { + return errors.Wrap(err, "unable to GetZones") + } + findZone := func(zoneRegion string) *SZone { + for i := range izones { + zone := izones[i].(*SZone) + if zone.Region == zoneRegion { + return zone + } + } + return nil + } + zoneRegion2Wire := map[string]*SWire{} + for i := range networks { + zoneRegion := networks[i].Region + zone := findZone(zoneRegion) + var ( + wire *SWire + ok bool + ) + if wire, ok = zoneRegion2Wire[zoneRegion]; !ok { + wire = &SWire{ + vpc: v, + zone: zone, + } + zoneRegion2Wire[zoneRegion] = wire + } + wire.inetworks = append(wire.inetworks, &networks[i]) + } + iwires := make([]cloudprovider.ICloudWire, 0, len(zoneRegion2Wire)) + for _, wire := range zoneRegion2Wire { + iwires = append(iwires, wire) + } + v.iwires = iwires + return nil +} + +func (v *SVpc) GetISecurityGroups() ([]cloudprovider.ICloudSecurityGroup, error) { + return nil, nil +} + +func (v *SVpc) GetIRouteTables() ([]cloudprovider.ICloudRouteTable, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (v *SVpc) GetIRouteTableById(routeTableId string) (cloudprovider.ICloudRouteTable, error) { + return nil, nil +} + +func (v *SVpc) Delete() error { + return cloudprovider.ErrNotImplemented +} + +func (v *SVpc) GetIWireById(wireId string) (cloudprovider.ICloudWire, error) { + iwires, err := v.GetIWires() + if err != nil { + return nil, err + } + for i := range iwires { + if iwires[i].GetGlobalId() == wireId { + return iwires[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} diff --git a/pkg/multicloud/ecloud/wire.go b/pkg/multicloud/ecloud/wire.go new file mode 100644 index 0000000000..0be1431995 --- /dev/null +++ b/pkg/multicloud/ecloud/wire.go @@ -0,0 +1,138 @@ +// 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 ecloud + +import ( + "fmt" + + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SWire struct { + multicloud.SResourceBase + vpc *SVpc + zone *SZone + inetworks []cloudprovider.ICloudNetwork +} + +func (w *SWire) GetId() string { + return fmt.Sprintf("%s-%s", w.vpc.GetId(), w.vpc.region.GetId()) +} + +func (w *SWire) GetName() string { + return w.GetId() +} + +func (w *SWire) GetGlobalId() string { + return fmt.Sprintf("%s-%s", w.vpc.GetGlobalId(), w.vpc.region.GetGlobalId()) +} + +func (w *SWire) GetStatus() string { + return "available" +} + +func (w *SWire) Refresh() error { + return nil + // TODO? w.fetchNetworks() +} + +func (w *SWire) IsEmulated() bool { + return true +} + +func (w *SWire) GetIVpc() cloudprovider.ICloudVpc { + return w.vpc +} + +func (w *SWire) GetIZone() cloudprovider.ICloudZone { + return w.zone +} + +func (w *SWire) GetINetworks() ([]cloudprovider.ICloudNetwork, error) { + if w.inetworks == nil { + err := w.fetchNetworks() + if err != nil { + return nil, err + } + } + return w.inetworks, nil +} + +func (w *SWire) GetBandwidth() int { + return 10000 +} + +func (w *SWire) GetINetworkById(netid string) (cloudprovider.ICloudNetwork, error) { + n, err := w.vpc.region.GetNetworkById(w.vpc.RouterId, w.zone.Region, netid) + if err != nil { + return nil, err + } + n.wire = w + return n, nil +} + +func (w *SWire) CreateINetwork(opts *cloudprovider.SNetworkCreateOptions) (cloudprovider.ICloudNetwork, error) { + return nil, nil +} + +func (w *SWire) CreateNetworks() (*SNetwork, error) { + // data := jsonutils.NewDict() + // req := jsonutils.NewDict() + // req.Set("availabilityZoneHints", jsonutils.NewString("RegionOne")) + // req.Set("networkName", jsonutils.NewString("zyone")) + // req.Set("networkTypeEnum", jsonutils.NewString("VM")) + // req.Set("region", jsonutils.NewString("RegionOne")) + // req.Set("routerId", jsonutils.NewString(w.vpc.RouterId)) + // subnet := jsonutils.NewDict() + // subnet.Set("cidr", jsonutils.NewString("192.168.46.0/24")) + // subnet.Set("ipVersion", jsonutils.NewString("4")) + // subnet.Set("subnetName", jsonutils.NewString("zyone")) + // req.Set("subnets", jsonutils.NewArray(subnet)) + // data.Set("networkCreateReq", req) + // fmt.Printf("data:\n%s", data.PrettyString()) + // request := NewConsoleRequest(w.vpc.region.ID, "/api/v2/netcenter/network", nil, req) + // request.SetMethod("POST") + // resp, err := w.vpc.region.client.request(context.Background(), request) + // if err != nil { + // return nil, err + // } + // fmt.Printf("resp:%s", resp) + // return nil, nil + return nil, cloudprovider.ErrNotImplemented +} + +func (w *SWire) GetNetworkById(netId string) (*SNetwork, error) { + n, err := w.vpc.region.GetNetworkById(w.vpc.RouterId, w.zone.Region, netId) + if err != nil { + return nil, err + } + n.wire = w + return n, nil +} + +func (w *SWire) fetchNetworks() error { + networks, err := w.vpc.region.GetNetworks(w.vpc.RouterId, w.zone.Region) + if err != nil { + return err + } + inetworks := make([]cloudprovider.ICloudNetwork, len(networks)) + for i := range networks { + networks[i].wire = w + inetworks[i] = &networks[i] + } + w.inetworks = inetworks + return nil +} diff --git a/pkg/multicloud/ecloud/zone.go b/pkg/multicloud/ecloud/zone.go new file mode 100644 index 0000000000..6f6e7f18de --- /dev/null +++ b/pkg/multicloud/ecloud/zone.go @@ -0,0 +1,147 @@ +// 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 ecloud + +import ( + "fmt" + + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SZone struct { + multicloud.SResourceBase + region *SRegion + host *SHost + + istorages []cloudprovider.ICloudStorage + + ID string `json:"id"` + Name string `json:"name"` + Region string `json:"region"` + Deleted bool + Visible bool +} + +func (z *SZone) GetId() string { + return z.ID +} + +func (z *SZone) GetName() string { + //return fmt.Sprintf("%s %s", CLOUD_PROVIDER_ECLOUD_CN, z.Name) + return z.Name +} + +func (z *SZone) GetGlobalId() string { + return fmt.Sprintf("%s/%s", z.region.GetGlobalId(), z.ID) +} + +func (z *SZone) GetStatus() string { + if z.Deleted || !z.Visible { + return "disable" + } + return "enable" +} + +func (z *SZone) Refresh() error { + return nil +} + +func (z *SZone) IsEmulated() bool { + return false +} + +func (z *SZone) GetI18n() cloudprovider.SModelI18nTable { + table := cloudprovider.SModelI18nTable{} + table["name"] = cloudprovider.NewSModelI18nEntry(z.GetName()).CN(z.GetName()).EN(fmt.Sprintf("%s %s", CLOUD_PROVIDER_ECLOUD_EN, z.Name)) + return table +} + +func (z *SZone) GetIRegion() cloudprovider.ICloudRegion { + return z.region +} + +func (z *SZone) getHost() cloudprovider.ICloudHost { + if z.host == nil { + z.host = &SHost{ + zone: z, + } + } + return z.host +} + +func (z *SZone) GetIHosts() ([]cloudprovider.ICloudHost, error) { + return []cloudprovider.ICloudHost{z.getHost()}, nil +} + +func (z *SZone) GetIHostById(id string) (cloudprovider.ICloudHost, error) { + host := z.getHost() + if host.GetGlobalId() == id { + return host, nil + } + return nil, cloudprovider.ErrNotFound +} + +func (z *SZone) GetIStorages() ([]cloudprovider.ICloudStorage, error) { + if z.istorages == nil { + err := z.fetchStorages() + if err != nil { + return nil, err + } + } + return z.istorages, nil +} + +func (z *SZone) fetchStorages() error { + istorages := make([]cloudprovider.ICloudStorage, len(storageTypes)) + for i := range istorages { + istorages[i] = &SStorage{ + zone: z, + storageType: storageTypes[i], + } + } + z.istorages = istorages + return nil +} + +func (z *SZone) getStorageByType(t string) (*SStorage, error) { + istorages, err := z.GetIStorages() + if err != nil { + return nil, err + } + for i := range istorages { + if istorages[i].GetStorageType() == t { + return istorages[i].(*SStorage), nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (z *SZone) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) { + istorages, err := z.GetIStorages() + if err != nil { + return nil, err + } + for i := range istorages { + if istorages[i].GetGlobalId() == id { + return istorages[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +type SZoneRegionBase struct { + Region string +} diff --git a/pkg/multicloud/loader/loader.go b/pkg/multicloud/loader/loader.go index 39cb6ef578..18eba323a5 100644 --- a/pkg/multicloud/loader/loader.go +++ b/pkg/multicloud/loader/loader.go @@ -22,6 +22,7 @@ import ( _ "yunion.io/x/onecloud/pkg/multicloud/aws/provider" _ "yunion.io/x/onecloud/pkg/multicloud/azure/provider" _ "yunion.io/x/onecloud/pkg/multicloud/ctyun/provider" + _ "yunion.io/x/onecloud/pkg/multicloud/ecloud/provider" // public clouds _ "yunion.io/x/onecloud/pkg/multicloud/esxi/provider" // private clouds _ "yunion.io/x/onecloud/pkg/multicloud/google/provider" // public clouds _ "yunion.io/x/onecloud/pkg/multicloud/huawei/provider" @@ -32,7 +33,6 @@ import ( _ "yunion.io/x/onecloud/pkg/multicloud/qcloud/provider" _ "yunion.io/x/onecloud/pkg/multicloud/ucloud/provider" // object storages _ "yunion.io/x/onecloud/pkg/multicloud/zstack/provider" // public clouds - _ "yunion.io/x/onecloud/pkg/multicloud/ecloud/provider" // public clouds ) func init() {